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