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