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