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