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