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