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