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