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