1 /* 2 * PROJECT: ReactOS Applications Manager 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Displaying a download dialog 5 * COPYRIGHT: Copyright 2001 John R. Sheets (for CodeWeavers) 6 * Copyright 2004 Mike McCormack (for CodeWeavers) 7 * Copyright 2005 Ge van Geldorp (gvg@reactos.org) 8 * Copyright 2009 Dmitry Chapyshev (dmitry@reactos.org) 9 * Copyright 2015 Ismael Ferreras Morezuelas (swyterzone+ros@gmail.com) 10 * Copyright 2017 Alexander Shaposhnikov (sanchaez@reactos.org) 11 */ 12 13 /* 14 * Based on Wine dlls/shdocvw/shdocvw_main.c 15 * 16 * This library is free software; you can redistribute it and/or 17 * modify it under the terms of the GNU Lesser General Public 18 * License as published by the Free Software Foundation; either 19 * version 2.1 of the License, or (at your option) any later version. 20 * 21 * This library is distributed in the hope that it will be useful, 22 * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 24 * Lesser General Public License for more details. 25 * 26 * You should have received a copy of the GNU Lesser General Public 27 * License along with this library; if not, write to the Free Software 28 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 29 */ 30 #include "rapps.h" 31 32 #include <shlobj_undoc.h> 33 #include <shlguid_undoc.h> 34 35 #include <atlbase.h> 36 #include <atlcom.h> 37 #include <atlwin.h> 38 #include <wininet.h> 39 #include <shellutils.h> 40 41 #include <debug.h> 42 43 #include <ui/rosctrls.h> 44 #include <windowsx.h> 45 #include <process.h> 46 #undef SubclassWindow 47 48 #include "rosui.h" 49 #include "dialogs.h" 50 #include "misc.h" 51 52 #ifdef USE_CERT_PINNING 53 #define CERT_ISSUER_INFO_OLD "US\r\nLet's Encrypt\r\nLet's Encrypt Authority X3" 54 #define CERT_ISSUER_INFO_NEW "US\r\nLet's Encrypt\r\nR3" 55 #define CERT_SUBJECT_INFO "rapps.reactos.org" 56 #endif 57 58 enum DownloadType 59 { 60 DLTYPE_APPLICATION, 61 DLTYPE_DBUPDATE, 62 DLTYPE_DBUPDATE_UNOFFICIAL 63 }; 64 65 enum DownloadStatus 66 { 67 DLSTATUS_WAITING = IDS_STATUS_WAITING, 68 DLSTATUS_DOWNLOADING = IDS_STATUS_DOWNLOADING, 69 DLSTATUS_WAITING_INSTALL = IDS_STATUS_DOWNLOADED, 70 DLSTATUS_INSTALLING = IDS_STATUS_INSTALLING, 71 DLSTATUS_INSTALLED = IDS_STATUS_INSTALLED, 72 DLSTATUS_FINISHED = IDS_STATUS_FINISHED 73 }; 74 75 CStringW 76 LoadStatusString(DownloadStatus StatusParam) 77 { 78 CStringW szString; 79 szString.LoadStringW(StatusParam); 80 return szString; 81 } 82 83 struct DownloadInfo 84 { 85 DownloadInfo() 86 { 87 } 88 DownloadInfo(const CAppInfo &AppInfo) : DLType(DLTYPE_APPLICATION) 89 { 90 AppInfo.GetDownloadInfo(szUrl, szSHA1, SizeInBytes); 91 szName = AppInfo.szDisplayName; 92 } 93 94 DownloadType DLType; 95 CStringW szUrl; 96 CStringW szName; 97 CStringW szSHA1; 98 ULONG SizeInBytes; 99 }; 100 101 struct DownloadParam 102 { 103 DownloadParam() : Dialog(NULL), AppInfo(), szCaption(NULL) 104 { 105 } 106 DownloadParam(HWND dlg, const ATL::CSimpleArray<DownloadInfo> &info, LPCWSTR caption) 107 : Dialog(dlg), AppInfo(info), szCaption(caption) 108 { 109 } 110 111 HWND Dialog; 112 ATL::CSimpleArray<DownloadInfo> AppInfo; 113 LPCWSTR szCaption; 114 }; 115 116 class CDownloaderProgress : public CWindowImpl<CDownloaderProgress, CWindow, CControlWinTraits> 117 { 118 CStringW m_szProgressText; 119 120 public: 121 CDownloaderProgress() 122 { 123 } 124 125 VOID 126 SetMarquee(BOOL Enable) 127 { 128 if (Enable) 129 ModifyStyle(0, PBS_MARQUEE, 0); 130 else 131 ModifyStyle(PBS_MARQUEE, 0, 0); 132 133 SendMessage(PBM_SETMARQUEE, Enable, 0); 134 } 135 136 VOID 137 SetProgress(ULONG ulProgress, ULONG ulProgressMax) 138 { 139 WCHAR szProgress[100]; 140 141 /* format the bits and bytes into pretty and accessible units... */ 142 StrFormatByteSizeW(ulProgress, szProgress, _countof(szProgress)); 143 144 /* use our subclassed progress bar text subroutine */ 145 CStringW ProgressText; 146 147 if (ulProgressMax) 148 { 149 /* total size is known */ 150 WCHAR szProgressMax[100]; 151 UINT uiPercentage = ((ULONGLONG)ulProgress * 100) / ulProgressMax; 152 153 /* send the current progress to the progress bar */ 154 if (!IsWindow()) 155 return; 156 SendMessage(PBM_SETPOS, uiPercentage, 0); 157 158 /* format total download size */ 159 StrFormatByteSizeW(ulProgressMax, szProgressMax, _countof(szProgressMax)); 160 161 /* generate the text on progress bar */ 162 ProgressText.Format(L"%u%% \x2014 %ls / %ls", uiPercentage, szProgress, szProgressMax); 163 } 164 else 165 { 166 /* send the current progress to the progress bar */ 167 if (!IsWindow()) 168 return; 169 SendMessage(PBM_SETPOS, 0, 0); 170 171 /* total size is not known, display only current size */ 172 ProgressText.Format(L"%ls...", szProgress); 173 } 174 175 /* and finally display it */ 176 if (!IsWindow()) 177 return; 178 SetWindowText(ProgressText.GetString()); 179 } 180 181 LRESULT 182 OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 183 { 184 return TRUE; 185 } 186 187 LRESULT 188 OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 189 { 190 PAINTSTRUCT ps; 191 HDC hDC = BeginPaint(&ps), hdcMem; 192 HBITMAP hbmMem; 193 HANDLE hOld; 194 RECT myRect; 195 UINT win_width, win_height; 196 197 GetClientRect(&myRect); 198 199 /* grab the progress bar rect size */ 200 win_width = myRect.right - myRect.left; 201 win_height = myRect.bottom - myRect.top; 202 203 /* create an off-screen DC for double-buffering */ 204 hdcMem = CreateCompatibleDC(hDC); 205 hbmMem = CreateCompatibleBitmap(hDC, win_width, win_height); 206 207 hOld = SelectObject(hdcMem, hbmMem); 208 209 /* call the original draw code and redirect it to our memory buffer */ 210 DefWindowProc(uMsg, (WPARAM)hdcMem, lParam); 211 212 /* draw our nifty progress text over it */ 213 SelectFont(hdcMem, GetStockFont(DEFAULT_GUI_FONT)); 214 DrawShadowText( 215 hdcMem, m_szProgressText.GetString(), m_szProgressText.GetLength(), &myRect, 216 DT_CENTER | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE, GetSysColor(COLOR_CAPTIONTEXT), 217 GetSysColor(COLOR_3DDKSHADOW), 1, 1); 218 219 /* transfer the off-screen DC to the screen */ 220 BitBlt(hDC, 0, 0, win_width, win_height, hdcMem, 0, 0, SRCCOPY); 221 222 /* free the off-screen DC */ 223 SelectObject(hdcMem, hOld); 224 DeleteObject(hbmMem); 225 DeleteDC(hdcMem); 226 227 EndPaint(&ps); 228 return 0; 229 } 230 231 LRESULT 232 OnSetText(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 233 { 234 PCWSTR pszText = (PCWSTR)lParam; 235 if (pszText) 236 { 237 if (m_szProgressText != pszText) 238 { 239 m_szProgressText = pszText; 240 InvalidateRect(NULL, TRUE); 241 } 242 } 243 else 244 { 245 if (!m_szProgressText.IsEmpty()) 246 { 247 m_szProgressText.Empty(); 248 InvalidateRect(NULL, TRUE); 249 } 250 } 251 return TRUE; 252 } 253 254 BEGIN_MSG_MAP(CDownloaderProgress) 255 MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd) 256 MESSAGE_HANDLER(WM_PAINT, OnPaint) 257 MESSAGE_HANDLER(WM_SETTEXT, OnSetText) 258 END_MSG_MAP() 259 }; 260 261 class CDowloadingAppsListView : public CListView 262 { 263 public: 264 HWND 265 Create(HWND hwndParent) 266 { 267 RECT r; 268 ::GetClientRect(hwndParent, &r); 269 r.top = (2 * r.top + 1 * r.bottom) / 3; /* The vertical position at ratio 1 : 2 */ 270 #define MARGIN 10 271 ::InflateRect(&r, -MARGIN, -MARGIN); 272 273 const DWORD style = WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | 274 LVS_NOCOLUMNHEADER; 275 276 HWND hwnd = CListView::Create(hwndParent, r, NULL, style, WS_EX_CLIENTEDGE); 277 278 AddColumn(0, 150, LVCFMT_LEFT); 279 AddColumn(1, 120, LVCFMT_LEFT); 280 281 return hwnd; 282 } 283 284 VOID 285 LoadList(ATL::CSimpleArray<DownloadInfo> arrInfo) 286 { 287 for (INT i = 0; i < arrInfo.GetSize(); ++i) 288 { 289 AddRow(i, arrInfo[i].szName.GetString(), DLSTATUS_WAITING); 290 } 291 } 292 293 VOID 294 SetDownloadStatus(INT ItemIndex, DownloadStatus Status) 295 { 296 CStringW szBuffer = LoadStatusString(Status); 297 SetItemText(ItemIndex, 1, szBuffer.GetString()); 298 } 299 300 BOOL 301 AddItem(INT ItemIndex, LPWSTR lpText) 302 { 303 LVITEMW Item; 304 305 ZeroMemory(&Item, sizeof(Item)); 306 307 Item.mask = LVIF_TEXT | LVIF_STATE; 308 Item.pszText = lpText; 309 Item.iItem = ItemIndex; 310 311 return InsertItem(&Item); 312 } 313 314 VOID 315 AddRow(INT RowIndex, LPCWSTR szAppName, const DownloadStatus Status) 316 { 317 CStringW szStatus = LoadStatusString(Status); 318 AddItem(RowIndex, const_cast<LPWSTR>(szAppName)); 319 SetDownloadStatus(RowIndex, Status); 320 } 321 322 BOOL 323 AddColumn(INT Index, INT Width, INT Format) 324 { 325 LVCOLUMNW Column; 326 ZeroMemory(&Column, sizeof(Column)); 327 328 Column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM; 329 Column.iSubItem = Index; 330 Column.cx = Width; 331 Column.fmt = Format; 332 333 return (InsertColumn(Index, &Column) == -1) ? FALSE : TRUE; 334 } 335 }; 336 337 #ifdef USE_CERT_PINNING 338 static BOOL 339 CertGetSubjectAndIssuer(HINTERNET hFile, CLocalPtr<char> &subjectInfo, CLocalPtr<char> &issuerInfo) 340 { 341 DWORD certInfoLength; 342 INTERNET_CERTIFICATE_INFOA certInfo; 343 DWORD size, flags; 344 345 size = sizeof(flags); 346 if (!InternetQueryOptionA(hFile, INTERNET_OPTION_SECURITY_FLAGS, &flags, &size)) 347 { 348 return FALSE; 349 } 350 351 if (!flags & SECURITY_FLAG_SECURE) 352 { 353 return FALSE; 354 } 355 356 /* Despite what the header indicates, the implementation of INTERNET_CERTIFICATE_INFO is not Unicode-aware. */ 357 certInfoLength = sizeof(certInfo); 358 if (!InternetQueryOptionA(hFile, INTERNET_OPTION_SECURITY_CERTIFICATE_STRUCT, &certInfo, &certInfoLength)) 359 { 360 return FALSE; 361 } 362 363 subjectInfo.Attach(certInfo.lpszSubjectInfo); 364 issuerInfo.Attach(certInfo.lpszIssuerInfo); 365 366 if (certInfo.lpszProtocolName) 367 LocalFree(certInfo.lpszProtocolName); 368 if (certInfo.lpszSignatureAlgName) 369 LocalFree(certInfo.lpszSignatureAlgName); 370 if (certInfo.lpszEncryptionAlgName) 371 LocalFree(certInfo.lpszEncryptionAlgName); 372 373 return certInfo.lpszSubjectInfo && certInfo.lpszIssuerInfo; 374 } 375 #endif 376 377 inline VOID 378 MessageBox_LoadString(HWND hMainWnd, INT StringID) 379 { 380 CStringW szMsgText; 381 if (szMsgText.LoadStringW(StringID)) 382 { 383 MessageBoxW(hMainWnd, szMsgText.GetString(), NULL, MB_OK | MB_ICONERROR); 384 } 385 } 386 387 // Download dialog (loaddlg.cpp) 388 class CDownloadManager 389 { 390 static ATL::CSimpleArray<DownloadInfo> AppsDownloadList; 391 static CDowloadingAppsListView DownloadsListView; 392 static CDownloaderProgress ProgressBar; 393 static BOOL bCancelled; 394 static BOOL bModal; 395 static VOID 396 UpdateProgress(HWND hDlg, ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText); 397 398 public: 399 static VOID 400 Add(DownloadInfo info); 401 static VOID 402 Download(const DownloadInfo &DLInfo, BOOL bIsModal = FALSE); 403 static INT_PTR CALLBACK 404 DownloadDlgProc(HWND Dlg, UINT uMsg, WPARAM wParam, LPARAM lParam); 405 static unsigned int WINAPI 406 ThreadFunc(LPVOID Context); 407 static VOID LaunchDownloadDialog(BOOL); 408 }; 409 410 // CDownloadManager 411 ATL::CSimpleArray<DownloadInfo> CDownloadManager::AppsDownloadList; 412 CDowloadingAppsListView CDownloadManager::DownloadsListView; 413 CDownloaderProgress CDownloadManager::ProgressBar; 414 BOOL CDownloadManager::bCancelled = FALSE; 415 BOOL CDownloadManager::bModal = FALSE; 416 417 VOID 418 CDownloadManager::Add(DownloadInfo info) 419 { 420 AppsDownloadList.Add(info); 421 } 422 423 VOID 424 CDownloadManager::Download(const DownloadInfo &DLInfo, BOOL bIsModal) 425 { 426 AppsDownloadList.RemoveAll(); 427 AppsDownloadList.Add(DLInfo); 428 LaunchDownloadDialog(bIsModal); 429 } 430 431 INT_PTR CALLBACK 432 CDownloadManager::DownloadDlgProc(HWND Dlg, UINT uMsg, WPARAM wParam, LPARAM lParam) 433 { 434 static WCHAR szCaption[MAX_PATH]; 435 436 switch (uMsg) 437 { 438 case WM_INITDIALOG: 439 { 440 HICON hIconSm, hIconBg; 441 CStringW szTempCaption; 442 443 bCancelled = FALSE; 444 445 if (hMainWnd) 446 { 447 hIconBg = (HICON)GetClassLongPtrW(hMainWnd, GCLP_HICON); 448 hIconSm = (HICON)GetClassLongPtrW(hMainWnd, GCLP_HICONSM); 449 } 450 if (!hMainWnd || (!hIconBg || !hIconSm)) 451 { 452 /* Load the default icon */ 453 hIconBg = hIconSm = LoadIconW(hInst, MAKEINTRESOURCEW(IDI_MAIN)); 454 } 455 456 if (hIconBg && hIconSm) 457 { 458 SendMessageW(Dlg, WM_SETICON, ICON_BIG, (LPARAM)hIconBg); 459 SendMessageW(Dlg, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm); 460 } 461 462 HWND Item = GetDlgItem(Dlg, IDC_DOWNLOAD_PROGRESS); 463 if (Item) 464 { 465 // initialize the default values for our nifty progress bar 466 // and subclass it so that it learns to print a status text 467 ProgressBar.SubclassWindow(Item); 468 ProgressBar.SendMessage(PBM_SETRANGE, 0, MAKELPARAM(0, 100)); 469 ProgressBar.SendMessage(PBM_SETPOS, 0, 0); 470 if (AppsDownloadList.GetSize() > 0) 471 ProgressBar.SetProgress(0, AppsDownloadList[0].SizeInBytes); 472 } 473 474 // Add a ListView 475 HWND hListView = DownloadsListView.Create(Dlg); 476 if (!hListView) 477 { 478 return FALSE; 479 } 480 DownloadsListView.LoadList(AppsDownloadList); 481 482 // Get a dlg string for later use 483 GetWindowTextW(Dlg, szCaption, _countof(szCaption)); 484 485 // Hide a placeholder from displaying 486 szTempCaption = szCaption; 487 szTempCaption.Replace(L"%ls", L""); 488 SetWindowText(Dlg, szTempCaption.GetString()); 489 490 ShowWindow(Dlg, SW_SHOW); 491 492 // Start download process 493 DownloadParam *param = new DownloadParam(Dlg, AppsDownloadList, szCaption); 494 unsigned int ThreadId; 495 HANDLE Thread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void *)param, 0, &ThreadId); 496 if (!Thread) 497 { 498 return FALSE; 499 } 500 501 CloseHandle(Thread); 502 AppsDownloadList.RemoveAll(); 503 return TRUE; 504 } 505 506 case WM_COMMAND: 507 if (wParam == IDCANCEL) 508 { 509 bCancelled = TRUE; 510 PostMessageW(Dlg, WM_CLOSE, 0, 0); 511 } 512 return FALSE; 513 514 case WM_CLOSE: 515 if (ProgressBar) 516 ProgressBar.UnsubclassWindow(TRUE); 517 if (CDownloadManager::bModal) 518 { 519 ::EndDialog(Dlg, 0); 520 } 521 else 522 { 523 ::DestroyWindow(Dlg); 524 } 525 return TRUE; 526 527 default: 528 return FALSE; 529 } 530 } 531 532 BOOL UrlHasBeenCopied; 533 534 VOID 535 CDownloadManager::UpdateProgress( 536 HWND hDlg, 537 ULONG ulProgress, 538 ULONG ulProgressMax, 539 ULONG ulStatusCode, 540 LPCWSTR szStatusText) 541 { 542 HWND Item; 543 544 if (!IsWindow(hDlg)) 545 return; 546 ProgressBar.SetProgress(ulProgress, ulProgressMax); 547 548 if (!IsWindow(hDlg)) 549 return; 550 Item = GetDlgItem(hDlg, IDC_DOWNLOAD_STATUS); 551 if (Item && szStatusText && wcslen(szStatusText) > 0 && UrlHasBeenCopied == FALSE) 552 { 553 SIZE_T len = wcslen(szStatusText) + 1; 554 CStringW buf; 555 DWORD dummyLen; 556 557 /* beautify our url for display purposes */ 558 if (!InternetCanonicalizeUrlW(szStatusText, buf.GetBuffer(len), &dummyLen, ICU_DECODE | ICU_NO_ENCODE)) 559 { 560 /* just use the original */ 561 buf.ReleaseBuffer(); 562 buf = szStatusText; 563 } 564 else 565 { 566 buf.ReleaseBuffer(); 567 } 568 569 /* paste it into our dialog and don't do it again in this instance */ 570 ::SetWindowText(Item, buf.GetString()); 571 UrlHasBeenCopied = TRUE; 572 } 573 } 574 575 BOOL 576 ShowLastError(HWND hWndOwner, BOOL bInetError, DWORD dwLastError) 577 { 578 CLocalPtr<WCHAR> lpMsg; 579 580 if (!FormatMessageW( 581 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | 582 (bInetError ? FORMAT_MESSAGE_FROM_HMODULE : FORMAT_MESSAGE_FROM_SYSTEM), 583 (bInetError ? GetModuleHandleW(L"wininet.dll") : NULL), dwLastError, LANG_USER_DEFAULT, (LPWSTR)&lpMsg, 0, 584 NULL)) 585 { 586 DPRINT1("FormatMessageW unexpected failure (err %d)\n", GetLastError()); 587 return FALSE; 588 } 589 590 MessageBoxW(hWndOwner, lpMsg, NULL, MB_OK | MB_ICONERROR); 591 return TRUE; 592 } 593 594 unsigned int WINAPI 595 CDownloadManager::ThreadFunc(LPVOID param) 596 { 597 CPathW Path; 598 PWSTR p, q; 599 600 HWND hDlg = static_cast<DownloadParam *>(param)->Dialog; 601 HWND Item; 602 INT iAppId; 603 604 ULONG dwContentLen, dwBytesWritten, dwBytesRead, dwStatus; 605 ULONG dwCurrentBytesRead = 0; 606 ULONG dwStatusLen = sizeof(dwStatus); 607 608 BOOL bTempfile = FALSE; 609 610 HINTERNET hOpen = NULL; 611 HINTERNET hFile = NULL; 612 HANDLE hOut = INVALID_HANDLE_VALUE; 613 614 unsigned char lpBuffer[4096]; 615 LPCWSTR lpszAgent = L"RApps/1.1"; 616 URL_COMPONENTSW urlComponents; 617 size_t urlLength, filenameLength; 618 619 const ATL::CSimpleArray<DownloadInfo> &InfoArray = static_cast<DownloadParam *>(param)->AppInfo; 620 LPCWSTR szCaption = static_cast<DownloadParam *>(param)->szCaption; 621 CStringW szNewCaption; 622 623 const DWORD dwUrlConnectFlags = 624 INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_KEEP_CONNECTION; 625 626 if (InfoArray.GetSize() <= 0) 627 { 628 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD); 629 goto end; 630 } 631 632 for (iAppId = 0; iAppId < InfoArray.GetSize(); ++iAppId) 633 { 634 // Reset progress bar 635 if (!IsWindow(hDlg)) 636 break; 637 Item = GetDlgItem(hDlg, IDC_DOWNLOAD_PROGRESS); 638 if (Item) 639 { 640 ProgressBar.SetMarquee(FALSE); 641 ProgressBar.SendMessage(PBM_SETPOS, 0, 0); 642 ProgressBar.SetProgress(0, InfoArray[iAppId].SizeInBytes); 643 } 644 645 // is this URL an update package for RAPPS? if so store it in a different place 646 if (InfoArray[iAppId].DLType != DLTYPE_APPLICATION) 647 { 648 if (!GetStorageDirectory(Path)) 649 { 650 ShowLastError(hMainWnd, FALSE, GetLastError()); 651 goto end; 652 } 653 } 654 else 655 { 656 Path = SettingsInfo.szDownloadDir; 657 } 658 659 // Change caption to show the currently downloaded app 660 switch (InfoArray[iAppId].DLType) 661 { 662 case DLTYPE_APPLICATION: 663 szNewCaption.Format(szCaption, InfoArray[iAppId].szName.GetString()); 664 break; 665 case DLTYPE_DBUPDATE: 666 szNewCaption.LoadStringW(IDS_DL_DIALOG_DB_DOWNLOAD_DISP); 667 break; 668 case DLTYPE_DBUPDATE_UNOFFICIAL: 669 szNewCaption.LoadStringW(IDS_DL_DIALOG_DB_UNOFFICIAL_DOWNLOAD_DISP); 670 break; 671 } 672 673 if (!IsWindow(hDlg)) 674 goto end; 675 SetWindowTextW(hDlg, szNewCaption.GetString()); 676 677 // build the path for the download 678 p = wcsrchr(InfoArray[iAppId].szUrl.GetString(), L'/'); 679 q = wcsrchr(InfoArray[iAppId].szUrl.GetString(), L'?'); 680 681 // do we have a final slash separator? 682 if (!p) 683 { 684 MessageBox_LoadString(hMainWnd, IDS_UNABLE_PATH); 685 goto end; 686 } 687 688 // prepare the tentative length of the filename, maybe we've to remove part of it later on 689 filenameLength = wcslen(p) * sizeof(WCHAR); 690 691 /* do we have query arguments in the target URL after the filename? account for them 692 (e.g. https://example.org/myfile.exe?no_adware_plz) */ 693 if (q && q > p && (q - p) > 0) 694 filenameLength -= wcslen(q - 1) * sizeof(WCHAR); 695 696 // is the path valid? can we access it? 697 if (GetFileAttributesW(Path) == INVALID_FILE_ATTRIBUTES) 698 { 699 if (!CreateDirectoryW(Path, NULL)) 700 { 701 ShowLastError(hMainWnd, FALSE, GetLastError()); 702 goto end; 703 } 704 } 705 706 switch (InfoArray[iAppId].DLType) 707 { 708 case DLTYPE_DBUPDATE: 709 case DLTYPE_DBUPDATE_UNOFFICIAL: 710 Path += APPLICATION_DATABASE_NAME; 711 break; 712 case DLTYPE_APPLICATION: 713 Path += (LPWSTR)(p + 1); // use the filename retrieved from URL 714 break; 715 } 716 717 if ((InfoArray[iAppId].DLType == DLTYPE_APPLICATION) && InfoArray[iAppId].szSHA1[0] && 718 GetFileAttributesW(Path) != INVALID_FILE_ATTRIBUTES) 719 { 720 // only open it in case of total correctness 721 if (VerifyInteg(InfoArray[iAppId].szSHA1.GetString(), Path)) 722 goto run; 723 } 724 725 // Add the download URL 726 if (!IsWindow(hDlg)) 727 goto end; 728 SetDlgItemTextW(hDlg, IDC_DOWNLOAD_STATUS, InfoArray[iAppId].szUrl.GetString()); 729 730 DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_DOWNLOADING); 731 732 // download it 733 UrlHasBeenCopied = FALSE; 734 bTempfile = TRUE; 735 736 /* FIXME: this should just be using the system-wide proxy settings */ 737 switch (SettingsInfo.Proxy) 738 { 739 case 0: // preconfig 740 default: 741 hOpen = InternetOpenW(lpszAgent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); 742 break; 743 case 1: // direct (no proxy) 744 hOpen = InternetOpenW(lpszAgent, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); 745 break; 746 case 2: // use proxy 747 hOpen = InternetOpenW( 748 lpszAgent, INTERNET_OPEN_TYPE_PROXY, SettingsInfo.szProxyServer, SettingsInfo.szNoProxyFor, 0); 749 break; 750 } 751 752 if (!hOpen) 753 { 754 ShowLastError(hMainWnd, TRUE, GetLastError()); 755 goto end; 756 } 757 758 dwStatusLen = sizeof(dwStatus); 759 760 memset(&urlComponents, 0, sizeof(urlComponents)); 761 urlComponents.dwStructSize = sizeof(urlComponents); 762 763 urlLength = InfoArray[iAppId].szUrl.GetLength(); 764 urlComponents.dwSchemeLength = urlLength + 1; 765 urlComponents.lpszScheme = (LPWSTR)malloc(urlComponents.dwSchemeLength * sizeof(WCHAR)); 766 767 if (!InternetCrackUrlW(InfoArray[iAppId].szUrl, urlLength + 1, ICU_DECODE | ICU_ESCAPE, &urlComponents)) 768 { 769 ShowLastError(hMainWnd, TRUE, GetLastError()); 770 goto end; 771 } 772 773 dwContentLen = 0; 774 775 if (urlComponents.nScheme == INTERNET_SCHEME_HTTP || urlComponents.nScheme == INTERNET_SCHEME_HTTPS) 776 { 777 hFile = InternetOpenUrlW(hOpen, InfoArray[iAppId].szUrl.GetString(), NULL, 0, dwUrlConnectFlags, 0); 778 if (!hFile) 779 { 780 if (!ShowLastError(hMainWnd, TRUE, GetLastError())) 781 { 782 /* Workaround for CORE-17377 */ 783 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD2); 784 } 785 goto end; 786 } 787 788 // query connection 789 if (!HttpQueryInfoW(hFile, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &dwStatus, &dwStatusLen, NULL)) 790 { 791 ShowLastError(hMainWnd, TRUE, GetLastError()); 792 goto end; 793 } 794 795 if (dwStatus != HTTP_STATUS_OK) 796 { 797 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD); 798 goto end; 799 } 800 801 // query content length 802 HttpQueryInfoW( 803 hFile, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwContentLen, &dwStatusLen, NULL); 804 } 805 else if (urlComponents.nScheme == INTERNET_SCHEME_FTP) 806 { 807 // force passive mode on FTP 808 hFile = 809 InternetOpenUrlW(hOpen, InfoArray[iAppId].szUrl, NULL, 0, dwUrlConnectFlags | INTERNET_FLAG_PASSIVE, 0); 810 if (!hFile) 811 { 812 if (!ShowLastError(hMainWnd, TRUE, GetLastError())) 813 { 814 /* Workaround for CORE-17377 */ 815 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD2); 816 } 817 goto end; 818 } 819 820 dwContentLen = FtpGetFileSize(hFile, &dwStatus); 821 } 822 else if (urlComponents.nScheme == INTERNET_SCHEME_FILE) 823 { 824 // Add support for the file scheme so testing locally is simpler 825 WCHAR LocalFilePath[MAX_PATH]; 826 DWORD cchPath = _countof(LocalFilePath); 827 // Ideally we would use PathCreateFromUrlAlloc here, but that is not exported (yet) 828 HRESULT hr = PathCreateFromUrlW(InfoArray[iAppId].szUrl, LocalFilePath, &cchPath, 0); 829 if (SUCCEEDED(hr)) 830 { 831 if (CopyFileW(LocalFilePath, Path, FALSE)) 832 { 833 goto run; 834 } 835 else 836 { 837 ShowLastError(hMainWnd, FALSE, GetLastError()); 838 goto end; 839 } 840 } 841 else 842 { 843 ShowLastError(hMainWnd, FALSE, hr); 844 goto end; 845 } 846 } 847 848 if (!dwContentLen) 849 { 850 // Someone was nice enough to add this, let's use it 851 if (InfoArray[iAppId].SizeInBytes) 852 { 853 dwContentLen = InfoArray[iAppId].SizeInBytes; 854 } 855 else 856 { 857 // content-length is not known, enable marquee mode 858 ProgressBar.SetMarquee(TRUE); 859 } 860 } 861 862 free(urlComponents.lpszScheme); 863 864 #ifdef USE_CERT_PINNING 865 // are we using HTTPS to download the RAPPS update package? check if the certificate is original 866 if ((urlComponents.nScheme == INTERNET_SCHEME_HTTPS) && (InfoArray[iAppId].DLType == DLTYPE_DBUPDATE)) 867 { 868 CLocalPtr<char> subjectName, issuerName; 869 CStringA szMsgText; 870 bool bAskQuestion = false; 871 if (!CertGetSubjectAndIssuer(hFile, subjectName, issuerName)) 872 { 873 szMsgText.LoadStringW(IDS_UNABLE_TO_QUERY_CERT); 874 bAskQuestion = true; 875 } 876 else 877 { 878 if (strcmp(subjectName, CERT_SUBJECT_INFO) || 879 (strcmp(issuerName, CERT_ISSUER_INFO_OLD) && strcmp(issuerName, CERT_ISSUER_INFO_NEW))) 880 { 881 szMsgText.Format(IDS_MISMATCH_CERT_INFO, (char *)subjectName, (const char *)issuerName); 882 bAskQuestion = true; 883 } 884 } 885 886 if (bAskQuestion) 887 { 888 if (MessageBoxA(hMainWnd, szMsgText.GetString(), NULL, MB_YESNO | MB_ICONERROR) != IDYES) 889 { 890 goto end; 891 } 892 } 893 } 894 #endif 895 896 hOut = CreateFileW(Path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); 897 898 if (hOut == INVALID_HANDLE_VALUE) 899 { 900 ShowLastError(hMainWnd, FALSE, GetLastError()); 901 goto end; 902 } 903 904 dwCurrentBytesRead = 0; 905 do 906 { 907 if (!InternetReadFile(hFile, lpBuffer, _countof(lpBuffer), &dwBytesRead)) 908 { 909 ShowLastError(hMainWnd, TRUE, GetLastError()); 910 goto end; 911 } 912 913 if (!WriteFile(hOut, &lpBuffer[0], dwBytesRead, &dwBytesWritten, NULL)) 914 { 915 ShowLastError(hMainWnd, FALSE, GetLastError()); 916 goto end; 917 } 918 919 dwCurrentBytesRead += dwBytesRead; 920 if (!IsWindow(hDlg)) 921 goto end; 922 UpdateProgress(hDlg, dwCurrentBytesRead, dwContentLen, 0, InfoArray[iAppId].szUrl.GetString()); 923 } while (dwBytesRead && !bCancelled); 924 925 CloseHandle(hOut); 926 hOut = INVALID_HANDLE_VALUE; 927 928 if (bCancelled) 929 { 930 DPRINT1("Operation cancelled\n"); 931 goto end; 932 } 933 934 if (!dwContentLen) 935 { 936 // set progress bar to 100% 937 ProgressBar.SetMarquee(FALSE); 938 939 dwContentLen = dwCurrentBytesRead; 940 if (!IsWindow(hDlg)) 941 goto end; 942 UpdateProgress(hDlg, dwCurrentBytesRead, dwContentLen, 0, InfoArray[iAppId].szUrl.GetString()); 943 } 944 945 /* if this thing isn't a RAPPS update and it has a SHA-1 checksum 946 verify its integrity by using the native advapi32.A_SHA1 functions */ 947 if ((InfoArray[iAppId].DLType == DLTYPE_APPLICATION) && InfoArray[iAppId].szSHA1[0] != 0) 948 { 949 CStringW szMsgText; 950 951 // change a few strings in the download dialog to reflect the verification process 952 if (!szMsgText.LoadStringW(IDS_INTEG_CHECK_TITLE)) 953 { 954 DPRINT1("Unable to load string\n"); 955 goto end; 956 } 957 958 if (!IsWindow(hDlg)) 959 goto end; 960 SetWindowTextW(hDlg, szMsgText.GetString()); 961 ::SetDlgItemTextW(hDlg, IDC_DOWNLOAD_STATUS, Path); 962 963 // this may take a while, depending on the file size 964 if (!VerifyInteg(InfoArray[iAppId].szSHA1.GetString(), Path)) 965 { 966 if (!szMsgText.LoadStringW(IDS_INTEG_CHECK_FAIL)) 967 { 968 DPRINT1("Unable to load string\n"); 969 goto end; 970 } 971 972 if (!IsWindow(hDlg)) 973 goto end; 974 MessageBoxW(hDlg, szMsgText.GetString(), NULL, MB_OK | MB_ICONERROR); 975 goto end; 976 } 977 } 978 979 run: 980 DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_WAITING_INSTALL); 981 982 // run it 983 if (InfoArray[iAppId].DLType == DLTYPE_APPLICATION) 984 { 985 SHELLEXECUTEINFOW shExInfo = {0}; 986 shExInfo.cbSize = sizeof(shExInfo); 987 shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS; 988 shExInfo.lpVerb = L"open"; 989 shExInfo.lpFile = Path; 990 shExInfo.lpParameters = L""; 991 shExInfo.nShow = SW_SHOW; 992 993 /* FIXME: Do we want to log installer status? */ 994 WriteLogMessage(EVENTLOG_SUCCESS, MSG_SUCCESS_INSTALL, InfoArray[iAppId].szName); 995 996 if (ShellExecuteExW(&shExInfo)) 997 { 998 // reflect installation progress in the titlebar 999 // TODO: make a separate string with a placeholder to include app name? 1000 CStringW szMsgText = LoadStatusString(DLSTATUS_INSTALLING); 1001 if (!IsWindow(hDlg)) 1002 goto end; 1003 SetWindowTextW(hDlg, szMsgText.GetString()); 1004 1005 DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_INSTALLING); 1006 1007 // TODO: issue an install operation separately so that the apps could be downloaded in the background 1008 WaitForSingleObject(shExInfo.hProcess, INFINITE); 1009 CloseHandle(shExInfo.hProcess); 1010 } 1011 else 1012 { 1013 ShowLastError(hMainWnd, FALSE, GetLastError()); 1014 } 1015 } 1016 1017 end: 1018 if (hOut != INVALID_HANDLE_VALUE) 1019 CloseHandle(hOut); 1020 1021 if (hFile) 1022 InternetCloseHandle(hFile); 1023 InternetCloseHandle(hOpen); 1024 1025 if (bTempfile) 1026 { 1027 if (bCancelled || (SettingsInfo.bDelInstaller && (InfoArray[iAppId].DLType == DLTYPE_APPLICATION))) 1028 DeleteFileW(Path); 1029 } 1030 1031 if (!IsWindow(hDlg)) 1032 return 0; 1033 DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_FINISHED); 1034 } 1035 1036 delete static_cast<DownloadParam *>(param); 1037 if (!IsWindow(hDlg)) 1038 return 0; 1039 SendMessageW(hDlg, WM_CLOSE, 0, 0); 1040 return 0; 1041 } 1042 1043 // TODO: Reuse the dialog 1044 VOID 1045 CDownloadManager::LaunchDownloadDialog(BOOL bIsModal) 1046 { 1047 CDownloadManager::bModal = bIsModal; 1048 if (bIsModal) 1049 { 1050 DialogBoxW(hInst, MAKEINTRESOURCEW(IDD_DOWNLOAD_DIALOG), hMainWnd, DownloadDlgProc); 1051 } 1052 else 1053 { 1054 CreateDialogW(hInst, MAKEINTRESOURCEW(IDD_DOWNLOAD_DIALOG), hMainWnd, DownloadDlgProc); 1055 } 1056 } 1057 // CDownloadManager 1058 1059 BOOL 1060 DownloadListOfApplications(const CAtlList<CAppInfo *> &AppsList, BOOL bIsModal) 1061 { 1062 if (AppsList.IsEmpty()) 1063 return FALSE; 1064 1065 POSITION CurrentListPosition = AppsList.GetHeadPosition(); 1066 while (CurrentListPosition) 1067 { 1068 const CAppInfo *Info = AppsList.GetNext(CurrentListPosition); 1069 CDownloadManager::Add(DownloadInfo(*Info)); 1070 } 1071 1072 // Create a dialog and issue a download process 1073 CDownloadManager::LaunchDownloadDialog(bIsModal); 1074 1075 return TRUE; 1076 } 1077 1078 BOOL 1079 DownloadApplication(CAppInfo *pAppInfo) 1080 { 1081 if (!pAppInfo) 1082 return FALSE; 1083 1084 CDownloadManager::Download(*pAppInfo, FALSE); 1085 return TRUE; 1086 } 1087 1088 VOID 1089 DownloadApplicationsDB(LPCWSTR lpUrl, BOOL IsOfficial) 1090 { 1091 static DownloadInfo DatabaseDLInfo; 1092 DatabaseDLInfo.szUrl = lpUrl; 1093 DatabaseDLInfo.szName.LoadStringW(IDS_DL_DIALOG_DB_DISP); 1094 DatabaseDLInfo.DLType = IsOfficial ? DLTYPE_DBUPDATE : DLTYPE_DBUPDATE_UNOFFICIAL; 1095 CDownloadManager::Download(DatabaseDLInfo, TRUE); 1096 } 1097