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