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