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