[RAPPS] CDownloadManager: simplify canceling
[reactos.git] / base / applications / rapps / loaddlg.cpp
1 /*
2 * PROJECT: ReactOS Applications Manager
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * FILE: base/applications/rapps/loaddlg.cpp
5 * PURPOSE: Displaying a download dialog
6 * COPYRIGHT: Copyright 2001 John R. Sheets (for CodeWeavers)
7 * Copyright 2004 Mike McCormack (for CodeWeavers)
8 * Copyright 2005 Ge van Geldorp (gvg@reactos.org)
9 * Copyright 2009 Dmitry Chapyshev (dmitry@reactos.org)
10 * Copyright 2015 Ismael Ferreras Morezuelas (swyterzone+ros@gmail.com)
11 * Copyright 2017 Alexander Shaposhnikov (sanchaez@reactos.org)
12 */
13
14 /*
15 * Based on Wine dlls/shdocvw/shdocvw_main.c
16 *
17 * This library is free software; you can redistribute it and/or
18 * modify it under the terms of the GNU Lesser General Public
19 * License as published by the Free Software Foundation; either
20 * version 2.1 of the License, or (at your option) any later version.
21 *
22 * This library is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 * Lesser General Public License for more details.
26 *
27 * You should have received a copy of the GNU Lesser General Public
28 * License along with this library; if not, write to the Free Software
29 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
30 */
31 #include "rapps.h"
32
33 #include <shlobj_undoc.h>
34 #include <shlguid_undoc.h>
35
36 #include <atlbase.h>
37 #include <atlcom.h>
38 #include <atlwin.h>
39 #include <wininet.h>
40 #include <shellutils.h>
41
42 #include <rosctrls.h>
43 #include <windowsx.h>
44 #undef SubclassWindow
45
46 #include "rosui.h"
47 #include "dialogs.h"
48 #include "misc.h"
49
50 #ifdef USE_CERT_PINNING
51 #define CERT_ISSUER_INFO "US\r\nLet's Encrypt\r\nLet's Encrypt Authority X3"
52 #define CERT_SUBJECT_INFO "rapps.reactos.org"
53 #endif
54
55 enum DownloadStatus
56 {
57 DLSTATUS_WAITING = IDS_STATUS_WAITING,
58 DLSTATUS_DOWNLOADING = IDS_STATUS_DOWNLOADING,
59 DLSTATUS_WAITING_INSTALL = IDS_STATUS_DOWNLOADED,
60 DLSTATUS_INSTALLING = IDS_STATUS_INSTALLING,
61 DLSTATUS_INSTALLED = IDS_STATUS_INSTALLED,
62 DLSTATUS_FINISHED = IDS_STATUS_FINISHED
63 };
64
65 ATL::CStringW LoadStatusString(DownloadStatus StatusParam)
66 {
67 ATL::CStringW szString;
68 szString.LoadStringW(StatusParam);
69 return szString;
70 }
71
72 struct DownloadInfo
73 {
74 DownloadInfo() {}
75 DownloadInfo(const CAvailableApplicationInfo& AppInfo)
76 :szUrl(AppInfo.m_szUrlDownload), szName(AppInfo.m_szName), szSHA1(AppInfo.m_szSHA1)
77 {
78 }
79
80 ATL::CStringW szUrl;
81 ATL::CStringW szName;
82 ATL::CStringW szSHA1;
83 };
84
85 struct DownloadParam
86 {
87 DownloadParam() : Dialog(NULL), AppInfo(), szCaption(NULL) {}
88 DownloadParam(HWND dlg, const ATL::CSimpleArray<DownloadInfo> &info, LPCWSTR caption)
89 : Dialog(dlg), AppInfo(info), szCaption(caption)
90 {
91 }
92
93 HWND Dialog;
94 ATL::CSimpleArray<DownloadInfo> AppInfo;
95 LPCWSTR szCaption;
96 };
97
98
99 class CDownloaderProgress
100 : public CWindowImpl<CDownloaderProgress, CWindow, CControlWinTraits>
101 {
102 ATL::CStringW m_szProgressText;
103
104 public:
105 CDownloaderProgress()
106 {
107 }
108
109 VOID SetMarquee(BOOL Enable)
110 {
111 if (Enable)
112 ModifyStyle(0, PBS_MARQUEE, 0);
113 else
114 ModifyStyle(PBS_MARQUEE, 0, 0);
115
116 SendMessage(PBM_SETMARQUEE, Enable, 0);
117 }
118
119 VOID SetProgress(ULONG ulProgress, ULONG ulProgressMax)
120 {
121 WCHAR szProgress[100];
122
123 /* format the bits and bytes into pretty and accessible units... */
124 StrFormatByteSizeW(ulProgress, szProgress, _countof(szProgress));
125
126 /* use our subclassed progress bar text subroutine */
127 ATL::CStringW ProgressText;
128
129 if (ulProgressMax)
130 {
131 /* total size is known */
132 WCHAR szProgressMax[100];
133 UINT uiPercentage = ((ULONGLONG) ulProgress * 100) / ulProgressMax;
134
135 /* send the current progress to the progress bar */
136 SendMessage(PBM_SETPOS, uiPercentage, 0);
137
138 /* format total download size */
139 StrFormatByteSizeW(ulProgressMax, szProgressMax, _countof(szProgressMax));
140
141 /* generate the text on progress bar */
142 ProgressText.Format(L"%u%% \x2014 %ls / %ls",
143 uiPercentage,
144 szProgress,
145 szProgressMax);
146 }
147 else
148 {
149 /* send the current progress to the progress bar */
150 SendMessage(PBM_SETPOS, 0, 0);
151
152 /* total size is not known, display only current size */
153 ProgressText.Format(L"%ls...", szProgress);
154 }
155
156 /* and finally display it */
157 SendMessage(WM_SETTEXT, 0, (LPARAM) ProgressText.GetString());
158 }
159
160 LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
161 {
162 PAINTSTRUCT ps;
163 HDC hDC = BeginPaint(&ps), hdcMem;
164 HBITMAP hbmMem;
165 HANDLE hOld;
166 RECT myRect;
167 UINT win_width, win_height;
168
169 GetClientRect(&myRect);
170
171 /* grab the progress bar rect size */
172 win_width = myRect.right - myRect.left;
173 win_height = myRect.bottom - myRect.top;
174
175 /* create an off-screen DC for double-buffering */
176 hdcMem = CreateCompatibleDC(hDC);
177 hbmMem = CreateCompatibleBitmap(hDC, win_width, win_height);
178
179 hOld = SelectObject(hdcMem, hbmMem);
180
181 /* call the original draw code and redirect it to our memory buffer */
182 DefWindowProc(uMsg, (WPARAM) hdcMem, lParam);
183
184 /* draw our nifty progress text over it */
185 SelectFont(hdcMem, GetStockFont(DEFAULT_GUI_FONT));
186 DrawShadowText(hdcMem, m_szProgressText.GetString(), m_szProgressText.GetLength(),
187 &myRect,
188 DT_CENTER | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE,
189 GetSysColor(COLOR_CAPTIONTEXT),
190 GetSysColor(COLOR_3DSHADOW),
191 1, 1);
192
193 /* transfer the off-screen DC to the screen */
194 BitBlt(hDC, 0, 0, win_width, win_height, hdcMem, 0, 0, SRCCOPY);
195
196 /* free the off-screen DC */
197 SelectObject(hdcMem, hOld);
198 DeleteObject(hbmMem);
199 DeleteDC(hdcMem);
200
201 EndPaint(&ps);
202 return 0;
203 }
204
205 LRESULT OnSetText(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
206 {
207 if (lParam)
208 {
209 m_szProgressText = (PCWSTR) lParam;
210 }
211 return 0;
212 }
213
214 BEGIN_MSG_MAP(CDownloaderProgress)
215 MESSAGE_HANDLER(WM_ERASEBKGND, OnPaint)
216 MESSAGE_HANDLER(WM_PAINT, OnPaint)
217 MESSAGE_HANDLER(WM_SETTEXT, OnSetText)
218 END_MSG_MAP()
219 };
220
221 class CDownloadDialog :
222 public CComObjectRootEx<CComMultiThreadModelNoCS>,
223 public IBindStatusCallback
224 {
225 HWND m_hDialog;
226 BOOL m_UrlHasBeenCopied;
227 CDownloaderProgress* m_progress;
228
229 public:
230 ~CDownloadDialog()
231 {
232 //DestroyWindow(m_hDialog);
233 }
234
235 HRESULT Initialize(HWND Dlg, CDownloaderProgress* pProgress)
236 {
237 m_hDialog = Dlg;
238 m_UrlHasBeenCopied = FALSE;
239 m_progress = pProgress;
240 return S_OK;
241 }
242
243 virtual HRESULT STDMETHODCALLTYPE OnStartBinding(
244 DWORD dwReserved,
245 IBinding *pib)
246 {
247 return S_OK;
248 }
249
250 virtual HRESULT STDMETHODCALLTYPE GetPriority(
251 LONG *pnPriority)
252 {
253 return S_OK;
254 }
255
256 virtual HRESULT STDMETHODCALLTYPE OnLowResource(
257 DWORD reserved)
258 {
259 return S_OK;
260 }
261
262 virtual HRESULT STDMETHODCALLTYPE OnProgress(
263 ULONG ulProgress,
264 ULONG ulProgressMax,
265 ULONG ulStatusCode,
266 LPCWSTR szStatusText)
267 {
268 HWND Item;
269 LONG r;
270
271 m_progress->SetProgress(ulProgress, ulProgressMax);
272
273 Item = GetDlgItem(m_hDialog, IDC_DOWNLOAD_STATUS);
274 if (Item && szStatusText && wcslen(szStatusText) > 0 && m_UrlHasBeenCopied == FALSE)
275 {
276 SIZE_T len = wcslen(szStatusText) + 1;
277 ATL::CStringW buf;
278 DWORD dummyLen;
279
280 /* beautify our url for display purposes */
281 if (!InternetCanonicalizeUrlW(szStatusText, buf.GetBuffer(len), &dummyLen, ICU_DECODE | ICU_NO_ENCODE))
282 {
283 /* just use the original */
284 buf.ReleaseBuffer();
285 buf = szStatusText;
286 }
287 else
288 {
289 buf.ReleaseBuffer();
290 }
291
292 /* paste it into our dialog and don't do it again in this instance */
293 SendMessageW(Item, WM_SETTEXT, 0, (LPARAM) buf.GetString());
294 m_UrlHasBeenCopied = TRUE;
295 }
296
297 SetLastError(ERROR_SUCCESS);
298 r = GetWindowLongPtrW(m_hDialog, GWLP_USERDATA);
299 if (r || GetLastError() != ERROR_SUCCESS)
300 {
301 return E_ABORT;
302 }
303
304 return S_OK;
305 }
306
307 virtual HRESULT STDMETHODCALLTYPE OnStopBinding(
308 HRESULT hresult,
309 LPCWSTR szError)
310 {
311 return S_OK;
312 }
313
314 virtual HRESULT STDMETHODCALLTYPE GetBindInfo(
315 DWORD *grfBINDF,
316 BINDINFO *pbindinfo)
317 {
318 return S_OK;
319 }
320
321 virtual HRESULT STDMETHODCALLTYPE OnDataAvailable(
322 DWORD grfBSCF,
323 DWORD dwSize,
324 FORMATETC *pformatetc,
325 STGMEDIUM *pstgmed)
326 {
327 return S_OK;
328 }
329
330 virtual HRESULT STDMETHODCALLTYPE OnObjectAvailable(
331 REFIID riid,
332 IUnknown *punk)
333 {
334 return S_OK;
335 }
336
337 BEGIN_COM_MAP(CDownloadDialog)
338 COM_INTERFACE_ENTRY_IID(IID_IBindStatusCallback, IBindStatusCallback)
339 END_COM_MAP()
340 };
341
342 class CDowloadingAppsListView
343 : public CListView
344 {
345 public:
346 HWND Create(HWND hwndParent)
347 {
348 RECT r = {10, 150, 320, 350};
349 const DWORD style = WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SINGLESEL
350 | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | LVS_NOCOLUMNHEADER;
351
352 HWND hwnd = CListView::Create(hwndParent, r, NULL, style, WS_EX_CLIENTEDGE);
353
354 AddColumn(0, 150, LVCFMT_LEFT);
355 AddColumn(1, 120, LVCFMT_LEFT);
356
357 return hwnd;
358 }
359
360 VOID LoadList(ATL::CSimpleArray<DownloadInfo> arrInfo)
361 {
362 for (INT i = 0; i < arrInfo.GetSize(); ++i)
363 {
364 AddRow(i, arrInfo[i].szName.GetString(), DLSTATUS_WAITING);
365 }
366 }
367
368 VOID SetDownloadStatus(INT ItemIndex, DownloadStatus Status)
369 {
370 ATL::CStringW szBuffer = LoadStatusString(Status);
371 SetItemText(ItemIndex, 1, szBuffer.GetString());
372 }
373
374 BOOL AddItem(INT ItemIndex, LPWSTR lpText)
375 {
376 LVITEMW Item;
377
378 ZeroMemory(&Item, sizeof(Item));
379
380 Item.mask = LVIF_TEXT | LVIF_STATE;
381 Item.pszText = lpText;
382 Item.iItem = ItemIndex;
383
384 return InsertItem(&Item);
385 }
386
387 VOID AddRow(INT RowIndex, LPCWSTR szAppName, const DownloadStatus Status)
388 {
389 ATL::CStringW szStatus = LoadStatusString(Status);
390 AddItem(RowIndex,
391 const_cast<LPWSTR>(szAppName));
392 SetDownloadStatus(RowIndex, Status);
393 }
394
395 BOOL AddColumn(INT Index, INT Width, INT Format)
396 {
397 LVCOLUMNW Column;
398 ZeroMemory(&Column, sizeof(Column));
399
400 Column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM;
401 Column.iSubItem = Index;
402 Column.cx = Width;
403 Column.fmt = Format;
404
405 return (InsertColumn(Index, &Column) == -1) ? FALSE : TRUE;
406 }
407 };
408
409 extern "C"
410 HRESULT WINAPI CDownloadDialog_Constructor(HWND Dlg, CDownloaderProgress* pProgress, REFIID riid, LPVOID *ppv)
411 {
412 return ShellObjectCreatorInit<CDownloadDialog>(Dlg, pProgress, riid, ppv);
413 }
414
415 #ifdef USE_CERT_PINNING
416 typedef CHeapPtr<char, CLocalAllocator> CLocalPtr;
417
418 static BOOL CertGetSubjectAndIssuer(HINTERNET hFile, CLocalPtr& subjectInfo, CLocalPtr& issuerInfo)
419 {
420 DWORD certInfoLength;
421 INTERNET_CERTIFICATE_INFOA certInfo;
422 DWORD size, flags;
423
424 size = sizeof(flags);
425 if (!InternetQueryOptionA(hFile, INTERNET_OPTION_SECURITY_FLAGS, &flags, &size))
426 {
427 return FALSE;
428 }
429
430 if (!flags & SECURITY_FLAG_SECURE)
431 {
432 return FALSE;
433 }
434
435 /* Despite what the header indicates, the implementation of INTERNET_CERTIFICATE_INFO is not Unicode-aware. */
436 certInfoLength = sizeof(certInfo);
437 if (!InternetQueryOptionA(hFile,
438 INTERNET_OPTION_SECURITY_CERTIFICATE_STRUCT,
439 &certInfo,
440 &certInfoLength))
441 {
442 return FALSE;
443 }
444
445 subjectInfo.Attach(certInfo.lpszSubjectInfo);
446 issuerInfo.Attach(certInfo.lpszIssuerInfo);
447
448 if (certInfo.lpszProtocolName)
449 LocalFree(certInfo.lpszProtocolName);
450 if (certInfo.lpszSignatureAlgName)
451 LocalFree(certInfo.lpszSignatureAlgName);
452 if (certInfo.lpszEncryptionAlgName)
453 LocalFree(certInfo.lpszEncryptionAlgName);
454
455 return certInfo.lpszSubjectInfo && certInfo.lpszIssuerInfo;
456 }
457 #endif
458
459 inline VOID MessageBox_LoadString(HWND hMainWnd, INT StringID)
460 {
461 ATL::CStringW szMsgText;
462 if (szMsgText.LoadStringW(StringID))
463 {
464 MessageBoxW(hMainWnd, szMsgText.GetString(), NULL, MB_OK | MB_ICONERROR);
465 }
466 }
467
468 // Download dialog (loaddlg.cpp)
469 class CDownloadManager
470 {
471 static ATL::CSimpleArray<DownloadInfo> AppsToInstallList;
472 static CDowloadingAppsListView DownloadsListView;
473 static CDownloaderProgress ProgressBar;
474 static BOOL bCancelled;
475 public:
476 static VOID Add(DownloadInfo info);
477 static VOID Download(const DownloadInfo& DLInfo, BOOL bIsModal = FALSE);
478 static INT_PTR CALLBACK DownloadDlgProc(HWND Dlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
479 static DWORD WINAPI ThreadFunc(LPVOID Context);
480 static BOOL DownloadListOfApplications(const ATL::CSimpleArray<CAvailableApplicationInfo>& AppsList, BOOL bIsModal = FALSE);
481 static BOOL DownloadApplication(CAvailableApplicationInfo* pAppInfo, BOOL bIsModal = FALSE);
482 static VOID DownloadApplicationsDB(LPCWSTR lpUrl);
483 static VOID LaunchDownloadDialog(BOOL);
484 };
485
486
487 // CDownloadManager
488 ATL::CSimpleArray<DownloadInfo> CDownloadManager::AppsToInstallList;
489 CDowloadingAppsListView CDownloadManager::DownloadsListView;
490 CDownloaderProgress CDownloadManager::ProgressBar;
491 BOOL CDownloadManager::bCancelled;
492
493 VOID CDownloadManager::Add(DownloadInfo info)
494 {
495 AppsToInstallList.Add(info);
496 }
497
498 VOID CDownloadManager::Download(const DownloadInfo &DLInfo, BOOL bIsModal)
499 {
500 AppsToInstallList.RemoveAll();
501 AppsToInstallList.Add(DLInfo);
502 LaunchDownloadDialog(bIsModal);
503 }
504
505 INT_PTR CALLBACK CDownloadManager::DownloadDlgProc(HWND Dlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
506 {
507 static WCHAR szCaption[MAX_PATH];
508
509 switch (uMsg)
510 {
511 case WM_INITDIALOG:
512 {
513 HICON hIconSm, hIconBg;
514 ATL::CStringW szTempCaption;
515
516 bCancelled = FALSE;
517
518 hIconBg = (HICON) GetClassLongPtrW(hMainWnd, GCLP_HICON);
519 hIconSm = (HICON) GetClassLongPtrW(hMainWnd, GCLP_HICONSM);
520
521 if (hIconBg && hIconSm)
522 {
523 SendMessageW(Dlg, WM_SETICON, ICON_BIG, (LPARAM) hIconBg);
524 SendMessageW(Dlg, WM_SETICON, ICON_SMALL, (LPARAM) hIconSm);
525 }
526
527 SetWindowLongW(Dlg, GWLP_USERDATA, 0);
528 HWND Item = GetDlgItem(Dlg, IDC_DOWNLOAD_PROGRESS);
529 if (Item)
530 {
531 // initialize the default values for our nifty progress bar
532 // and subclass it so that it learns to print a status text
533 ProgressBar.SubclassWindow(Item);
534 ProgressBar.SendMessage(PBM_SETRANGE, 0, MAKELPARAM(0, 100));
535 ProgressBar.SendMessage(PBM_SETPOS, 0, 0);
536 }
537
538 // Add a ListView
539 HWND hListView = DownloadsListView.Create(Dlg);
540 if (!hListView)
541 {
542 return FALSE;
543 }
544 DownloadsListView.LoadList(AppsToInstallList);
545
546 // Get a dlg string for later use
547 GetWindowTextW(Dlg, szCaption, _countof(szCaption));
548
549 // Hide a placeholder from displaying
550 szTempCaption = szCaption;
551 szTempCaption.Replace(L"%ls", L"");
552 SetWindowText(Dlg, szTempCaption.GetString());
553
554 ShowWindow(Dlg, SW_SHOW);
555
556 // Start download process
557 DownloadParam *param = new DownloadParam(Dlg, AppsToInstallList, szCaption);
558 DWORD ThreadId;
559 HANDLE Thread = CreateThread(NULL, 0, ThreadFunc, (LPVOID) param, 0, &ThreadId);
560
561 if (!Thread)
562 {
563 return FALSE;
564 }
565
566 CloseHandle(Thread);
567 AppsToInstallList.RemoveAll();
568 return TRUE;
569 }
570
571 case WM_COMMAND:
572 if (wParam == IDCANCEL)
573 {
574 bCancelled = TRUE;
575 PostMessageW(Dlg, WM_CLOSE, 0, 0);
576 }
577 return FALSE;
578
579 case WM_CLOSE:
580 EndDialog(Dlg, 0);
581 //DestroyWindow(Dlg);
582 return TRUE;
583
584 default:
585 return FALSE;
586 }
587 }
588
589 DWORD WINAPI CDownloadManager::ThreadFunc(LPVOID param)
590 {
591 CComPtr<IBindStatusCallback> dl;
592 ATL::CStringW Path;
593 PWSTR p, q;
594
595 HWND hDlg = static_cast<DownloadParam*>(param)->Dialog;
596 HWND Item;
597 INT iAppId;
598
599 ULONG dwContentLen, dwBytesWritten, dwBytesRead, dwStatus;
600 ULONG dwCurrentBytesRead = 0;
601 ULONG dwStatusLen = sizeof(dwStatus);
602
603 BOOL bTempfile = FALSE;
604 BOOL bCab = FALSE;
605
606 HINTERNET hOpen = NULL;
607 HINTERNET hFile = NULL;
608 HANDLE hOut = INVALID_HANDLE_VALUE;
609
610 unsigned char lpBuffer[4096];
611 LPCWSTR lpszAgent = L"RApps/1.0";
612 URL_COMPONENTS urlComponents;
613 size_t urlLength, filenameLength;
614
615 const ATL::CSimpleArray<DownloadInfo> &InfoArray = static_cast<DownloadParam*>(param)->AppInfo;
616 LPCWSTR szCaption = static_cast<DownloadParam*>(param)->szCaption;
617 ATL::CStringW szNewCaption;
618
619 const DWORD dwUrlConnectFlags = INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_KEEP_CONNECTION;
620
621 if (InfoArray.GetSize() <= 0)
622 {
623 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD);
624 goto end;
625 }
626
627 for (iAppId = 0; iAppId < InfoArray.GetSize(); ++iAppId)
628 {
629 // Reset progress bar
630 Item = GetDlgItem(hDlg, IDC_DOWNLOAD_PROGRESS);
631 if (Item)
632 {
633 ProgressBar.SetMarquee(FALSE);
634 ProgressBar.SendMessage(WM_SETTEXT, 0, (LPARAM) L"");
635 ProgressBar.SendMessage(PBM_SETPOS, 0, 0);
636 }
637
638 // is this URL an update package for RAPPS? if so store it in a different place
639 if (InfoArray[iAppId].szUrl == APPLICATION_DATABASE_URL)
640 {
641 bCab = TRUE;
642 if (!GetStorageDirectory(Path))
643 goto end;
644 }
645 else
646 {
647 bCab = FALSE;
648 Path = SettingsInfo.szDownloadDir;
649 }
650
651 // Change caption to show the currently downloaded app
652 if (!bCab)
653 {
654 szNewCaption.Format(szCaption, InfoArray[iAppId].szName.GetString());
655 }
656 else
657 {
658 szNewCaption.LoadStringW(IDS_DL_DIALOG_DB_DOWNLOAD_DISP);
659 }
660
661 SetWindowTextW(hDlg, szNewCaption.GetString());
662
663 // build the path for the download
664 p = wcsrchr(InfoArray[iAppId].szUrl.GetString(), L'/');
665 q = wcsrchr(InfoArray[iAppId].szUrl.GetString(), L'?');
666
667 // do we have a final slash separator?
668 if (!p)
669 goto end;
670
671 // prepare the tentative length of the filename, maybe we've to remove part of it later on
672 filenameLength = wcslen(p) * sizeof(WCHAR);
673
674 /* do we have query arguments in the target URL after the filename? account for them
675 (e.g. https://example.org/myfile.exe?no_adware_plz) */
676 if (q && q > p && (q - p) > 0)
677 filenameLength -= wcslen(q - 1) * sizeof(WCHAR);
678
679 // is the path valid? can we access it?
680 if (GetFileAttributesW(Path.GetString()) == INVALID_FILE_ATTRIBUTES)
681 {
682 if (!CreateDirectoryW(Path.GetString(), NULL))
683 goto end;
684 }
685
686 // append a \ to the provided file system path, and the filename portion from the URL after that
687 Path += L"\\";
688 Path += (LPWSTR) (p + 1);
689
690 if (!bCab && InfoArray[iAppId].szSHA1[0] && GetFileAttributesW(Path.GetString()) != INVALID_FILE_ATTRIBUTES)
691 {
692 // only open it in case of total correctness
693 if (VerifyInteg(InfoArray[iAppId].szSHA1.GetString(), Path))
694 goto run;
695 }
696
697 // Add the download URL
698 SetDlgItemTextW(hDlg, IDC_DOWNLOAD_STATUS, InfoArray[iAppId].szUrl.GetString());
699
700 DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_DOWNLOADING);
701
702 // download it
703 bTempfile = TRUE;
704 CDownloadDialog_Constructor(hDlg, &ProgressBar, IID_PPV_ARG(IBindStatusCallback, &dl));
705
706 if (dl == NULL)
707 goto end;
708
709 /* FIXME: this should just be using the system-wide proxy settings */
710 switch (SettingsInfo.Proxy)
711 {
712 case 0: // preconfig
713 default:
714 hOpen = InternetOpenW(lpszAgent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
715 break;
716 case 1: // direct (no proxy)
717 hOpen = InternetOpenW(lpszAgent, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
718 break;
719 case 2: // use proxy
720 hOpen = InternetOpenW(lpszAgent, INTERNET_OPEN_TYPE_PROXY, SettingsInfo.szProxyServer, SettingsInfo.szNoProxyFor, 0);
721 break;
722 }
723
724 if (!hOpen)
725 goto end;
726
727 dwStatusLen = sizeof(dwStatus);
728
729 memset(&urlComponents, 0, sizeof(urlComponents));
730 urlComponents.dwStructSize = sizeof(urlComponents);
731
732 urlLength = InfoArray[iAppId].szUrl.GetLength();
733 urlComponents.dwSchemeLength = urlLength + 1;
734 urlComponents.lpszScheme = (LPWSTR) malloc(urlComponents.dwSchemeLength * sizeof(WCHAR));
735 urlComponents.dwHostNameLength = urlLength + 1;
736 urlComponents.lpszHostName = (LPWSTR) malloc(urlComponents.dwHostNameLength * sizeof(WCHAR));
737
738 if (!InternetCrackUrlW(InfoArray[iAppId].szUrl, urlLength + 1, ICU_DECODE | ICU_ESCAPE, &urlComponents))
739 goto end;
740
741 dwContentLen = 0;
742
743 if (urlComponents.nScheme == INTERNET_SCHEME_HTTP || urlComponents.nScheme == INTERNET_SCHEME_HTTPS)
744 {
745 hFile = InternetOpenUrlW(hOpen, InfoArray[iAppId].szUrl.GetString(), NULL, 0,
746 dwUrlConnectFlags,
747 0);
748 if (!hFile)
749 {
750 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD2);
751 goto end;
752 }
753
754 // query connection
755 if (!HttpQueryInfoW(hFile, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &dwStatus, &dwStatusLen, NULL))
756 goto end;
757
758 if (dwStatus != HTTP_STATUS_OK)
759 {
760 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD);
761 goto end;
762 }
763
764 // query content length
765 HttpQueryInfoW(hFile, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwContentLen, &dwStatusLen, NULL);
766 }
767
768 if (urlComponents.nScheme == INTERNET_SCHEME_FTP)
769 {
770 // force passive mode on FTP
771 hFile = InternetOpenUrlW(hOpen, InfoArray[iAppId].szUrl.GetString(), NULL, 0,
772 dwUrlConnectFlags | INTERNET_FLAG_PASSIVE,
773 0);
774 if (!hFile)
775 {
776 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD2);
777 goto end;
778 }
779
780 dwContentLen = FtpGetFileSize(hFile, &dwStatus);
781 }
782
783 if (!dwContentLen)
784 {
785 // content-length is not known, enable marquee mode
786 ProgressBar.SetMarquee(TRUE);
787 }
788
789 free(urlComponents.lpszScheme);
790 free(urlComponents.lpszHostName);
791
792 #ifdef USE_CERT_PINNING
793 // are we using HTTPS to download the RAPPS update package? check if the certificate is original
794 if ((urlComponents.nScheme == INTERNET_SCHEME_HTTPS) &&
795 (wcscmp(InfoArray[iAppId].szUrl, APPLICATION_DATABASE_URL) == 0))
796 {
797 CLocalPtr subjectName, issuerName;
798 CStringW szMsgText;
799 bool bAskQuestion = false;
800 if (!CertGetSubjectAndIssuer(hFile, subjectName, issuerName))
801 {
802 szMsgText.LoadStringW(IDS_UNABLE_TO_QUERY_CERT);
803 bAskQuestion = true;
804 }
805 else
806 {
807 if (strcmp(subjectName, CERT_SUBJECT_INFO) ||
808 strcmp(issuerName, CERT_ISSUER_INFO))
809 {
810 szMsgText.Format(IDS_MISMATCH_CERT_INFO, (char*)subjectName, (const char*)issuerName);
811 bAskQuestion = true;
812 }
813 }
814
815 if (bAskQuestion)
816 {
817 if (MessageBoxW(hMainWnd, szMsgText.GetString(), NULL, MB_YESNO | MB_ICONERROR) != IDYES)
818 {
819 goto end;
820 }
821 }
822 }
823 #endif
824
825 hOut = CreateFileW(Path.GetString(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
826
827 if (hOut == INVALID_HANDLE_VALUE)
828 goto end;
829
830 dwCurrentBytesRead = 0;
831 do
832 {
833 if (!InternetReadFile(hFile, lpBuffer, _countof(lpBuffer), &dwBytesRead))
834 {
835 MessageBox_LoadString(hMainWnd, IDS_INTERRUPTED_DOWNLOAD);
836 goto end;
837 }
838
839 if (!WriteFile(hOut, &lpBuffer[0], dwBytesRead, &dwBytesWritten, NULL))
840 {
841 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_WRITE);
842 goto end;
843 }
844
845 dwCurrentBytesRead += dwBytesRead;
846 dl->OnProgress(dwCurrentBytesRead, dwContentLen, 0, InfoArray[iAppId].szUrl.GetString());
847 } while (dwBytesRead && !bCancelled);
848
849 CloseHandle(hOut);
850 hOut = INVALID_HANDLE_VALUE;
851
852 if (bCancelled)
853 goto end;
854
855 if (!dwContentLen)
856 {
857 // set progress bar to 100%
858 ProgressBar.SetMarquee(FALSE);
859
860 dwContentLen = dwCurrentBytesRead;
861 dl->OnProgress(dwCurrentBytesRead, dwContentLen, 0, InfoArray[iAppId].szUrl.GetString());
862 }
863
864 /* if this thing isn't a RAPPS update and it has a SHA-1 checksum
865 verify its integrity by using the native advapi32.A_SHA1 functions */
866 if (!bCab && InfoArray[iAppId].szSHA1[0] != 0)
867 {
868 ATL::CStringW szMsgText;
869
870 // change a few strings in the download dialog to reflect the verification process
871 if (!szMsgText.LoadStringW(IDS_INTEG_CHECK_TITLE))
872 goto end;
873
874 SetWindowTextW(hDlg, szMsgText.GetString());
875 SendMessageW(GetDlgItem(hDlg, IDC_DOWNLOAD_STATUS), WM_SETTEXT, 0, (LPARAM) Path.GetString());
876
877 // this may take a while, depending on the file size
878 if (!VerifyInteg(InfoArray[iAppId].szSHA1.GetString(), Path.GetString()))
879 {
880 if (!szMsgText.LoadStringW(IDS_INTEG_CHECK_FAIL))
881 goto end;
882
883 MessageBoxW(hDlg, szMsgText.GetString(), NULL, MB_OK | MB_ICONERROR);
884 goto end;
885 }
886 }
887
888 run:
889 DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_WAITING_INSTALL);
890
891 // run it
892 if (!bCab)
893 {
894 SHELLEXECUTEINFOW shExInfo = {0};
895 shExInfo.cbSize = sizeof(shExInfo);
896 shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
897 shExInfo.lpVerb = L"open";
898 shExInfo.lpFile = Path.GetString();
899 shExInfo.lpParameters = L"";
900 shExInfo.nShow = SW_SHOW;
901
902 if (ShellExecuteExW(&shExInfo))
903 {
904 //reflect installation progress in the titlebar
905 //TODO: make a separate string with a placeholder to include app name?
906 ATL::CStringW szMsgText = LoadStatusString(DLSTATUS_INSTALLING);
907 SetWindowTextW(hDlg, szMsgText.GetString());
908
909 DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_INSTALLING);
910
911 //TODO: issue an install operation separately so that the apps could be downloaded in the background
912 WaitForSingleObject(shExInfo.hProcess, INFINITE);
913 CloseHandle(shExInfo.hProcess);
914 }
915 else
916 {
917 MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_INSTALL);
918 }
919 }
920
921 end:
922 if (hOut != INVALID_HANDLE_VALUE)
923 CloseHandle(hOut);
924
925 InternetCloseHandle(hFile);
926 InternetCloseHandle(hOpen);
927
928 if (bTempfile)
929 {
930 if (bCancelled || (SettingsInfo.bDelInstaller && !bCab))
931 DeleteFileW(Path.GetString());
932 }
933
934 DownloadsListView.SetDownloadStatus(iAppId, DLSTATUS_FINISHED);
935 }
936
937 delete static_cast<DownloadParam*>(param);
938 SendMessageW(hDlg, WM_CLOSE, 0, 0);
939 return 0;
940 }
941
942 //TODO: Reuse the dialog
943 VOID CDownloadManager::LaunchDownloadDialog(BOOL bIsModal)
944 {
945 if (bIsModal)
946 {
947 DialogBoxW(hInst,
948 MAKEINTRESOURCEW(IDD_DOWNLOAD_DIALOG),
949 hMainWnd,
950 DownloadDlgProc);
951 }
952 else
953 {
954 CreateDialogW(hInst,
955 MAKEINTRESOURCEW(IDD_DOWNLOAD_DIALOG),
956 hMainWnd,
957 DownloadDlgProc);
958 }
959 }
960 // CDownloadManager
961
962
963 BOOL DownloadListOfApplications(const ATL::CSimpleArray<CAvailableApplicationInfo>& AppsList, BOOL bIsModal)
964 {
965 if (AppsList.GetSize() == 0)
966 return FALSE;
967
968 // Initialize shared variables
969 for (INT i = 0; i < AppsList.GetSize(); ++i)
970 {
971 CDownloadManager::Add(AppsList[i]); // implicit conversion to DownloadInfo
972 }
973
974 // Create a dialog and issue a download process
975 CDownloadManager::LaunchDownloadDialog(bIsModal);
976
977 return TRUE;
978 }
979
980 BOOL DownloadApplication(CAvailableApplicationInfo* pAppInfo, BOOL bIsModal)
981 {
982 if (!pAppInfo)
983 return FALSE;
984
985 CDownloadManager::Download(*pAppInfo, bIsModal);
986 return TRUE;
987 }
988
989 VOID DownloadApplicationsDB(LPCWSTR lpUrl)
990 {
991 static DownloadInfo DatabaseDLInfo;
992 DatabaseDLInfo.szUrl = lpUrl;
993 DatabaseDLInfo.szName.LoadStringW(IDS_DL_DIALOG_DB_DISP);
994 CDownloadManager::Download(DatabaseDLInfo, TRUE);
995 }
996