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