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