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