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