xref: /reactos/base/applications/rapps/loaddlg.cpp (revision 8a978a17)
1 /*
2  * PROJECT:     ReactOS Applications Manager
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Displaying a download dialog
5  * COPYRIGHT:   Copyright 2001 John R. Sheets             (for CodeWeavers)
6  *              Copyright 2004 Mike McCormack             (for CodeWeavers)
7  *              Copyright 2005 Ge van Geldorp             (gvg@reactos.org)
8  *              Copyright 2009 Dmitry Chapyshev           (dmitry@reactos.org)
9  *              Copyright 2015 Ismael Ferreras Morezuelas (swyterzone+ros@gmail.com)
10  *              Copyright 2017 Alexander Shaposhnikov     (sanchaez@reactos.org)
11  */
12 
13  /*
14   * Based on Wine dlls/shdocvw/shdocvw_main.c
15   *
16   * This library is free software; you can redistribute it and/or
17   * modify it under the terms of the GNU Lesser General Public
18   * License as published by the Free Software Foundation; either
19   * version 2.1 of the License, or (at your option) any later version.
20   *
21   * This library is distributed in the hope that it will be useful,
22   * but WITHOUT ANY WARRANTY; without even the implied warranty of
23   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
24   * Lesser General Public License for more details.
25   *
26   * You should have received a copy of the GNU Lesser General Public
27   * License along with this library; if not, write to the Free Software
28   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
29   */
30 #include "rapps.h"
31 
32 #include <shlobj_undoc.h>
33 #include <shlguid_undoc.h>
34 
35 #include <atlbase.h>
36 #include <atlcom.h>
37 #include <atlwin.h>
38 #include <wininet.h>
39 #include <shellutils.h>
40 
41 #include <debug.h>
42 
43 #include <rosctrls.h>
44 #include <windowsx.h>
45 #include <process.h>
46 #undef SubclassWindow
47 
48 #include "rosui.h"
49 #include "dialogs.h"
50 #include "misc.h"
51 
52 #ifdef USE_CERT_PINNING
53 #define CERT_ISSUER_INFO "US\r\nLet's Encrypt\r\nLet's Encrypt Authority X3"
54 #define CERT_SUBJECT_INFO "rapps.reactos.org"
55 #endif
56 
57 
58 enum DownloadType
59 {
60     DLTYPE_APPLICATION,
61     DLTYPE_DBUPDATE,
62     DLTYPE_DBUPDATE_UNOFFICIAL
63 };
64 
65 enum DownloadStatus
66 {
67     DLSTATUS_WAITING = IDS_STATUS_WAITING,
68     DLSTATUS_DOWNLOADING = IDS_STATUS_DOWNLOADING,
69     DLSTATUS_WAITING_INSTALL = IDS_STATUS_DOWNLOADED,
70     DLSTATUS_INSTALLING = IDS_STATUS_INSTALLING,
71     DLSTATUS_INSTALLED = IDS_STATUS_INSTALLED,
72     DLSTATUS_FINISHED = IDS_STATUS_FINISHED
73 };
74 
75 ATL::CStringW LoadStatusString(DownloadStatus StatusParam)
76 {
77     ATL::CStringW szString;
78     szString.LoadStringW(StatusParam);
79     return szString;
80 }
81 
82 struct DownloadInfo
83 {
84     DownloadInfo() {}
85     DownloadInfo(const CAvailableApplicationInfo& AppInfo)
86         : DLType(DLTYPE_APPLICATION)
87         , szUrl(AppInfo.m_szUrlDownload)
88         , szName(AppInfo.m_szName)
89         , szSHA1(AppInfo.m_szSHA1)
90         , SizeInBytes(AppInfo.m_SizeBytes)
91     {
92     }
93 
94     DownloadType DLType;
95     ATL::CStringW szUrl;
96     ATL::CStringW szName;
97     ATL::CStringW szSHA1;
98     ULONG SizeInBytes;
99 };
100 
101 struct DownloadParam
102 {
103     DownloadParam() : Dialog(NULL), AppInfo(), szCaption(NULL) {}
104     DownloadParam(HWND dlg, const ATL::CSimpleArray<DownloadInfo> &info, LPCWSTR caption)
105         : Dialog(dlg), AppInfo(info), szCaption(caption)
106     {
107     }
108 
109     HWND Dialog;
110     ATL::CSimpleArray<DownloadInfo> AppInfo;
111     LPCWSTR szCaption;
112 };
113 
114 
115 class CDownloaderProgress
116     : public CWindowImpl<CDownloaderProgress, CWindow, CControlWinTraits>
117 {
118     ATL::CStringW m_szProgressText;
119 
120 public:
121     CDownloaderProgress()
122     {
123     }
124 
125     VOID SetMarquee(BOOL Enable)
126     {
127         if (Enable)
128             ModifyStyle(0, PBS_MARQUEE, 0);
129         else
130             ModifyStyle(PBS_MARQUEE, 0, 0);
131 
132         SendMessage(PBM_SETMARQUEE, Enable, 0);
133     }
134 
135     VOID SetProgress(ULONG ulProgress, ULONG ulProgressMax)
136     {
137         WCHAR szProgress[100];
138 
139         /* format the bits and bytes into pretty and accessible units... */
140         StrFormatByteSizeW(ulProgress, szProgress, _countof(szProgress));
141 
142         /* use our subclassed progress bar text subroutine */
143         ATL::CStringW ProgressText;
144 
145         if (ulProgressMax)
146         {
147             /* total size is known */
148             WCHAR szProgressMax[100];
149             UINT uiPercentage = ((ULONGLONG) ulProgress * 100) / ulProgressMax;
150 
151             /* send the current progress to the progress bar */
152             if (!IsWindow()) return;
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             if (!IsWindow()) return;
168             SendMessage(PBM_SETPOS, 0, 0);
169 
170             /* total size is not known, display only current size */
171             ProgressText.Format(L"%ls...", szProgress);
172         }
173 
174         /* and finally display it */
175         if (!IsWindow()) return;
176         SendMessage(WM_SETTEXT, 0, (LPARAM) ProgressText.GetString());
177     }
178 
179     LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
180     {
181         PAINTSTRUCT  ps;
182         HDC hDC = BeginPaint(&ps), hdcMem;
183         HBITMAP hbmMem;
184         HANDLE hOld;
185         RECT myRect;
186         UINT win_width, win_height;
187 
188         GetClientRect(&myRect);
189 
190         /* grab the progress bar rect size */
191         win_width = myRect.right - myRect.left;
192         win_height = myRect.bottom - myRect.top;
193 
194         /* create an off-screen DC for double-buffering */
195         hdcMem = CreateCompatibleDC(hDC);
196         hbmMem = CreateCompatibleBitmap(hDC, win_width, win_height);
197 
198         hOld = SelectObject(hdcMem, hbmMem);
199 
200         /* call the original draw code and redirect it to our memory buffer */
201         DefWindowProc(uMsg, (WPARAM) hdcMem, lParam);
202 
203         /* draw our nifty progress text over it */
204         SelectFont(hdcMem, GetStockFont(DEFAULT_GUI_FONT));
205         DrawShadowText(hdcMem, m_szProgressText.GetString(), m_szProgressText.GetLength(),
206                        &myRect,
207                        DT_CENTER | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE,
208                        GetSysColor(COLOR_CAPTIONTEXT),
209                        GetSysColor(COLOR_3DSHADOW),
210                        1, 1);
211 
212         /* transfer the off-screen DC to the screen */
213         BitBlt(hDC, 0, 0, win_width, win_height, hdcMem, 0, 0, SRCCOPY);
214 
215         /* free the off-screen DC */
216         SelectObject(hdcMem, hOld);
217         DeleteObject(hbmMem);
218         DeleteDC(hdcMem);
219 
220         EndPaint(&ps);
221         return 0;
222     }
223 
224     LRESULT OnSetText(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
225     {
226         if (lParam)
227         {
228             m_szProgressText = (PCWSTR) lParam;
229         }
230         return 0;
231     }
232 
233     BEGIN_MSG_MAP(CDownloaderProgress)
234         MESSAGE_HANDLER(WM_ERASEBKGND, OnPaint)
235         MESSAGE_HANDLER(WM_PAINT, OnPaint)
236         MESSAGE_HANDLER(WM_SETTEXT, OnSetText)
237     END_MSG_MAP()
238 };
239 
240 class CDowloadingAppsListView
241     : public CListView
242 {
243 public:
244     HWND Create(HWND hwndParent)
245     {
246         RECT r = {10, 150, 320, 350};
247         const DWORD style = WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SINGLESEL
248             | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | LVS_NOCOLUMNHEADER;
249 
250         HWND hwnd = CListView::Create(hwndParent, r, NULL, style, WS_EX_CLIENTEDGE);
251 
252         AddColumn(0, 150, LVCFMT_LEFT);
253         AddColumn(1, 120, LVCFMT_LEFT);
254 
255         return hwnd;
256     }
257 
258     VOID LoadList(ATL::CSimpleArray<DownloadInfo> arrInfo)
259     {
260         for (INT i = 0; i < arrInfo.GetSize(); ++i)
261         {
262             AddRow(i, arrInfo[i].szName.GetString(), DLSTATUS_WAITING);
263         }
264     }
265 
266     VOID SetDownloadStatus(INT ItemIndex, DownloadStatus Status)
267     {
268         ATL::CStringW szBuffer = LoadStatusString(Status);
269         SetItemText(ItemIndex, 1, szBuffer.GetString());
270     }
271 
272     BOOL AddItem(INT ItemIndex, LPWSTR lpText)
273     {
274         LVITEMW Item;
275 
276         ZeroMemory(&Item, sizeof(Item));
277 
278         Item.mask = LVIF_TEXT | LVIF_STATE;
279         Item.pszText = lpText;
280         Item.iItem = ItemIndex;
281 
282         return InsertItem(&Item);
283     }
284 
285     VOID AddRow(INT RowIndex, LPCWSTR szAppName, const DownloadStatus Status)
286     {
287         ATL::CStringW szStatus = LoadStatusString(Status);
288         AddItem(RowIndex,
289                 const_cast<LPWSTR>(szAppName));
290         SetDownloadStatus(RowIndex, Status);
291     }
292 
293     BOOL AddColumn(INT Index, INT Width, INT Format)
294     {
295         LVCOLUMNW Column;
296         ZeroMemory(&Column, sizeof(Column));
297 
298         Column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM;
299         Column.iSubItem = Index;
300         Column.cx = Width;
301         Column.fmt = Format;
302 
303         return (InsertColumn(Index, &Column) == -1) ? FALSE : TRUE;
304     }
305 };
306 
307 #ifdef USE_CERT_PINNING
308 static BOOL CertGetSubjectAndIssuer(HINTERNET hFile, CLocalPtr<char>& subjectInfo, CLocalPtr<char>& 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> AppsDownloadList;
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::AppsDownloadList;
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     AppsDownloadList.Add(info);
386 }
387 
388 VOID CDownloadManager::Download(const DownloadInfo &DLInfo, BOOL bIsModal)
389 {
390     AppsDownloadList.RemoveAll();
391     AppsDownloadList.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(AppsDownloadList);
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, AppsDownloadList, 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         AppsDownloadList.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     if (!IsWindow(hDlg)) return;
497     ProgressBar.SetProgress(ulProgress, ulProgressMax);
498 
499     if (!IsWindow(hDlg)) return;
500     Item = GetDlgItem(hDlg, IDC_DOWNLOAD_STATUS);
501     if (Item && szStatusText && wcslen(szStatusText) > 0 && UrlHasBeenCopied == FALSE)
502     {
503         SIZE_T len = wcslen(szStatusText) + 1;
504         ATL::CStringW buf;
505         DWORD dummyLen;
506 
507         /* beautify our url for display purposes */
508         if (!InternetCanonicalizeUrlW(szStatusText, buf.GetBuffer(len), &dummyLen, ICU_DECODE | ICU_NO_ENCODE))
509         {
510             /* just use the original */
511             buf.ReleaseBuffer();
512             buf = szStatusText;
513         }
514         else
515         {
516             buf.ReleaseBuffer();
517         }
518 
519         /* paste it into our dialog and don't do it again in this instance */
520         SendMessageW(Item, WM_SETTEXT, 0, (LPARAM) buf.GetString());
521         UrlHasBeenCopied = TRUE;
522     }
523 }
524 
525 VOID ShowLastError(
526     HWND hWndOwner,
527     DWORD dwLastError)
528 {
529     CLocalPtr<WCHAR> lpMsg;
530 
531     if (!FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
532                         FORMAT_MESSAGE_FROM_SYSTEM |
533                         FORMAT_MESSAGE_IGNORE_INSERTS,
534                         NULL,
535                         dwLastError,
536                         LANG_USER_DEFAULT,
537                         (LPWSTR)&lpMsg,
538                         0, NULL))
539     {
540         return;
541     }
542 
543     MessageBoxW(hWndOwner, lpMsg, NULL, MB_OK | MB_ICONERROR);
544 }
545 
546 unsigned int WINAPI CDownloadManager::ThreadFunc(LPVOID param)
547 {
548     ATL::CStringW Path;
549     PWSTR p, q;
550 
551     HWND hDlg = static_cast<DownloadParam*>(param)->Dialog;
552     HWND Item;
553     INT iAppId;
554 
555     ULONG dwContentLen, dwBytesWritten, dwBytesRead, dwStatus;
556     ULONG dwCurrentBytesRead = 0;
557     ULONG dwStatusLen = sizeof(dwStatus);
558 
559     BOOL bTempfile = FALSE;
560 
561     HINTERNET hOpen = NULL;
562     HINTERNET hFile = NULL;
563     HANDLE hOut = INVALID_HANDLE_VALUE;
564 
565     unsigned char lpBuffer[4096];
566     LPCWSTR lpszAgent = L"RApps/1.1";
567     URL_COMPONENTSW urlComponents;
568     size_t urlLength, filenameLength;
569 
570     const ATL::CSimpleArray<DownloadInfo> &InfoArray = static_cast<DownloadParam*>(param)->AppInfo;
571     LPCWSTR szCaption = static_cast<DownloadParam*>(param)->szCaption;
572     ATL::CStringW szNewCaption;
573 
574     const DWORD dwUrlConnectFlags = INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_KEEP_CONNECTION;
575 
576     if (InfoArray.GetSize() <= 0)
577     {
578         MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD);
579         goto end;
580     }
581 
582     for (iAppId = 0; iAppId < InfoArray.GetSize(); ++iAppId)
583     {
584         // Reset progress bar
585         if (!IsWindow(hDlg)) break;
586         Item = GetDlgItem(hDlg, IDC_DOWNLOAD_PROGRESS);
587         if (Item)
588         {
589             ProgressBar.SetMarquee(FALSE);
590             ProgressBar.SendMessage(WM_SETTEXT, 0, (LPARAM) L"");
591             ProgressBar.SendMessage(PBM_SETPOS, 0, 0);
592         }
593 
594         // is this URL an update package for RAPPS? if so store it in a different place
595         if (InfoArray[iAppId].DLType != DLTYPE_APPLICATION)
596         {
597             if (!GetStorageDirectory(Path))
598             {
599                 ShowLastError(hMainWnd, GetLastError());
600                 goto end;
601             }
602         }
603         else
604         {
605             Path = SettingsInfo.szDownloadDir;
606         }
607 
608         // Change caption to show the currently downloaded app
609         switch(InfoArray[iAppId].DLType)
610         {
611         case DLTYPE_APPLICATION:
612             szNewCaption.Format(szCaption, InfoArray[iAppId].szName.GetString());
613             break;
614         case DLTYPE_DBUPDATE:
615             szNewCaption.LoadStringW(IDS_DL_DIALOG_DB_DOWNLOAD_DISP);
616             break;
617         case DLTYPE_DBUPDATE_UNOFFICIAL:
618             szNewCaption.LoadStringW(IDS_DL_DIALOG_DB_UNOFFICIAL_DOWNLOAD_DISP);
619             break;
620         }
621 
622         if (!IsWindow(hDlg)) goto end;
623         SetWindowTextW(hDlg, szNewCaption.GetString());
624 
625         // build the path for the download
626         p = wcsrchr(InfoArray[iAppId].szUrl.GetString(), L'/');
627         q = wcsrchr(InfoArray[iAppId].szUrl.GetString(), L'?');
628 
629         // do we have a final slash separator?
630         if (!p)
631         {
632             MessageBox_LoadString(hMainWnd, IDS_UNABLE_PATH);
633             goto end;
634         }
635 
636         // prepare the tentative length of the filename, maybe we've to remove part of it later on
637         filenameLength = wcslen(p) * sizeof(WCHAR);
638 
639         /* do we have query arguments in the target URL after the filename? account for them
640         (e.g. https://example.org/myfile.exe?no_adware_plz) */
641         if (q && q > p && (q - p) > 0)
642             filenameLength -= wcslen(q - 1) * sizeof(WCHAR);
643 
644         // is the path valid? can we access it?
645         if (GetFileAttributesW(Path.GetString()) == INVALID_FILE_ATTRIBUTES)
646         {
647             if (!CreateDirectoryW(Path.GetString(), NULL))
648             {
649                 ShowLastError(hMainWnd, GetLastError());
650                 goto end;
651             }
652         }
653 
654         // append a \ to the provided file system path, and the filename portion from the URL after that
655 
656         PathAddBackslashW(Path.GetBuffer(MAX_PATH));
657         switch (InfoArray[iAppId].DLType)
658         {
659         case DLTYPE_DBUPDATE:
660         case DLTYPE_DBUPDATE_UNOFFICIAL:
661             PathAppendW(Path.GetBuffer(), APPLICATION_DATABASE_NAME);
662             break;
663         case DLTYPE_APPLICATION:
664             PathAppendW(Path.GetBuffer(), (LPWSTR)(p + 1)); // use the filename retrieved from URL
665             break;
666         }
667         Path.ReleaseBuffer();
668 
669         if ((InfoArray[iAppId].DLType == DLTYPE_APPLICATION) && InfoArray[iAppId].szSHA1[0] && GetFileAttributesW(Path.GetString()) != INVALID_FILE_ATTRIBUTES)
670         {
671             // only open it in case of total correctness
672             if (VerifyInteg(InfoArray[iAppId].szSHA1.GetString(), Path))
673                 goto run;
674         }
675 
676         // Add the download URL
677         if (!IsWindow(hDlg)) goto end;
678         SetDlgItemTextW(hDlg, IDC_DOWNLOAD_STATUS, InfoArray[iAppId].szUrl.GetString());
679 
680         DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_DOWNLOADING);
681 
682         // download it
683         UrlHasBeenCopied = FALSE;
684         bTempfile = TRUE;
685 
686         /* FIXME: this should just be using the system-wide proxy settings */
687         switch (SettingsInfo.Proxy)
688         {
689         case 0: // preconfig
690         default:
691             hOpen = InternetOpenW(lpszAgent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
692             break;
693         case 1: // direct (no proxy)
694             hOpen = InternetOpenW(lpszAgent, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
695             break;
696         case 2: // use proxy
697             hOpen = InternetOpenW(lpszAgent, INTERNET_OPEN_TYPE_PROXY, SettingsInfo.szProxyServer, SettingsInfo.szNoProxyFor, 0);
698             break;
699         }
700 
701         if (!hOpen)
702         {
703             ShowLastError(hMainWnd, GetLastError());
704             goto end;
705         }
706 
707         dwStatusLen = sizeof(dwStatus);
708 
709         memset(&urlComponents, 0, sizeof(urlComponents));
710         urlComponents.dwStructSize = sizeof(urlComponents);
711 
712         urlLength = InfoArray[iAppId].szUrl.GetLength();
713         urlComponents.dwSchemeLength = urlLength + 1;
714         urlComponents.lpszScheme = (LPWSTR) malloc(urlComponents.dwSchemeLength * sizeof(WCHAR));
715 
716         if (!InternetCrackUrlW(InfoArray[iAppId].szUrl, urlLength + 1, ICU_DECODE | ICU_ESCAPE, &urlComponents))
717         {
718             ShowLastError(hMainWnd, GetLastError());
719             goto end;
720         }
721 
722         dwContentLen = 0;
723 
724         if (urlComponents.nScheme == INTERNET_SCHEME_HTTP ||
725             urlComponents.nScheme == INTERNET_SCHEME_HTTPS)
726         {
727             hFile = InternetOpenUrlW(hOpen, InfoArray[iAppId].szUrl.GetString(), NULL, 0,
728                                      dwUrlConnectFlags,
729                                      0);
730             if (!hFile)
731             {
732                 ShowLastError(hMainWnd, GetLastError());
733                 goto end;
734             }
735 
736             // query connection
737             if (!HttpQueryInfoW(hFile, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &dwStatus, &dwStatusLen, NULL))
738             {
739                 ShowLastError(hMainWnd, GetLastError());
740                 goto end;
741             }
742 
743             if (dwStatus != HTTP_STATUS_OK)
744             {
745                 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD);
746                 goto end;
747             }
748 
749             // query content length
750             HttpQueryInfoW(hFile, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwContentLen, &dwStatusLen, NULL);
751         }
752         else if (urlComponents.nScheme == INTERNET_SCHEME_FTP)
753         {
754             // force passive mode on FTP
755             hFile = InternetOpenUrlW(hOpen, InfoArray[iAppId].szUrl, NULL, 0,
756                                      dwUrlConnectFlags | INTERNET_FLAG_PASSIVE,
757                                      0);
758             if (!hFile)
759             {
760                 ShowLastError(hMainWnd, GetLastError());
761                 goto end;
762             }
763 
764             dwContentLen = FtpGetFileSize(hFile, &dwStatus);
765         }
766         else if (urlComponents.nScheme == INTERNET_SCHEME_FILE)
767         {
768             // Add support for the file scheme so testing locally is simpler
769             WCHAR LocalFilePath[MAX_PATH];
770             DWORD cchPath = _countof(LocalFilePath);
771             // Ideally we would use PathCreateFromUrlAlloc here, but that is not exported (yet)
772             HRESULT hr = PathCreateFromUrlW(InfoArray[iAppId].szUrl, LocalFilePath, &cchPath, 0);
773             if (SUCCEEDED(hr))
774             {
775                 if (CopyFileW(LocalFilePath, Path, FALSE))
776                 {
777                     goto run;
778                 }
779                 else
780                 {
781                     ShowLastError(hMainWnd, GetLastError());
782                     goto end;
783                 }
784             }
785             else
786             {
787                 ShowLastError(hMainWnd, hr);
788                 goto end;
789             }
790         }
791 
792         if (!dwContentLen)
793         {
794             // Someone was nice enough to add this, let's use it
795             if (InfoArray[iAppId].SizeInBytes)
796             {
797                 dwContentLen = InfoArray[iAppId].SizeInBytes;
798             }
799             else
800             {
801                 // content-length is not known, enable marquee mode
802                 ProgressBar.SetMarquee(TRUE);
803             }
804         }
805 
806         free(urlComponents.lpszScheme);
807 
808 #ifdef USE_CERT_PINNING
809         // are we using HTTPS to download the RAPPS update package? check if the certificate is original
810         if ((urlComponents.nScheme == INTERNET_SCHEME_HTTPS) &&
811             (InfoArray[iAppId].DLType == DLTYPE_DBUPDATE))
812         {
813             CLocalPtr<char> subjectName, issuerName;
814             CStringA szMsgText;
815             bool bAskQuestion = false;
816             if (!CertGetSubjectAndIssuer(hFile, subjectName, issuerName))
817             {
818                 szMsgText.LoadStringW(IDS_UNABLE_TO_QUERY_CERT);
819                 bAskQuestion = true;
820             }
821             else
822             {
823                 if (strcmp(subjectName, CERT_SUBJECT_INFO) ||
824                     strcmp(issuerName, CERT_ISSUER_INFO))
825                 {
826                     szMsgText.Format(IDS_MISMATCH_CERT_INFO, (char*)subjectName, (const char*)issuerName);
827                     bAskQuestion = true;
828                 }
829             }
830 
831             if (bAskQuestion)
832             {
833                 if (MessageBoxA(hMainWnd, szMsgText.GetString(), NULL, MB_YESNO | MB_ICONERROR) != IDYES)
834                 {
835                     goto end;
836                 }
837             }
838         }
839 #endif
840 
841         hOut = CreateFileW(Path.GetString(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
842 
843         if (hOut == INVALID_HANDLE_VALUE)
844         {
845             ShowLastError(hMainWnd, GetLastError());
846             goto end;
847         }
848 
849         dwCurrentBytesRead = 0;
850         do
851         {
852             if (!InternetReadFile(hFile, lpBuffer, _countof(lpBuffer), &dwBytesRead))
853             {
854                 ShowLastError(hMainWnd, GetLastError());
855                 goto end;
856             }
857 
858             if (!WriteFile(hOut, &lpBuffer[0], dwBytesRead, &dwBytesWritten, NULL))
859             {
860                 ShowLastError(hMainWnd, GetLastError());
861                 goto end;
862             }
863 
864             dwCurrentBytesRead += dwBytesRead;
865             if (!IsWindow(hDlg)) goto end;
866             UpdateProgress(hDlg, dwCurrentBytesRead, dwContentLen, 0, InfoArray[iAppId].szUrl.GetString());
867         } while (dwBytesRead && !bCancelled);
868 
869         CloseHandle(hOut);
870         hOut = INVALID_HANDLE_VALUE;
871 
872         if (bCancelled)
873         {
874             DPRINT1("Operation cancelled\n");
875             goto end;
876         }
877 
878         if (!dwContentLen)
879         {
880             // set progress bar to 100%
881             ProgressBar.SetMarquee(FALSE);
882 
883             dwContentLen = dwCurrentBytesRead;
884             if (!IsWindow(hDlg)) goto end;
885             UpdateProgress(hDlg, dwCurrentBytesRead, dwContentLen, 0, InfoArray[iAppId].szUrl.GetString());
886         }
887 
888         /* if this thing isn't a RAPPS update and it has a SHA-1 checksum
889         verify its integrity by using the native advapi32.A_SHA1 functions */
890         if ((InfoArray[iAppId].DLType == DLTYPE_APPLICATION) && InfoArray[iAppId].szSHA1[0] != 0)
891         {
892             ATL::CStringW szMsgText;
893 
894             // change a few strings in the download dialog to reflect the verification process
895             if (!szMsgText.LoadStringW(IDS_INTEG_CHECK_TITLE))
896             {
897                 DPRINT1("Unable to load string\n");
898                 goto end;
899             }
900 
901             if (!IsWindow(hDlg)) goto end;
902             SetWindowTextW(hDlg, szMsgText.GetString());
903             SendMessageW(GetDlgItem(hDlg, IDC_DOWNLOAD_STATUS), WM_SETTEXT, 0, (LPARAM) Path.GetString());
904 
905             // this may take a while, depending on the file size
906             if (!VerifyInteg(InfoArray[iAppId].szSHA1.GetString(), Path.GetString()))
907             {
908                 if (!szMsgText.LoadStringW(IDS_INTEG_CHECK_FAIL))
909                 {
910                     DPRINT1("Unable to load string\n");
911                     goto end;
912                 }
913 
914                 if (!IsWindow(hDlg)) goto end;
915                 MessageBoxW(hDlg, szMsgText.GetString(), NULL, MB_OK | MB_ICONERROR);
916                 goto end;
917             }
918         }
919 
920 run:
921         DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_WAITING_INSTALL);
922 
923         // run it
924         if (InfoArray[iAppId].DLType == DLTYPE_APPLICATION)
925         {
926             SHELLEXECUTEINFOW shExInfo = {0};
927             shExInfo.cbSize = sizeof(shExInfo);
928             shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
929             shExInfo.lpVerb = L"open";
930             shExInfo.lpFile = Path.GetString();
931             shExInfo.lpParameters = L"";
932             shExInfo.nShow = SW_SHOW;
933 
934             if (ShellExecuteExW(&shExInfo))
935             {
936                 //reflect installation progress in the titlebar
937                 //TODO: make a separate string with a placeholder to include app name?
938                 ATL::CStringW szMsgText = LoadStatusString(DLSTATUS_INSTALLING);
939                 if (!IsWindow(hDlg)) goto end;
940                 SetWindowTextW(hDlg, szMsgText.GetString());
941 
942                 DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_INSTALLING);
943 
944                 //TODO: issue an install operation separately so that the apps could be downloaded in the background
945                 WaitForSingleObject(shExInfo.hProcess, INFINITE);
946                 CloseHandle(shExInfo.hProcess);
947             }
948             else
949             {
950                 ShowLastError(hMainWnd, GetLastError());
951             }
952         }
953 
954 end:
955         if (hOut != INVALID_HANDLE_VALUE)
956             CloseHandle(hOut);
957 
958         if (hFile)
959             InternetCloseHandle(hFile);
960         InternetCloseHandle(hOpen);
961 
962         if (bTempfile)
963         {
964             if (bCancelled || (SettingsInfo.bDelInstaller && (InfoArray[iAppId].DLType == DLTYPE_APPLICATION)))
965                 DeleteFileW(Path.GetString());
966         }
967 
968         if (!IsWindow(hDlg)) return 0;
969         DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_FINISHED);
970     }
971 
972     delete static_cast<DownloadParam*>(param);
973     if (!IsWindow(hDlg)) return 0;
974     SendMessageW(hDlg, WM_CLOSE, 0, 0);
975     return 0;
976 }
977 
978 //TODO: Reuse the dialog
979 VOID CDownloadManager::LaunchDownloadDialog(BOOL bIsModal)
980 {
981     CDownloadManager::bModal = bIsModal;
982     if (bIsModal)
983     {
984         DialogBoxW(hInst,
985                    MAKEINTRESOURCEW(IDD_DOWNLOAD_DIALOG),
986                    hMainWnd,
987                    DownloadDlgProc);
988     }
989     else
990     {
991         CreateDialogW(hInst,
992                       MAKEINTRESOURCEW(IDD_DOWNLOAD_DIALOG),
993                       hMainWnd,
994                       DownloadDlgProc);
995     }
996 }
997 // CDownloadManager
998 
999 
1000 BOOL DownloadListOfApplications(const ATL::CSimpleArray<CAvailableApplicationInfo>& AppsList, BOOL bIsModal)
1001 {
1002     if (AppsList.GetSize() == 0)
1003         return FALSE;
1004 
1005     // Initialize shared variables
1006     for (INT i = 0; i < AppsList.GetSize(); ++i)
1007     {
1008         CDownloadManager::Add(AppsList[i]); // implicit conversion to DownloadInfo
1009     }
1010 
1011     // Create a dialog and issue a download process
1012     CDownloadManager::LaunchDownloadDialog(bIsModal);
1013 
1014     return TRUE;
1015 }
1016 
1017 BOOL DownloadApplication(CAvailableApplicationInfo* pAppInfo, BOOL bIsModal)
1018 {
1019     if (!pAppInfo)
1020         return FALSE;
1021 
1022     CDownloadManager::Download(*pAppInfo, bIsModal);
1023     return TRUE;
1024 }
1025 
1026 VOID DownloadApplicationsDB(LPCWSTR lpUrl, BOOL IsOfficial)
1027 {
1028     static DownloadInfo DatabaseDLInfo;
1029     DatabaseDLInfo.szUrl = lpUrl;
1030     DatabaseDLInfo.szName.LoadStringW(IDS_DL_DIALOG_DB_DISP);
1031     DatabaseDLInfo.DLType = IsOfficial ? DLTYPE_DBUPDATE : DLTYPE_DBUPDATE_UNOFFICIAL;
1032     CDownloadManager::Download(DatabaseDLInfo, TRUE);
1033 }
1034 
1035