xref: /reactos/base/applications/rapps/loaddlg.cpp (revision f4d29a74)
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 "US\r\nLet's Encrypt\r\nLet's Encrypt Authority X3"
51 #define CERT_SUBJECT_INFO "rapps.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             SIZE_T len = wcslen(szStatusText) + 1;
194             ATL::CStringW buf;
195             DWORD dummyLen;
196 
197             /* beautify our url for display purposes */
198             if (!InternetCanonicalizeUrlW(szStatusText, buf.GetBuffer(len), &dummyLen, ICU_DECODE | ICU_NO_ENCODE))
199             {
200                 /* just use the original */
201                 buf.ReleaseBuffer();
202                 buf = szStatusText;
203             }
204             else
205             {
206                 buf.ReleaseBuffer();
207             }
208 
209             /* paste it into our dialog and don't do it again in this instance */
210             SendMessageW(Item, WM_SETTEXT, 0, (LPARAM) buf.GetString());
211             m_UrlHasBeenCopied = TRUE;
212         }
213 
214         SetLastError(ERROR_SUCCESS);
215         r = GetWindowLongPtrW(m_hDialog, GWLP_USERDATA);
216         if (r || GetLastError() != ERROR_SUCCESS)
217         {
218             *m_pbCancelled = TRUE;
219             return E_ABORT;
220         }
221 
222         return S_OK;
223     }
224 
225     virtual HRESULT STDMETHODCALLTYPE OnStopBinding(
226         HRESULT hresult,
227         LPCWSTR szError)
228     {
229         return S_OK;
230     }
231 
232     virtual HRESULT STDMETHODCALLTYPE GetBindInfo(
233         DWORD *grfBINDF,
234         BINDINFO *pbindinfo)
235     {
236         return S_OK;
237     }
238 
239     virtual HRESULT STDMETHODCALLTYPE OnDataAvailable(
240         DWORD grfBSCF,
241         DWORD dwSize,
242         FORMATETC *pformatetc,
243         STGMEDIUM *pstgmed)
244     {
245         return S_OK;
246     }
247 
248     virtual HRESULT STDMETHODCALLTYPE OnObjectAvailable(
249         REFIID riid,
250         IUnknown *punk)
251     {
252         return S_OK;
253     }
254 
255     BEGIN_COM_MAP(CDownloadDialog)
256         COM_INTERFACE_ENTRY_IID(IID_IBindStatusCallback, IBindStatusCallback)
257     END_COM_MAP()
258 };
259 
260 class CDowloadingAppsListView
261     : public CUiWindow<CListView>
262 {
263 public:
264     HWND Create(HWND hwndParent)
265     {
266         RECT r = {10, 150, 320, 350};
267         const DWORD style = WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SINGLESEL
268             | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | LVS_NOCOLUMNHEADER;
269 
270         HWND hwnd = CListView::Create(hwndParent, r, NULL, style, WS_EX_CLIENTEDGE);
271 
272         AddColumn(0, 150, LVCFMT_LEFT);
273         AddColumn(1, 120, LVCFMT_LEFT);
274 
275         return hwnd;
276     }
277 
278     VOID LoadList(ATL::CSimpleArray<DownloadInfo> arrInfo)
279     {
280         for (INT i = 0; i < arrInfo.GetSize(); ++i)
281         {
282             AddRow(i, arrInfo[i].szName.GetString(), DLSTATUS_WAITING);
283         }
284     }
285 
286     VOID SetDownloadStatus(INT ItemIndex, DownloadStatus Status)
287     {
288         HWND hListView = GetWindow();
289         ATL::CStringW szBuffer = LoadStatusString(Status);
290         ListView_SetItemText(hListView, ItemIndex, 1, const_cast<LPWSTR>(szBuffer.GetString()));
291     }
292 
293     BOOL AddItem(INT ItemIndex, LPWSTR lpText)
294     {
295         LVITEMW Item;
296 
297         ZeroMemory(&Item, sizeof(Item));
298 
299         Item.mask = LVIF_TEXT | LVIF_STATE;
300         Item.pszText = lpText;
301         Item.iItem = ItemIndex;
302 
303         return InsertItem(&Item);
304     }
305 
306     VOID AddRow(INT RowIndex, LPCWSTR szAppName, const DownloadStatus Status)
307     {
308         ATL::CStringW szStatus = LoadStatusString(Status);
309         AddItem(RowIndex,
310                 const_cast<LPWSTR>(szAppName));
311         SetDownloadStatus(RowIndex, Status);
312     }
313 
314     BOOL AddColumn(INT Index, INT Width, INT Format)
315     {
316         LVCOLUMNW Column;
317         ZeroMemory(&Column, sizeof(Column));
318 
319         Column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM;
320         Column.iSubItem = Index;
321         Column.cx = Width;
322         Column.fmt = Format;
323 
324         return (InsertColumn(Index, &Column) == -1) ? FALSE : TRUE;
325     }
326 };
327 
328 extern "C"
329 HRESULT WINAPI CDownloadDialog_Constructor(HWND Dlg, BOOL *pbCancelled, REFIID riid, LPVOID *ppv)
330 {
331     return ShellObjectCreatorInit<CDownloadDialog>(Dlg, pbCancelled, riid, ppv);
332 }
333 
334 #ifdef USE_CERT_PINNING
335 typedef CHeapPtr<char, CLocalAllocator> CLocalPtr;
336 
337 static BOOL CertGetSubjectAndIssuer(HINTERNET hFile, CLocalPtr& subjectInfo, CLocalPtr& issuerInfo)
338 {
339     DWORD certInfoLength;
340     INTERNET_CERTIFICATE_INFOA certInfo;
341     DWORD size, flags;
342 
343     size = sizeof(flags);
344     if (!InternetQueryOptionA(hFile, INTERNET_OPTION_SECURITY_FLAGS, &flags, &size))
345     {
346         return FALSE;
347     }
348 
349     if (!flags & SECURITY_FLAG_SECURE)
350     {
351         return FALSE;
352     }
353 
354     /* Despite what the header indicates, the implementation of INTERNET_CERTIFICATE_INFO is not Unicode-aware. */
355     certInfoLength = sizeof(certInfo);
356     if (!InternetQueryOptionA(hFile,
357                               INTERNET_OPTION_SECURITY_CERTIFICATE_STRUCT,
358                               &certInfo,
359                               &certInfoLength))
360     {
361         return FALSE;
362     }
363 
364     subjectInfo.Attach(certInfo.lpszSubjectInfo);
365     issuerInfo.Attach(certInfo.lpszIssuerInfo);
366 
367     if (certInfo.lpszProtocolName)
368         LocalFree(certInfo.lpszProtocolName);
369     if (certInfo.lpszSignatureAlgName)
370         LocalFree(certInfo.lpszSignatureAlgName);
371     if (certInfo.lpszEncryptionAlgName)
372         LocalFree(certInfo.lpszEncryptionAlgName);
373 
374     return certInfo.lpszSubjectInfo && certInfo.lpszIssuerInfo;
375 }
376 #endif
377 
378 inline VOID MessageBox_LoadString(HWND hMainWnd, INT StringID)
379 {
380     ATL::CStringW szMsgText;
381     if (szMsgText.LoadStringW(StringID))
382     {
383         MessageBoxW(hMainWnd, szMsgText.GetString(), NULL, MB_OK | MB_ICONERROR);
384     }
385 }
386 
387 // CDownloadManager
388 ATL::CSimpleArray<DownloadInfo>         CDownloadManager::AppsToInstallList;
389 CDowloadingAppsListView                 CDownloadManager::DownloadsListView;
390 
391 VOID CDownloadManager::Download(const DownloadInfo &DLInfo, BOOL bIsModal)
392 {
393     AppsToInstallList.RemoveAll();
394     AppsToInstallList.Add(DLInfo);
395     LaunchDownloadDialog(bIsModal);
396 }
397 
398 INT_PTR CALLBACK CDownloadManager::DownloadDlgProc(HWND Dlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
399 {
400     static WCHAR szCaption[MAX_PATH];
401 
402     switch (uMsg)
403     {
404     case WM_INITDIALOG:
405     {
406         HICON hIconSm, hIconBg;
407         ATL::CStringW szTempCaption;
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         SetWindowLongW(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             SendMessageW(Item, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
425             SendMessageW(Item, PBM_SETPOS, 0, 0);
426 
427             SetWindowSubclass(Item, DownloadProgressProc, 0, 0);
428         }
429 
430         // Add a ListView
431         HWND hListView = DownloadsListView.Create(Dlg);
432         if (!hListView)
433         {
434             return FALSE;
435         }
436         DownloadsListView.LoadList(AppsToInstallList);
437 
438         // Get a dlg string for later use
439         GetWindowTextW(Dlg, szCaption, _countof(szCaption));
440 
441         // Hide a placeholder from displaying
442         szTempCaption = szCaption;
443         szTempCaption.Replace(L"%ls", L"");
444         SetWindowText(Dlg, szTempCaption.GetString());
445 
446         ShowWindow(Dlg, SW_SHOW);
447 
448         // Start download process
449         DownloadParam *param = new DownloadParam(Dlg, AppsToInstallList, szCaption);
450         DWORD ThreadId;
451         HANDLE Thread = CreateThread(NULL, 0, ThreadFunc, (LPVOID) param, 0, &ThreadId);
452 
453         if (!Thread)
454         {
455             return FALSE;
456         }
457 
458         CloseHandle(Thread);
459         AppsToInstallList.RemoveAll();
460         return TRUE;
461     }
462 
463     case WM_COMMAND:
464         if (wParam == IDCANCEL)
465         {
466             SetWindowLongW(Dlg, GWLP_USERDATA, 1);
467             PostMessageW(Dlg, WM_CLOSE, 0, 0);
468         }
469         return FALSE;
470 
471     case WM_CLOSE:
472         EndDialog(Dlg, 0);
473         //DestroyWindow(Dlg);
474         return TRUE;
475 
476     default:
477         return FALSE;
478     }
479 }
480 
481 LRESULT CALLBACK CDownloadManager::DownloadProgressProc(HWND hWnd,
482                                                         UINT uMsg,
483                                                         WPARAM wParam,
484                                                         LPARAM lParam,
485                                                         UINT_PTR uIdSubclass,
486                                                         DWORD_PTR dwRefData)
487 {
488     static ATL::CStringW szProgressText;
489 
490     switch (uMsg)
491     {
492     case WM_SETTEXT:
493     {
494         if (lParam)
495         {
496             szProgressText = (PCWSTR) lParam;
497         }
498         return TRUE;
499     }
500 
501     case WM_ERASEBKGND:
502     case WM_PAINT:
503     {
504         PAINTSTRUCT  ps;
505         HDC hDC = BeginPaint(hWnd, &ps), hdcMem;
506         HBITMAP hbmMem;
507         HANDLE hOld;
508         RECT myRect;
509         UINT win_width, win_height;
510 
511         GetClientRect(hWnd, &myRect);
512 
513         /* grab the progress bar rect size */
514         win_width = myRect.right - myRect.left;
515         win_height = myRect.bottom - myRect.top;
516 
517         /* create an off-screen DC for double-buffering */
518         hdcMem = CreateCompatibleDC(hDC);
519         hbmMem = CreateCompatibleBitmap(hDC, win_width, win_height);
520 
521         hOld = SelectObject(hdcMem, hbmMem);
522 
523         /* call the original draw code and redirect it to our memory buffer */
524         DefSubclassProc(hWnd, uMsg, (WPARAM) hdcMem, lParam);
525 
526         /* draw our nifty progress text over it */
527         SelectFont(hdcMem, GetStockFont(DEFAULT_GUI_FONT));
528         DrawShadowText(hdcMem, szProgressText.GetString(), szProgressText.GetLength(),
529                        &myRect,
530                        DT_CENTER | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE,
531                        GetSysColor(COLOR_CAPTIONTEXT),
532                        GetSysColor(COLOR_3DSHADOW),
533                        1, 1);
534 
535         /* transfer the off-screen DC to the screen */
536         BitBlt(hDC, 0, 0, win_width, win_height, hdcMem, 0, 0, SRCCOPY);
537 
538         /* free the off-screen DC */
539         SelectObject(hdcMem, hOld);
540         DeleteObject(hbmMem);
541         DeleteDC(hdcMem);
542 
543         EndPaint(hWnd, &ps);
544         return 0;
545     }
546 
547     /* Raymond Chen says that we should safely unsubclass all the things!
548     (http://blogs.msdn.com/b/oldnewthing/archive/2003/11/11/55653.aspx) */
549 
550     case WM_NCDESTROY:
551     {
552         szProgressText.Empty();
553         RemoveWindowSubclass(hWnd, DownloadProgressProc, uIdSubclass);
554     }
555     /* Fall-through */
556     default:
557         return DefSubclassProc(hWnd, uMsg, wParam, lParam);
558     }
559 }
560 
561 VOID CDownloadManager::SetProgressMarquee(HWND Item, BOOL Enable)
562 {
563     if (!Item)
564         return;
565 
566     DWORD style = GetWindowLongPtr(Item, GWL_STYLE);
567     if (!style)
568         return;
569 
570     if (!SetWindowLongPtr(Item, GWL_STYLE, (Enable ? style | PBS_MARQUEE : style & ~PBS_MARQUEE)))
571         return;
572 
573     SendMessageW(Item, PBM_SETMARQUEE, Enable, 0);
574 }
575 
576 DWORD WINAPI CDownloadManager::ThreadFunc(LPVOID param)
577 {
578     CComPtr<IBindStatusCallback> dl;
579     ATL::CStringW Path;
580     PWSTR p, q;
581 
582     HWND hDlg = static_cast<DownloadParam*>(param)->Dialog;
583     HWND Item;
584     INT iAppId;
585 
586     ULONG dwContentLen, dwBytesWritten, dwBytesRead, dwStatus;
587     ULONG dwCurrentBytesRead = 0;
588     ULONG dwStatusLen = sizeof(dwStatus);
589 
590     BOOL bCancelled = FALSE;
591     BOOL bTempfile = FALSE;
592     BOOL bCab = FALSE;
593 
594     HINTERNET hOpen = NULL;
595     HINTERNET hFile = NULL;
596     HANDLE hOut = INVALID_HANDLE_VALUE;
597 
598     unsigned char lpBuffer[4096];
599     LPCWSTR lpszAgent = L"RApps/1.0";
600     URL_COMPONENTS urlComponents;
601     size_t urlLength, filenameLength;
602 
603     const ATL::CSimpleArray<DownloadInfo> &InfoArray = static_cast<DownloadParam*>(param)->AppInfo;
604     LPCWSTR szCaption = static_cast<DownloadParam*>(param)->szCaption;
605     ATL::CStringW szNewCaption;
606 
607     const DWORD dwUrlConnectFlags = INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_KEEP_CONNECTION;
608 
609     if (InfoArray.GetSize() <= 0)
610     {
611         MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD);
612         goto end;
613     }
614 
615     for (iAppId = 0; iAppId < InfoArray.GetSize(); ++iAppId)
616     {
617         // Reset progress bar
618         Item = GetDlgItem(hDlg, IDC_DOWNLOAD_PROGRESS);
619         if (Item)
620         {
621             SetProgressMarquee(Item, FALSE);
622             SendMessageW(Item, WM_SETTEXT, 0, (LPARAM) L"");
623             SendMessageW(Item, PBM_SETPOS, 0, 0);
624         }
625 
626         // is this URL an update package for RAPPS? if so store it in a different place
627         if (InfoArray[iAppId].szUrl == APPLICATION_DATABASE_URL)
628         {
629             bCab = TRUE;
630             if (!GetStorageDirectory(Path))
631                 goto end;
632         }
633         else
634         {
635             bCab = FALSE;
636             Path = SettingsInfo.szDownloadDir;
637         }
638 
639         // Change caption to show the currently downloaded app
640         if (!bCab)
641         {
642             szNewCaption.Format(szCaption, InfoArray[iAppId].szName.GetString());
643         }
644         else
645         {
646             szNewCaption.LoadStringW(IDS_DL_DIALOG_DB_DOWNLOAD_DISP);
647         }
648 
649         SetWindowTextW(hDlg, szNewCaption.GetString());
650 
651         // build the path for the download
652         p = wcsrchr(InfoArray[iAppId].szUrl.GetString(), L'/');
653         q = wcsrchr(InfoArray[iAppId].szUrl.GetString(), L'?');
654 
655         // do we have a final slash separator?
656         if (!p)
657             goto end;
658 
659         // prepare the tentative length of the filename, maybe we've to remove part of it later on
660         filenameLength = wcslen(p) * sizeof(WCHAR);
661 
662         /* do we have query arguments in the target URL after the filename? account for them
663         (e.g. https://example.org/myfile.exe?no_adware_plz) */
664         if (q && q > p && (q - p) > 0)
665             filenameLength -= wcslen(q - 1) * sizeof(WCHAR);
666 
667         // is the path valid? can we access it?
668         if (GetFileAttributesW(Path.GetString()) == INVALID_FILE_ATTRIBUTES)
669         {
670             if (!CreateDirectoryW(Path.GetString(), NULL))
671                 goto end;
672         }
673 
674         // append a \ to the provided file system path, and the filename portion from the URL after that
675         Path += L"\\";
676         Path += (LPWSTR) (p + 1);
677 
678         if (!bCab && InfoArray[iAppId].szSHA1[0] && GetFileAttributesW(Path.GetString()) != INVALID_FILE_ATTRIBUTES)
679         {
680             // only open it in case of total correctness
681             if (VerifyInteg(InfoArray[iAppId].szSHA1.GetString(), Path))
682                 goto run;
683         }
684 
685         // Add the download URL
686         SetDlgItemTextW(hDlg, IDC_DOWNLOAD_STATUS, InfoArray[iAppId].szUrl.GetString());
687 
688         DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_DOWNLOADING);
689 
690         // download it
691         bTempfile = TRUE;
692         CDownloadDialog_Constructor(hDlg, &bCancelled, IID_PPV_ARG(IBindStatusCallback, &dl));
693 
694         if (dl == NULL)
695             goto end;
696 
697         /* FIXME: this should just be using the system-wide proxy settings */
698         switch (SettingsInfo.Proxy)
699         {
700         case 0: // preconfig
701         default:
702             hOpen = InternetOpenW(lpszAgent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
703             break;
704         case 1: // direct (no proxy)
705             hOpen = InternetOpenW(lpszAgent, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
706             break;
707         case 2: // use proxy
708             hOpen = InternetOpenW(lpszAgent, INTERNET_OPEN_TYPE_PROXY, SettingsInfo.szProxyServer, SettingsInfo.szNoProxyFor, 0);
709             break;
710         }
711 
712         if (!hOpen)
713             goto end;
714 
715         dwStatusLen = sizeof(dwStatus);
716 
717         memset(&urlComponents, 0, sizeof(urlComponents));
718         urlComponents.dwStructSize = sizeof(urlComponents);
719 
720         urlLength = InfoArray[iAppId].szUrl.GetLength();
721         urlComponents.dwSchemeLength = urlLength + 1;
722         urlComponents.lpszScheme = (LPWSTR) malloc(urlComponents.dwSchemeLength * sizeof(WCHAR));
723         urlComponents.dwHostNameLength = urlLength + 1;
724         urlComponents.lpszHostName = (LPWSTR) malloc(urlComponents.dwHostNameLength * sizeof(WCHAR));
725 
726         if (!InternetCrackUrlW(InfoArray[iAppId].szUrl, urlLength + 1, ICU_DECODE | ICU_ESCAPE, &urlComponents))
727             goto end;
728 
729         dwContentLen = 0;
730 
731         if (urlComponents.nScheme == INTERNET_SCHEME_HTTP || urlComponents.nScheme == INTERNET_SCHEME_HTTPS)
732         {
733             hFile = InternetOpenUrlW(hOpen, InfoArray[iAppId].szUrl.GetString(), NULL, 0,
734                                      dwUrlConnectFlags,
735                                      0);
736             if (!hFile)
737             {
738                 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD2);
739                 goto end;
740             }
741 
742             // query connection
743             if (!HttpQueryInfoW(hFile, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &dwStatus, &dwStatusLen, NULL))
744                 goto end;
745 
746             if (dwStatus != HTTP_STATUS_OK)
747             {
748                 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD);
749                 goto end;
750             }
751 
752             // query content length
753             HttpQueryInfoW(hFile, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwContentLen, &dwStatusLen, NULL);
754         }
755 
756         if (urlComponents.nScheme == INTERNET_SCHEME_FTP)
757         {
758             // force passive mode on FTP
759             hFile = InternetOpenUrlW(hOpen, InfoArray[iAppId].szUrl.GetString(), NULL, 0,
760                                      dwUrlConnectFlags | INTERNET_FLAG_PASSIVE,
761                                      0);
762             if (!hFile)
763             {
764                 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD2);
765                 goto end;
766             }
767 
768             dwContentLen = FtpGetFileSize(hFile, &dwStatus);
769         }
770 
771         if (!dwContentLen)
772         {
773             // content-length is not known, enable marquee mode
774             SetProgressMarquee(Item, TRUE);
775         }
776 
777         free(urlComponents.lpszScheme);
778         free(urlComponents.lpszHostName);
779 
780 #ifdef USE_CERT_PINNING
781         // are we using HTTPS to download the RAPPS update package? check if the certificate is original
782         if ((urlComponents.nScheme == INTERNET_SCHEME_HTTPS) &&
783             (wcscmp(InfoArray[iAppId].szUrl, APPLICATION_DATABASE_URL) == 0))
784         {
785             CLocalPtr subjectName, issuerName;
786             CStringW szMsgText;
787             bool bAskQuestion = false;
788             if (!CertGetSubjectAndIssuer(hFile, subjectName, issuerName))
789             {
790                 szMsgText.LoadStringW(IDS_UNABLE_TO_QUERY_CERT);
791                 bAskQuestion = true;
792             }
793             else
794             {
795                 if (strcmp(subjectName, CERT_SUBJECT_INFO) ||
796                     strcmp(issuerName, CERT_ISSUER_INFO))
797                 {
798                     szMsgText.Format(IDS_MISMATCH_CERT_INFO, (char*)subjectName, (const char*)issuerName);
799                     bAskQuestion = true;
800                 }
801             }
802 
803             if (bAskQuestion)
804             {
805                 if (MessageBoxW(hMainWnd, szMsgText.GetString(), NULL, MB_YESNO | MB_ICONERROR) != IDYES)
806                 {
807                     goto end;
808                 }
809             }
810         }
811 #endif
812 
813         hOut = CreateFileW(Path.GetString(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
814 
815         if (hOut == INVALID_HANDLE_VALUE)
816             goto end;
817 
818         dwCurrentBytesRead = 0;
819         do
820         {
821             if (!InternetReadFile(hFile, lpBuffer, _countof(lpBuffer), &dwBytesRead))
822             {
823                 MessageBox_LoadString(hMainWnd, IDS_INTERRUPTED_DOWNLOAD);
824                 goto end;
825             }
826 
827             if (!WriteFile(hOut, &lpBuffer[0], dwBytesRead, &dwBytesWritten, NULL))
828             {
829                 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_WRITE);
830                 goto end;
831             }
832 
833             dwCurrentBytesRead += dwBytesRead;
834             dl->OnProgress(dwCurrentBytesRead, dwContentLen, 0, InfoArray[iAppId].szUrl.GetString());
835         } while (dwBytesRead && !bCancelled);
836 
837         CloseHandle(hOut);
838         hOut = INVALID_HANDLE_VALUE;
839 
840         if (bCancelled)
841             goto end;
842 
843         if (!dwContentLen)
844         {
845             // set progress bar to 100%
846             SetProgressMarquee(Item, FALSE);
847 
848             dwContentLen = dwCurrentBytesRead;
849             dl->OnProgress(dwCurrentBytesRead, dwContentLen, 0, InfoArray[iAppId].szUrl.GetString());
850         }
851 
852         /* if this thing isn't a RAPPS update and it has a SHA-1 checksum
853         verify its integrity by using the native advapi32.A_SHA1 functions */
854         if (!bCab && InfoArray[iAppId].szSHA1[0] != 0)
855         {
856             ATL::CStringW szMsgText;
857 
858             // change a few strings in the download dialog to reflect the verification process
859             if (!szMsgText.LoadStringW(IDS_INTEG_CHECK_TITLE))
860                 goto end;
861 
862             SetWindowTextW(hDlg, szMsgText.GetString());
863             SendMessageW(GetDlgItem(hDlg, IDC_DOWNLOAD_STATUS), WM_SETTEXT, 0, (LPARAM) Path.GetString());
864 
865             // this may take a while, depending on the file size
866             if (!VerifyInteg(InfoArray[iAppId].szSHA1.GetString(), Path.GetString()))
867             {
868                 if (!szMsgText.LoadStringW(IDS_INTEG_CHECK_FAIL))
869                     goto end;
870 
871                 MessageBoxW(hDlg, szMsgText.GetString(), NULL, MB_OK | MB_ICONERROR);
872                 goto end;
873             }
874         }
875 
876 run:
877         DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_WAITING_INSTALL);
878 
879         // run it
880         if (!bCab)
881         {
882             SHELLEXECUTEINFOW shExInfo = {0};
883             shExInfo.cbSize = sizeof(shExInfo);
884             shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
885             shExInfo.lpVerb = L"open";
886             shExInfo.lpFile = Path.GetString();
887             shExInfo.lpParameters = L"";
888             shExInfo.nShow = SW_SHOW;
889 
890             if (ShellExecuteExW(&shExInfo))
891             {
892                 //reflect installation progress in the titlebar
893                 //TODO: make a separate string with a placeholder to include app name?
894                 ATL::CStringW szMsgText = LoadStatusString(DLSTATUS_INSTALLING);
895                 SetWindowTextW(hDlg, szMsgText.GetString());
896 
897                 DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_INSTALLING);
898 
899                 //TODO: issue an install operation separately so that the apps could be downloaded in the background
900                 WaitForSingleObject(shExInfo.hProcess, INFINITE);
901                 CloseHandle(shExInfo.hProcess);
902             }
903             else
904             {
905                 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_INSTALL);
906             }
907         }
908 
909 end:
910         if (hOut != INVALID_HANDLE_VALUE)
911             CloseHandle(hOut);
912 
913         InternetCloseHandle(hFile);
914         InternetCloseHandle(hOpen);
915 
916         if (bTempfile)
917         {
918             if (bCancelled || (SettingsInfo.bDelInstaller && !bCab))
919                 DeleteFileW(Path.GetString());
920         }
921 
922         DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_FINISHED);
923     }
924 
925     delete static_cast<DownloadParam*>(param);
926     SendMessageW(hDlg, WM_CLOSE, 0, 0);
927     return 0;
928 }
929 
930 BOOL CDownloadManager::DownloadListOfApplications(const ATL::CSimpleArray<CAvailableApplicationInfo>& AppsList, BOOL bIsModal)
931 {
932     if (AppsList.GetSize() == 0)
933         return FALSE;
934 
935     // Initialize shared variables
936     for (INT i = 0; i < AppsList.GetSize(); ++i)
937     {
938         AppsToInstallList.Add(AppsList[i]); // implicit conversion to DownloadInfo
939     }
940 
941     // Create a dialog and issue a download process
942     LaunchDownloadDialog(bIsModal);
943 
944     return TRUE;
945 }
946 
947 BOOL CDownloadManager::DownloadApplication(CAvailableApplicationInfo* pAppInfo, BOOL bIsModal)
948 {
949     if (!pAppInfo)
950         return FALSE;
951 
952     Download(*pAppInfo, bIsModal);
953     return TRUE;
954 }
955 
956 VOID CDownloadManager::DownloadApplicationsDB(LPCWSTR lpUrl)
957 {
958     static DownloadInfo DatabaseDLInfo;
959     DatabaseDLInfo.szUrl = lpUrl;
960     DatabaseDLInfo.szName.LoadStringW(IDS_DL_DIALOG_DB_DISP);
961     Download(DatabaseDLInfo, TRUE);
962 }
963 
964 //TODO: Reuse the dialog
965 VOID CDownloadManager::LaunchDownloadDialog(BOOL bIsModal)
966 {
967     if (bIsModal)
968     {
969         DialogBoxW(hInst,
970                    MAKEINTRESOURCEW(IDD_DOWNLOAD_DIALOG),
971                    hMainWnd,
972                    DownloadDlgProc);
973     }
974     else
975     {
976         CreateDialogW(hInst,
977                       MAKEINTRESOURCEW(IDD_DOWNLOAD_DIALOG),
978                       hMainWnd,
979                       DownloadDlgProc);
980     }
981 }
982 // CDownloadManager
983