xref: /reactos/base/applications/rapps/loaddlg.cpp (revision 22577aed)
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