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