xref: /reactos/base/applications/rapps/loaddlg.cpp (revision fc3ccb39)
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 static BOOL CertGetSubjectAndIssuer(HINTERNET hFile, CLocalPtr<char>& subjectInfo, CLocalPtr<char>& issuerInfo)
309 {
310     DWORD certInfoLength;
311     INTERNET_CERTIFICATE_INFOA certInfo;
312     DWORD size, flags;
313 
314     size = sizeof(flags);
315     if (!InternetQueryOptionA(hFile, INTERNET_OPTION_SECURITY_FLAGS, &flags, &size))
316     {
317         return FALSE;
318     }
319 
320     if (!flags & SECURITY_FLAG_SECURE)
321     {
322         return FALSE;
323     }
324 
325     /* Despite what the header indicates, the implementation of INTERNET_CERTIFICATE_INFO is not Unicode-aware. */
326     certInfoLength = sizeof(certInfo);
327     if (!InternetQueryOptionA(hFile,
328                               INTERNET_OPTION_SECURITY_CERTIFICATE_STRUCT,
329                               &certInfo,
330                               &certInfoLength))
331     {
332         return FALSE;
333     }
334 
335     subjectInfo.Attach(certInfo.lpszSubjectInfo);
336     issuerInfo.Attach(certInfo.lpszIssuerInfo);
337 
338     if (certInfo.lpszProtocolName)
339         LocalFree(certInfo.lpszProtocolName);
340     if (certInfo.lpszSignatureAlgName)
341         LocalFree(certInfo.lpszSignatureAlgName);
342     if (certInfo.lpszEncryptionAlgName)
343         LocalFree(certInfo.lpszEncryptionAlgName);
344 
345     return certInfo.lpszSubjectInfo && certInfo.lpszIssuerInfo;
346 }
347 #endif
348 
349 inline VOID MessageBox_LoadString(HWND hMainWnd, INT StringID)
350 {
351     ATL::CStringW szMsgText;
352     if (szMsgText.LoadStringW(StringID))
353     {
354         MessageBoxW(hMainWnd, szMsgText.GetString(), NULL, MB_OK | MB_ICONERROR);
355     }
356 }
357 
358 // Download dialog (loaddlg.cpp)
359 class CDownloadManager
360 {
361     static ATL::CSimpleArray<DownloadInfo> AppsDownloadList;
362     static CDowloadingAppsListView DownloadsListView;
363     static CDownloaderProgress ProgressBar;
364     static BOOL bCancelled;
365     static BOOL bModal;
366     static VOID UpdateProgress(HWND hDlg, ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText);
367 public:
368     static VOID Add(DownloadInfo info);
369     static VOID Download(const DownloadInfo& DLInfo, BOOL bIsModal = FALSE);
370     static INT_PTR CALLBACK DownloadDlgProc(HWND Dlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
371     static unsigned int WINAPI ThreadFunc(LPVOID Context);
372     static VOID LaunchDownloadDialog(BOOL);
373 };
374 
375 
376 // CDownloadManager
377 ATL::CSimpleArray<DownloadInfo>         CDownloadManager::AppsDownloadList;
378 CDowloadingAppsListView                 CDownloadManager::DownloadsListView;
379 CDownloaderProgress                     CDownloadManager::ProgressBar;
380 BOOL                                    CDownloadManager::bCancelled = FALSE;
381 BOOL                                    CDownloadManager::bModal = FALSE;
382 
383 VOID CDownloadManager::Add(DownloadInfo info)
384 {
385     AppsDownloadList.Add(info);
386 }
387 
388 VOID CDownloadManager::Download(const DownloadInfo &DLInfo, BOOL bIsModal)
389 {
390     AppsDownloadList.RemoveAll();
391     AppsDownloadList.Add(DLInfo);
392     LaunchDownloadDialog(bIsModal);
393 }
394 
395 INT_PTR CALLBACK CDownloadManager::DownloadDlgProc(HWND Dlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
396 {
397     static WCHAR szCaption[MAX_PATH];
398 
399     switch (uMsg)
400     {
401     case WM_INITDIALOG:
402     {
403         HICON hIconSm, hIconBg;
404         ATL::CStringW szTempCaption;
405 
406         bCancelled = FALSE;
407 
408         hIconBg = (HICON) GetClassLongPtrW(hMainWnd, GCLP_HICON);
409         hIconSm = (HICON) GetClassLongPtrW(hMainWnd, GCLP_HICONSM);
410 
411         if (hIconBg && hIconSm)
412         {
413             SendMessageW(Dlg, WM_SETICON, ICON_BIG, (LPARAM) hIconBg);
414             SendMessageW(Dlg, WM_SETICON, ICON_SMALL, (LPARAM) hIconSm);
415         }
416 
417         SetWindowLongPtrW(Dlg, GWLP_USERDATA, 0);
418         HWND Item = GetDlgItem(Dlg, IDC_DOWNLOAD_PROGRESS);
419         if (Item)
420         {
421             // initialize the default values for our nifty progress bar
422             // and subclass it so that it learns to print a status text
423             ProgressBar.SubclassWindow(Item);
424             ProgressBar.SendMessage(PBM_SETRANGE, 0, MAKELPARAM(0, 100));
425             ProgressBar.SendMessage(PBM_SETPOS, 0, 0);
426         }
427 
428         // Add a ListView
429         HWND hListView = DownloadsListView.Create(Dlg);
430         if (!hListView)
431         {
432             return FALSE;
433         }
434         DownloadsListView.LoadList(AppsDownloadList);
435 
436         // Get a dlg string for later use
437         GetWindowTextW(Dlg, szCaption, _countof(szCaption));
438 
439         // Hide a placeholder from displaying
440         szTempCaption = szCaption;
441         szTempCaption.Replace(L"%ls", L"");
442         SetWindowText(Dlg, szTempCaption.GetString());
443 
444         ShowWindow(Dlg, SW_SHOW);
445 
446         // Start download process
447         DownloadParam *param = new DownloadParam(Dlg, AppsDownloadList, szCaption);
448         unsigned int ThreadId;
449         HANDLE Thread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void *) param, 0, &ThreadId);
450 
451         if (!Thread)
452         {
453             return FALSE;
454         }
455 
456         CloseHandle(Thread);
457         AppsDownloadList.RemoveAll();
458         return TRUE;
459     }
460 
461     case WM_COMMAND:
462         if (wParam == IDCANCEL)
463         {
464             bCancelled = TRUE;
465             PostMessageW(Dlg, WM_CLOSE, 0, 0);
466         }
467         return FALSE;
468 
469     case WM_CLOSE:
470         if (CDownloadManager::bModal)
471         {
472             ::EndDialog(Dlg, 0);
473         }
474         else
475         {
476             ::DestroyWindow(Dlg);
477         }
478         return TRUE;
479 
480     default:
481         return FALSE;
482     }
483 }
484 
485 BOOL UrlHasBeenCopied;
486 
487 VOID CDownloadManager::UpdateProgress(
488     HWND hDlg,
489     ULONG ulProgress,
490     ULONG ulProgressMax,
491     ULONG ulStatusCode,
492     LPCWSTR szStatusText)
493 {
494     HWND Item;
495 
496     if (!IsWindow(hDlg)) return;
497     ProgressBar.SetProgress(ulProgress, ulProgressMax);
498 
499     if (!IsWindow(hDlg)) return;
500     Item = GetDlgItem(hDlg, IDC_DOWNLOAD_STATUS);
501     if (Item && szStatusText && wcslen(szStatusText) > 0 && UrlHasBeenCopied == FALSE)
502     {
503         SIZE_T len = wcslen(szStatusText) + 1;
504         ATL::CStringW buf;
505         DWORD dummyLen;
506 
507         /* beautify our url for display purposes */
508         if (!InternetCanonicalizeUrlW(szStatusText, buf.GetBuffer(len), &dummyLen, ICU_DECODE | ICU_NO_ENCODE))
509         {
510             /* just use the original */
511             buf.ReleaseBuffer();
512             buf = szStatusText;
513         }
514         else
515         {
516             buf.ReleaseBuffer();
517         }
518 
519         /* paste it into our dialog and don't do it again in this instance */
520         SendMessageW(Item, WM_SETTEXT, 0, (LPARAM) buf.GetString());
521         UrlHasBeenCopied = TRUE;
522     }
523 }
524 
525 BOOL ShowLastError(
526     HWND hWndOwner,
527     BOOL bInetError,
528     DWORD dwLastError)
529 {
530     CLocalPtr<WCHAR> lpMsg;
531 
532     if (!FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
533                         FORMAT_MESSAGE_IGNORE_INSERTS |
534                         (bInetError ? FORMAT_MESSAGE_FROM_HMODULE : FORMAT_MESSAGE_FROM_SYSTEM),
535                         (bInetError ? GetModuleHandleW(L"wininet.dll") : NULL),
536                         dwLastError,
537                         LANG_USER_DEFAULT,
538                         (LPWSTR)&lpMsg,
539                         0, NULL))
540     {
541         DPRINT1("FormatMessageW unexpected failure (err %d)\n", GetLastError());
542         return FALSE;
543     }
544 
545     MessageBoxW(hWndOwner, lpMsg, NULL, MB_OK | MB_ICONERROR);
546     return TRUE;
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.1";
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, FALSE, 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, FALSE, 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(), APPLICATION_DATABASE_NAME);
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, TRUE, 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, TRUE, GetLastError());
722             goto end;
723         }
724 
725         dwContentLen = 0;
726 
727         if (urlComponents.nScheme == INTERNET_SCHEME_HTTP ||
728             urlComponents.nScheme == INTERNET_SCHEME_HTTPS)
729         {
730             hFile = InternetOpenUrlW(hOpen, InfoArray[iAppId].szUrl.GetString(), NULL, 0,
731                                      dwUrlConnectFlags,
732                                      0);
733             if (!hFile)
734             {
735                 if (!ShowLastError(hMainWnd, TRUE, GetLastError()))
736                 {
737                     /* Workaround for CORE-17377 */
738                     MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD2);
739                 }
740                 goto end;
741             }
742 
743             // query connection
744             if (!HttpQueryInfoW(hFile, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &dwStatus, &dwStatusLen, NULL))
745             {
746                 ShowLastError(hMainWnd, TRUE, GetLastError());
747                 goto end;
748             }
749 
750             if (dwStatus != HTTP_STATUS_OK)
751             {
752                 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD);
753                 goto end;
754             }
755 
756             // query content length
757             HttpQueryInfoW(hFile, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwContentLen, &dwStatusLen, NULL);
758         }
759         else if (urlComponents.nScheme == INTERNET_SCHEME_FTP)
760         {
761             // force passive mode on FTP
762             hFile = InternetOpenUrlW(hOpen, InfoArray[iAppId].szUrl, NULL, 0,
763                                      dwUrlConnectFlags | INTERNET_FLAG_PASSIVE,
764                                      0);
765             if (!hFile)
766             {
767                 if (!ShowLastError(hMainWnd, TRUE, GetLastError()))
768                 {
769                     /* Workaround for CORE-17377 */
770                     MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD2);
771                 }
772                 goto end;
773             }
774 
775             dwContentLen = FtpGetFileSize(hFile, &dwStatus);
776         }
777         else if (urlComponents.nScheme == INTERNET_SCHEME_FILE)
778         {
779             // Add support for the file scheme so testing locally is simpler
780             WCHAR LocalFilePath[MAX_PATH];
781             DWORD cchPath = _countof(LocalFilePath);
782             // Ideally we would use PathCreateFromUrlAlloc here, but that is not exported (yet)
783             HRESULT hr = PathCreateFromUrlW(InfoArray[iAppId].szUrl, LocalFilePath, &cchPath, 0);
784             if (SUCCEEDED(hr))
785             {
786                 if (CopyFileW(LocalFilePath, Path, FALSE))
787                 {
788                     goto run;
789                 }
790                 else
791                 {
792                     ShowLastError(hMainWnd, FALSE, GetLastError());
793                     goto end;
794                 }
795             }
796             else
797             {
798                 ShowLastError(hMainWnd, FALSE, hr);
799                 goto end;
800             }
801         }
802 
803         if (!dwContentLen)
804         {
805             // Someone was nice enough to add this, let's use it
806             if (InfoArray[iAppId].SizeInBytes)
807             {
808                 dwContentLen = InfoArray[iAppId].SizeInBytes;
809             }
810             else
811             {
812                 // content-length is not known, enable marquee mode
813                 ProgressBar.SetMarquee(TRUE);
814             }
815         }
816 
817         free(urlComponents.lpszScheme);
818 
819 #ifdef USE_CERT_PINNING
820         // are we using HTTPS to download the RAPPS update package? check if the certificate is original
821         if ((urlComponents.nScheme == INTERNET_SCHEME_HTTPS) &&
822             (InfoArray[iAppId].DLType == DLTYPE_DBUPDATE))
823         {
824             CLocalPtr<char> subjectName, issuerName;
825             CStringA szMsgText;
826             bool bAskQuestion = false;
827             if (!CertGetSubjectAndIssuer(hFile, subjectName, issuerName))
828             {
829                 szMsgText.LoadStringW(IDS_UNABLE_TO_QUERY_CERT);
830                 bAskQuestion = true;
831             }
832             else
833             {
834                 if (strcmp(subjectName, CERT_SUBJECT_INFO) ||
835                     strcmp(issuerName, CERT_ISSUER_INFO))
836                 {
837                     szMsgText.Format(IDS_MISMATCH_CERT_INFO, (char*)subjectName, (const char*)issuerName);
838                     bAskQuestion = true;
839                 }
840             }
841 
842             if (bAskQuestion)
843             {
844                 if (MessageBoxA(hMainWnd, szMsgText.GetString(), NULL, MB_YESNO | MB_ICONERROR) != IDYES)
845                 {
846                     goto end;
847                 }
848             }
849         }
850 #endif
851 
852         hOut = CreateFileW(Path.GetString(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
853 
854         if (hOut == INVALID_HANDLE_VALUE)
855         {
856             ShowLastError(hMainWnd, FALSE, GetLastError());
857             goto end;
858         }
859 
860         dwCurrentBytesRead = 0;
861         do
862         {
863             if (!InternetReadFile(hFile, lpBuffer, _countof(lpBuffer), &dwBytesRead))
864             {
865                 ShowLastError(hMainWnd, TRUE, GetLastError());
866                 goto end;
867             }
868 
869             if (!WriteFile(hOut, &lpBuffer[0], dwBytesRead, &dwBytesWritten, NULL))
870             {
871                 ShowLastError(hMainWnd, FALSE, GetLastError());
872                 goto end;
873             }
874 
875             dwCurrentBytesRead += dwBytesRead;
876             if (!IsWindow(hDlg)) goto end;
877             UpdateProgress(hDlg, dwCurrentBytesRead, dwContentLen, 0, InfoArray[iAppId].szUrl.GetString());
878         } while (dwBytesRead && !bCancelled);
879 
880         CloseHandle(hOut);
881         hOut = INVALID_HANDLE_VALUE;
882 
883         if (bCancelled)
884         {
885             DPRINT1("Operation cancelled\n");
886             goto end;
887         }
888 
889         if (!dwContentLen)
890         {
891             // set progress bar to 100%
892             ProgressBar.SetMarquee(FALSE);
893 
894             dwContentLen = dwCurrentBytesRead;
895             if (!IsWindow(hDlg)) goto end;
896             UpdateProgress(hDlg, dwCurrentBytesRead, dwContentLen, 0, InfoArray[iAppId].szUrl.GetString());
897         }
898 
899         /* if this thing isn't a RAPPS update and it has a SHA-1 checksum
900         verify its integrity by using the native advapi32.A_SHA1 functions */
901         if ((InfoArray[iAppId].DLType == DLTYPE_APPLICATION) && InfoArray[iAppId].szSHA1[0] != 0)
902         {
903             ATL::CStringW szMsgText;
904 
905             // change a few strings in the download dialog to reflect the verification process
906             if (!szMsgText.LoadStringW(IDS_INTEG_CHECK_TITLE))
907             {
908                 DPRINT1("Unable to load string\n");
909                 goto end;
910             }
911 
912             if (!IsWindow(hDlg)) goto end;
913             SetWindowTextW(hDlg, szMsgText.GetString());
914             SendMessageW(GetDlgItem(hDlg, IDC_DOWNLOAD_STATUS), WM_SETTEXT, 0, (LPARAM) Path.GetString());
915 
916             // this may take a while, depending on the file size
917             if (!VerifyInteg(InfoArray[iAppId].szSHA1.GetString(), Path.GetString()))
918             {
919                 if (!szMsgText.LoadStringW(IDS_INTEG_CHECK_FAIL))
920                 {
921                     DPRINT1("Unable to load string\n");
922                     goto end;
923                 }
924 
925                 if (!IsWindow(hDlg)) goto end;
926                 MessageBoxW(hDlg, szMsgText.GetString(), NULL, MB_OK | MB_ICONERROR);
927                 goto end;
928             }
929         }
930 
931 run:
932         DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_WAITING_INSTALL);
933 
934         // run it
935         if (InfoArray[iAppId].DLType == DLTYPE_APPLICATION)
936         {
937             SHELLEXECUTEINFOW shExInfo = {0};
938             shExInfo.cbSize = sizeof(shExInfo);
939             shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
940             shExInfo.lpVerb = L"open";
941             shExInfo.lpFile = Path.GetString();
942             shExInfo.lpParameters = L"";
943             shExInfo.nShow = SW_SHOW;
944 
945             if (ShellExecuteExW(&shExInfo))
946             {
947                 //reflect installation progress in the titlebar
948                 //TODO: make a separate string with a placeholder to include app name?
949                 ATL::CStringW szMsgText = LoadStatusString(DLSTATUS_INSTALLING);
950                 if (!IsWindow(hDlg)) goto end;
951                 SetWindowTextW(hDlg, szMsgText.GetString());
952 
953                 DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_INSTALLING);
954 
955                 //TODO: issue an install operation separately so that the apps could be downloaded in the background
956                 WaitForSingleObject(shExInfo.hProcess, INFINITE);
957                 CloseHandle(shExInfo.hProcess);
958             }
959             else
960             {
961                 ShowLastError(hMainWnd, FALSE, GetLastError());
962             }
963         }
964 
965 end:
966         if (hOut != INVALID_HANDLE_VALUE)
967             CloseHandle(hOut);
968 
969         if (hFile)
970             InternetCloseHandle(hFile);
971         InternetCloseHandle(hOpen);
972 
973         if (bTempfile)
974         {
975             if (bCancelled || (SettingsInfo.bDelInstaller && (InfoArray[iAppId].DLType == DLTYPE_APPLICATION)))
976                 DeleteFileW(Path.GetString());
977         }
978 
979         if (!IsWindow(hDlg)) return 0;
980         DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_FINISHED);
981     }
982 
983     delete static_cast<DownloadParam*>(param);
984     if (!IsWindow(hDlg)) return 0;
985     SendMessageW(hDlg, WM_CLOSE, 0, 0);
986     return 0;
987 }
988 
989 //TODO: Reuse the dialog
990 VOID CDownloadManager::LaunchDownloadDialog(BOOL bIsModal)
991 {
992     CDownloadManager::bModal = bIsModal;
993     if (bIsModal)
994     {
995         DialogBoxW(hInst,
996                    MAKEINTRESOURCEW(IDD_DOWNLOAD_DIALOG),
997                    hMainWnd,
998                    DownloadDlgProc);
999     }
1000     else
1001     {
1002         CreateDialogW(hInst,
1003                       MAKEINTRESOURCEW(IDD_DOWNLOAD_DIALOG),
1004                       hMainWnd,
1005                       DownloadDlgProc);
1006     }
1007 }
1008 // CDownloadManager
1009 
1010 
1011 BOOL DownloadListOfApplications(const ATL::CSimpleArray<CAvailableApplicationInfo>& AppsList, BOOL bIsModal)
1012 {
1013     if (AppsList.GetSize() == 0)
1014         return FALSE;
1015 
1016     // Initialize shared variables
1017     for (INT i = 0; i < AppsList.GetSize(); ++i)
1018     {
1019         CDownloadManager::Add(AppsList[i]); // implicit conversion to DownloadInfo
1020     }
1021 
1022     // Create a dialog and issue a download process
1023     CDownloadManager::LaunchDownloadDialog(bIsModal);
1024 
1025     return TRUE;
1026 }
1027 
1028 BOOL DownloadApplication(CAvailableApplicationInfo* pAppInfo, BOOL bIsModal)
1029 {
1030     if (!pAppInfo)
1031         return FALSE;
1032 
1033     CDownloadManager::Download(*pAppInfo, bIsModal);
1034     return TRUE;
1035 }
1036 
1037 VOID DownloadApplicationsDB(LPCWSTR lpUrl, BOOL IsOfficial)
1038 {
1039     static DownloadInfo DatabaseDLInfo;
1040     DatabaseDLInfo.szUrl = lpUrl;
1041     DatabaseDLInfo.szName.LoadStringW(IDS_DL_DIALOG_DB_DISP);
1042     DatabaseDLInfo.DLType = IsOfficial ? DLTYPE_DBUPDATE : DLTYPE_DBUPDATE_UNOFFICIAL;
1043     CDownloadManager::Download(DatabaseDLInfo, TRUE);
1044 }
1045 
1046