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