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