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