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