Something went wrong. Try again.
Reactos
Something went wrong. Try again.
37 kB · 1235 lines
C++
at listview
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236/* * PROJECT: ReactOS Applications Manager * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) * PURPOSE: Displaying a download dialog * COPYRIGHT: Copyright 2001 John R. Sheets (for CodeWeavers) * Copyright 2004 Mike McCormack (for CodeWeavers) * Copyright 2005 Ge van Geldorp (gvg@reactos.org) * Copyright 2009 Dmitry Chapyshev (dmitry@reactos.org) * Copyright 2015 Ismael Ferreras Morezuelas (swyterzone+ros@gmail.com) * Copyright 2017 Alexander Shaposhnikov (sanchaez@reactos.org) */
/* * Based on Wine dlls/shdocvw/shdocvw_main.c * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */#include "rapps.h"
#include <shlobj_undoc.h>#include <shlguid_undoc.h>
#include <atlbase.h>#include <atlcom.h>#include <atlwin.h>#include <wininet.h>#include <shellutils.h>
#include <debug.h>
#include <ui/rosctrls.h>#include <windowsx.h>#include <shlwapi_undoc.h>#include <process.h>#undef SubclassWindow
#include "rosui.h"#include "dialogs.h"#include "misc.h"#include "unattended.h"
#ifdef USE_CERT_PINNING#define CERT_ISSUER_INFO_PREFIX "US\r\nLet's Encrypt\r\nR"#define CERT_ISSUER_INFO_OLD "US\r\nLet's Encrypt\r\nR3"#define CERT_ISSUER_INFO_NEW "US\r\nLet's Encrypt\r\nR11"#define CERT_SUBJECT_INFO "rapps.reactos.org"
static boolIsTrustedPinnedCert(LPCSTR Subject, LPCSTR Issuer){ if (strcmp(Subject, CERT_SUBJECT_INFO)) return false;#ifdef CERT_ISSUER_INFO_PREFIX return Issuer == StrStrA(Issuer, CERT_ISSUER_INFO_PREFIX);#else return !strcmp(Issuer, CERT_ISSUER_INFO_OLD) || !strcmp(Issuer, CERT_ISSUER_INFO_NEW);#endif}#endif // USE_CERT_PINNING
enum DownloadType{ DLTYPE_APPLICATION, DLTYPE_DBUPDATE, DLTYPE_DBUPDATE_UNOFFICIAL};
enum DownloadStatus{ DLSTATUS_WAITING = IDS_STATUS_WAITING, DLSTATUS_DOWNLOADING = IDS_STATUS_DOWNLOADING, DLSTATUS_WAITING_INSTALL = IDS_STATUS_DOWNLOADED, DLSTATUS_INSTALLING = IDS_STATUS_INSTALLING, DLSTATUS_INSTALLED = IDS_STATUS_INSTALLED, DLSTATUS_FINISHED = IDS_STATUS_FINISHED};
CStringWLoadStatusString(DownloadStatus StatusParam){ CStringW szString; szString.LoadStringW(StatusParam); return szString;}
#define FILENAME_VALID_CHAR ( \ PATH_CHAR_CLASS_LETTER | \ PATH_CHAR_CLASS_DOT | \ PATH_CHAR_CLASS_SEMICOLON | \ PATH_CHAR_CLASS_COMMA | \ PATH_CHAR_CLASS_SPACE | \ PATH_CHAR_CLASS_OTHER_VALID)
VOIDUrlUnescapeAndMakeFileNameValid(CStringW& str){ WCHAR szPath[MAX_PATH]; DWORD cchPath = _countof(szPath); UrlUnescapeW(const_cast<LPWSTR>((LPCWSTR)str), szPath, &cchPath, 0);
for (PWCHAR pch = szPath; *pch; ++pch) { if (!PathIsValidCharW(*pch, FILENAME_VALID_CHAR)) *pch = L'_'; }
str = szPath;}
static voidSetFriendlyUrl(HWND hWnd, LPCWSTR pszUrl){ CStringW buf; DWORD cch = (DWORD)(wcslen(pszUrl) + 1); if (InternetCanonicalizeUrlW(pszUrl, buf.GetBuffer(cch), &cch, ICU_DECODE | ICU_NO_ENCODE)) { buf.ReleaseBuffer(); pszUrl = buf; } SetWindowTextW(hWnd, pszUrl);}
struct DownloadInfo{ DownloadInfo() : DLType(DLTYPE_APPLICATION), IType(INSTALLER_UNKNOWN), SizeInBytes(0) { } DownloadInfo(const CAppInfo &AppInfo, UINT DAF = 0) : DLType(DLTYPE_APPLICATION), Flags(DAF) { AppInfo.GetDownloadInfo(szUrl, szSHA1, SizeInBytes); szName = AppInfo.szDisplayName; IType = AppInfo.GetInstallerType(); szPackageName = AppInfo.szIdentifier;
if (Flags & DAF_SILENT) IExecType = AppInfo.GetInstallerInfo(szSilentInstallArgs);
CConfigParser *cfg = static_cast<const CAvailableApplicationInfo&>(AppInfo).GetConfigParser(); if (cfg) cfg->GetString(DB_SAVEAS, szFileName); }
bool Equal(const DownloadInfo &other) const { return DLType == other.DLType && !lstrcmpW(szUrl, other.szUrl); }
DownloadType DLType; InstallerType IType; InstallerType IExecType = INSTALLER_UNKNOWN; UINT Flags = 0; CStringW szUrl; CStringW szName; CStringW szSHA1; CStringW szPackageName; CStringW szFileName; CStringW szSilentInstallArgs; ULONG SizeInBytes;};
class CDownloaderProgress : public CWindowImpl<CDownloaderProgress, CWindow, CControlWinTraits>{ CStringW m_szProgressText;
public: CDownloaderProgress() { }
VOID SetMarquee(BOOL Enable) { if (Enable) ModifyStyle(0, PBS_MARQUEE, 0); else ModifyStyle(PBS_MARQUEE, 0, 0);
SendMessage(PBM_SETMARQUEE, Enable, 0); }
VOID SetProgress(ULONG ulProgress, ULONG ulProgressMax) { WCHAR szProgress[100];
/* format the bits and bytes into pretty and accessible units... */ StrFormatByteSizeW(ulProgress, szProgress, _countof(szProgress));
/* use our subclassed progress bar text subroutine */ CStringW ProgressText;
if (ulProgressMax) { /* total size is known */ WCHAR szProgressMax[100]; UINT uiPercentage = ((ULONGLONG)ulProgress * 100) / ulProgressMax;
/* send the current progress to the progress bar */ if (!IsWindow()) return; SendMessage(PBM_SETPOS, uiPercentage, 0);
/* format total download size */ StrFormatByteSizeW(ulProgressMax, szProgressMax, _countof(szProgressMax));
/* generate the text on progress bar */ ProgressText.Format(L"%u%% \x2014 %ls / %ls", uiPercentage, szProgress, szProgressMax); } else { /* send the current progress to the progress bar */ if (!IsWindow()) return; SendMessage(PBM_SETPOS, 0, 0);
/* total size is not known, display only current size */ ProgressText.Format(L"%ls...", szProgress); }
/* and finally display it */ if (!IsWindow()) return; SetWindowText(ProgressText.GetString()); }
LRESULT OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) { return TRUE; }
LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) { PAINTSTRUCT ps; HDC hDC = BeginPaint(&ps), hdcMem; HBITMAP hbmMem; HANDLE hOld; RECT myRect; UINT win_width, win_height;
GetClientRect(&myRect);
/* grab the progress bar rect size */ win_width = myRect.right - myRect.left; win_height = myRect.bottom - myRect.top;
/* create an off-screen DC for double-buffering */ hdcMem = CreateCompatibleDC(hDC); hbmMem = CreateCompatibleBitmap(hDC, win_width, win_height);
hOld = SelectObject(hdcMem, hbmMem);
/* call the original draw code and redirect it to our memory buffer */ DefWindowProc(uMsg, (WPARAM)hdcMem, lParam);
/* draw our nifty progress text over it */ SelectFont(hdcMem, GetStockFont(DEFAULT_GUI_FONT)); DrawShadowText( hdcMem, m_szProgressText.GetString(), m_szProgressText.GetLength(), &myRect, DT_CENTER | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE, GetSysColor(COLOR_CAPTIONTEXT), GetSysColor(COLOR_3DDKSHADOW), 1, 1);
/* transfer the off-screen DC to the screen */ BitBlt(hDC, 0, 0, win_width, win_height, hdcMem, 0, 0, SRCCOPY);
/* free the off-screen DC */ SelectObject(hdcMem, hOld); DeleteObject(hbmMem); DeleteDC(hdcMem);
EndPaint(&ps); return 0; }
LRESULT OnSetText(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) { PCWSTR pszText = (PCWSTR)lParam; if (pszText) { if (m_szProgressText != pszText) { m_szProgressText = pszText; InvalidateRect(NULL, TRUE); } } else { if (!m_szProgressText.IsEmpty()) { m_szProgressText.Empty(); InvalidateRect(NULL, TRUE); } } return TRUE; }
BEGIN_MSG_MAP(CDownloaderProgress) MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd) MESSAGE_HANDLER(WM_PAINT, OnPaint) MESSAGE_HANDLER(WM_SETTEXT, OnSetText) END_MSG_MAP()};
class CDowloadingAppsListView : public CListView{ public: HWND Create(HWND hwndParent) { RECT r; ::GetClientRect(hwndParent, &r); r.top = (2 * r.top + 1 * r.bottom) / 3; /* The vertical position at ratio 1 : 2 */ const INT MARGIN = 10; ::InflateRect(&r, -MARGIN, -MARGIN);
const DWORD style = WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | LVS_NOCOLUMNHEADER;
HWND hwnd = CListView::Create(hwndParent, r, NULL, style, WS_EX_CLIENTEDGE);
AddColumn(0, 150, LVCFMT_LEFT); AddColumn(1, 120, LVCFMT_LEFT);
return hwnd; }
VOID LoadList(ATL::CSimpleArray<DownloadInfo> arrInfo, UINT Start = 0) { const INT base = GetItemCount(); for (INT i = Start; i < arrInfo.GetSize(); ++i) { AddRow(base + i - Start, arrInfo[i].szName, DLSTATUS_WAITING); } }
VOID SetDownloadStatus(INT ItemIndex, DownloadStatus Status) { CStringW szBuffer = LoadStatusString(Status); SetItemText(ItemIndex, 1, szBuffer.GetString()); }
BOOL AddItem(INT ItemIndex, LPWSTR lpText) { LVITEMW Item;
ZeroMemory(&Item, sizeof(Item));
Item.mask = LVIF_TEXT | LVIF_STATE; Item.pszText = lpText; Item.iItem = ItemIndex;
return InsertItem(&Item); }
VOID AddRow(INT RowIndex, LPCWSTR szAppName, const DownloadStatus Status) { CStringW szStatus = LoadStatusString(Status); AddItem(RowIndex, const_cast<LPWSTR>(szAppName)); SetDownloadStatus(RowIndex, Status); }
BOOL AddColumn(INT Index, INT Width, INT Format) { LVCOLUMNW Column; ZeroMemory(&Column, sizeof(Column));
Column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM; Column.iSubItem = Index; Column.cx = Width; Column.fmt = Format;
return (InsertColumn(Index, &Column) == -1) ? FALSE : TRUE; }};
#ifdef USE_CERT_PINNINGstatic BOOLCertGetSubjectAndIssuer(HINTERNET hFile, CLocalPtr<char> &subjectInfo, CLocalPtr<char> &issuerInfo){ DWORD certInfoLength; INTERNET_CERTIFICATE_INFOA certInfo; DWORD size, flags;
size = sizeof(flags); if (!InternetQueryOptionA(hFile, INTERNET_OPTION_SECURITY_FLAGS, &flags, &size)) { return FALSE; }
if (!flags & SECURITY_FLAG_SECURE) { return FALSE; }
/* Despite what the header indicates, the implementation of INTERNET_CERTIFICATE_INFO is not Unicode-aware. */ certInfoLength = sizeof(certInfo); if (!InternetQueryOptionA(hFile, INTERNET_OPTION_SECURITY_CERTIFICATE_STRUCT, &certInfo, &certInfoLength)) { return FALSE; }
subjectInfo.Attach(certInfo.lpszSubjectInfo); issuerInfo.Attach(certInfo.lpszIssuerInfo);
if (certInfo.lpszProtocolName) LocalFree(certInfo.lpszProtocolName); if (certInfo.lpszSignatureAlgName) LocalFree(certInfo.lpszSignatureAlgName); if (certInfo.lpszEncryptionAlgName) LocalFree(certInfo.lpszEncryptionAlgName);
return certInfo.lpszSubjectInfo && certInfo.lpszIssuerInfo;}#endif
static inline VOIDMessageBox_LoadString(HWND hOwnerWnd, INT StringID){ CStringW szMsgText; if (szMsgText.LoadStringW(StringID)) { MessageBoxW(hOwnerWnd, szMsgText.GetString(), NULL, MB_OK | MB_ICONERROR); }}
static BOOLShowLastError(HWND hWndOwner, BOOL bInetError, DWORD dwLastError){ CLocalPtr<WCHAR> lpMsg;
if (!FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | (bInetError ? FORMAT_MESSAGE_FROM_HMODULE : FORMAT_MESSAGE_FROM_SYSTEM), (bInetError ? GetModuleHandleW(L"wininet.dll") : NULL), dwLastError, LANG_USER_DEFAULT, (LPWSTR)&lpMsg, 0, NULL)) { DPRINT1("FormatMessageW unexpected failure (err %d)\n", GetLastError()); return FALSE; }
if (hWndOwner && !IsWindowVisible(hWndOwner)) hWndOwner = NULL; MessageBoxW(hWndOwner, lpMsg, NULL, MB_OK | MB_ICONERROR); return TRUE;}
// Download dialog (loaddlg.cpp)HWND g_hDownloadWnd = NULL;
class CDownloadManager : public CComCoClass<CDownloadManager, &CLSID_NULL>, public CComObjectRootEx<CComMultiThreadModelNoCS>, public IUnknown{public: enum { WM_ISCANCELLED = WM_APP, // Return BOOL WM_SETSTATUS, // wParam DownloadStatus WM_GETINSTANCE, // Return CDownloadManager* WM_GETNEXT, // Return DownloadInfo* or NULL };
CDownloadManager() : m_hDlg(NULL), m_Threads(0), m_Index(0), m_bCancelled(FALSE) {}
static CDownloadManager* CreateInstanceHelper(UINT Flags) { if (!(Flags & DAF_MODAL)) { CDownloadManager* pExisting = CDownloadManager::FindInstance(); if (pExisting) { pExisting->AddRef(); return pExisting; } } CComPtr<CDownloadManager> obj; if (FAILED(ShellObjectCreator(obj))) return NULL; obj->m_fDaf = Flags; obj->m_bModal = !!(Flags & DAF_MODAL); return obj.Detach(); }
static BOOL CreateInstance(BOOL Modal, CComPtr<CDownloadManager> &Obj) { CDownloadManager *p = CreateInstanceHelper(Modal); if (!p) return FALSE; Obj.Attach(p); return TRUE; }
static CDownloadManager* FindInstance() { if (g_hDownloadWnd && IsWindowVisible(g_hDownloadWnd)) return (CDownloadManager*)SendMessageW(g_hDownloadWnd, WM_GETINSTANCE, 0, 0); return NULL; }
BOOL IsCancelled() { return !IsWindow(m_hDlg) || SendMessageW(m_hDlg, WM_ISCANCELLED, 0, 0); }
BOOL IsSilentDialog() { return m_fDaf & DAF_SILENT; }
void StartWorkerThread(); void Add(const DownloadInfo &Info); void Show(); static INT_PTR CALLBACK DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); INT_PTR RealDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); void UpdateProgress(ULONG ulProgress, ULONG ulProgressMax); static unsigned int CALLBACK ThreadFunc(void*ThreadParam); void PerformDownloadAndInstall(const DownloadInfo &Info);
DECLARE_NO_REGISTRY() DECLARE_NOT_AGGREGATABLE(CDownloadManager) BEGIN_COM_MAP(CDownloadManager) END_COM_MAP()
protected: HWND m_hDlg; UINT m_Threads; UINT m_Index; BOOL m_bCancelled; BOOL m_bModal; UINT m_fDaf = 0; WCHAR m_szCaptionFmt[100]; ATL::CSimpleArray<DownloadInfo> m_List; CDowloadingAppsListView m_ListView; CDownloaderProgress m_ProgressBar;};
voidCDownloadManager::StartWorkerThread(){ AddRef(); // To keep m_List alive in thread unsigned int ThreadId; HANDLE Thread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, this, 0, &ThreadId); if (Thread) CloseHandle(Thread); else Release();}
voidCDownloadManager::Add(const DownloadInfo &Info){ const UINT count = m_List.GetSize(), start = count; for (UINT i = 0; i < count; ++i) { if (Info.Equal(m_List[i])) return; // Already in the list } m_List.Add(Info); if (m_hDlg) m_ListView.LoadList(m_List, start);}
voidCDownloadManager::Show(){ HWND hDlg = NULL; const BOOL bSilent = IsSilentDialog(), bModal = m_bModal; if (bModal && !bSilent) DialogBoxParamW(hInst, MAKEINTRESOURCEW(IDD_DOWNLOAD_DIALOG), hMainWnd, DlgProc, (LPARAM)this); else if (!m_hDlg || !IsWindow(m_hDlg)) hDlg = CreateDialogParamW(hInst, MAKEINTRESOURCEW(IDD_DOWNLOAD_DIALOG), hMainWnd, DlgProc, (LPARAM)this);
// A DialogBox dialog cannot be invisible, it is forced visible after WM_INITDIALOG returns. // We therefore use a modeless dialog when we are both modal and silent. for (MSG msg; bModal && bSilent && hDlg && GetMessageW(&msg, NULL, 0, 0);) DispatchMessage(&msg);}
INT_PTR CALLBACKCDownloadManager::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam){ CDownloadManager* pThis = (CDownloadManager*)GetWindowLongPtrW(hDlg, DWLP_USER); if (!pThis) { if (uMsg != WM_INITDIALOG) return FALSE; SetWindowLongPtrW(hDlg, DWLP_USER, lParam); pThis = (CDownloadManager*)lParam; } return pThis->RealDlgProc(hDlg, uMsg, wParam, lParam);}
INT_PTRCDownloadManager::RealDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam){ switch (uMsg) { case WM_INITDIALOG: { g_Busy++; AddRef(); m_hDlg = hDlg; if (!m_bModal) g_hDownloadWnd = hDlg;
HICON hIconSm, hIconBg; if (hMainWnd) { hIconBg = (HICON)GetClassLongPtrW(hMainWnd, GCLP_HICON); hIconSm = (HICON)GetClassLongPtrW(hMainWnd, GCLP_HICONSM); } if (!hMainWnd || (!hIconBg || !hIconSm)) { hIconBg = hIconSm = LoadIconW(hInst, MAKEINTRESOURCEW(IDI_MAIN)); } SendMessageW(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIconBg); SendMessageW(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm);
GetWindowTextW(hDlg, m_szCaptionFmt, _countof(m_szCaptionFmt)); CStringW buf; buf = m_szCaptionFmt; buf.Replace(L"%ls", L""); SetWindowTextW(hDlg, buf); // "Downloading..."
HWND hItem = GetDlgItem(hDlg, IDC_DOWNLOAD_PROGRESS); if (hItem) { // initialize the default values for our nifty progress bar // and subclass it so that it learns to print a status text m_ProgressBar.SubclassWindow(hItem); m_ProgressBar.SendMessageW(PBM_SETRANGE, 0, MAKELPARAM(0, 100)); m_ProgressBar.SendMessageW(PBM_SETPOS, 0, 0); if (m_List.GetSize() > 0) m_ProgressBar.SetProgress(0, m_List[0].SizeInBytes); }
if (!m_ListView.Create(hDlg)) return FALSE; m_ListView.LoadList(m_List);
ShowWindow(hDlg, IsSilentDialog() ? SW_HIDE : SW_SHOW); StartWorkerThread(); return TRUE; }
case WM_COMMAND: if (LOWORD(wParam) == IDCANCEL) { m_bCancelled = TRUE; PostMessageW(hDlg, WM_CLOSE, 0, 0); } return FALSE;
case WM_CLOSE: m_bCancelled = TRUE; if (m_ProgressBar) m_ProgressBar.UnsubclassWindow(TRUE); return m_bModal && !IsSilentDialog() ? ::EndDialog(hDlg, 0) : ::DestroyWindow(hDlg);
case WM_DESTROY: if (g_hDownloadWnd == hDlg) g_hDownloadWnd = NULL; g_Busy--; if (hMainWnd) PostMessage(hMainWnd, WM_NOTIFY_OPERATIONCOMPLETED, 0, 0); if (m_bModal && IsSilentDialog()) PostQuitMessage(0); Release(); break;
case WM_ISCANCELLED: return SetDlgMsgResult(hDlg, uMsg, m_bCancelled);
case WM_SETSTATUS: m_ListView.SetDownloadStatus(m_Index - 1, (DownloadStatus)wParam); break;
case WM_GETINSTANCE: return SetDlgMsgResult(hDlg, uMsg, (INT_PTR)this);
case WM_GETNEXT: { DownloadInfo *pItem = NULL; if (!m_bCancelled && m_Index < (SIZE_T)m_List.GetSize()) pItem = &m_List[m_Index++]; return SetDlgMsgResult(hDlg, uMsg, (INT_PTR)pItem); } } return FALSE;}
voidCDownloadManager::UpdateProgress(ULONG ulProgress, ULONG ulProgressMax){ m_ProgressBar.SetProgress(ulProgress, ulProgressMax);}
unsigned int CALLBACKCDownloadManager::ThreadFunc(void* ThreadParam){ CDownloadManager *pThis = (CDownloadManager*)ThreadParam; HWND hDlg = pThis->m_hDlg; for (;;) { DownloadInfo *pItem = (DownloadInfo*)SendMessageW(hDlg, WM_GETNEXT, 0, 0); if (!pItem) break; pThis->PerformDownloadAndInstall(*pItem); } SendMessageW(hDlg, WM_CLOSE, 0, 0); return pThis->Release();}
voidCDownloadManager::PerformDownloadAndInstall(const DownloadInfo &Info){ const HWND hDlg = m_hDlg; const HWND hStatus = GetDlgItem(m_hDlg, IDC_DOWNLOAD_STATUS); SetFriendlyUrl(hStatus, Info.szUrl);
m_ProgressBar.SetMarquee(FALSE); m_ProgressBar.SendMessageW(PBM_SETPOS, 0, 0); m_ProgressBar.SetProgress(0, Info.SizeInBytes);
CStringW str; CPathW Path; PCWSTR p;
ULONG dwContentLen, dwBytesWritten, dwBytesRead, dwStatus, dwStatusLen; ULONG dwCurrentBytesRead = 0; BOOL bTempfile = FALSE, bCancelled = FALSE;
HINTERNET hOpen = NULL; HINTERNET hFile = NULL; HANDLE hOut = INVALID_HANDLE_VALUE;
LPCWSTR lpszAgent = L"RApps/1.1"; const DWORD dwUrlConnectFlags = INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_KEEP_CONNECTION; URL_COMPONENTSW urlComponents; size_t urlLength; unsigned char lpBuffer[4096];
// Change caption to show the currently downloaded app switch (Info.DLType) { case DLTYPE_APPLICATION: str.Format(m_szCaptionFmt, Info.szName.GetString()); break; case DLTYPE_DBUPDATE: str.LoadStringW(IDS_DL_DIALOG_DB_DOWNLOAD_DISP); break; case DLTYPE_DBUPDATE_UNOFFICIAL: str.LoadStringW(IDS_DL_DIALOG_DB_UNOFFICIAL_DOWNLOAD_DISP); break; } SetWindowTextW(hDlg, str);
// is this URL an update package for RAPPS? if so store it in a different place if (Info.DLType != DLTYPE_APPLICATION) { if (!GetStorageDirectory(Path)) { ShowLastError(hMainWnd, FALSE, GetLastError()); goto end; } } else { Path = SettingsInfo.szDownloadDir; }
// build the path for the download p = wcsrchr(Info.szUrl.GetString(), L'/');
// do we have a final slash separator? if (!p) { MessageBox_LoadString(hMainWnd, IDS_UNABLE_PATH); goto end; }
// is the path valid? can we access it? if (GetFileAttributesW(Path) == INVALID_FILE_ATTRIBUTES) { if (!CreateDirectoryW(Path, NULL)) { ShowLastError(hMainWnd, FALSE, GetLastError()); goto end; } }
switch (Info.DLType) { case DLTYPE_DBUPDATE: case DLTYPE_DBUPDATE_UNOFFICIAL: Path += APPLICATION_DATABASE_NAME; break; case DLTYPE_APPLICATION: { CStringW name = Info.szFileName; if (name.IsEmpty()) name = p + 1; // use the filename retrieved from URL UrlUnescapeAndMakeFileNameValid(name); Path += name; break; } }
if ((Info.DLType == DLTYPE_APPLICATION) && Info.szSHA1[0] && GetFileAttributesW(Path) != INVALID_FILE_ATTRIBUTES) { // only open it in case of total correctness if (VerifyInteg(Info.szSHA1.GetString(), Path)) goto run; }
// Download it SendMessageW(hDlg, WM_SETSTATUS, DLSTATUS_DOWNLOADING, 0); /* FIXME: this should just be using the system-wide proxy settings */ switch (SettingsInfo.Proxy) { case 0: // preconfig default: hOpen = InternetOpenW(lpszAgent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); break; case 1: // direct (no proxy) hOpen = InternetOpenW(lpszAgent, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); break; case 2: // use proxy hOpen = InternetOpenW( lpszAgent, INTERNET_OPEN_TYPE_PROXY, SettingsInfo.szProxyServer, SettingsInfo.szNoProxyFor, 0); break; }
if (!hOpen) { ShowLastError(hMainWnd, TRUE, GetLastError()); goto end; }
bTempfile = TRUE; dwContentLen = 0; dwStatusLen = sizeof(dwStatus); ZeroMemory(&urlComponents, sizeof(urlComponents)); urlComponents.dwStructSize = sizeof(urlComponents);
urlLength = Info.szUrl.GetLength(); urlComponents.dwSchemeLength = urlLength + 1; urlComponents.lpszScheme = (LPWSTR)malloc(urlComponents.dwSchemeLength * sizeof(WCHAR));
if (!InternetCrackUrlW(Info.szUrl, urlLength + 1, ICU_DECODE | ICU_ESCAPE, &urlComponents)) { ShowLastError(hMainWnd, TRUE, GetLastError()); goto end; }
if (urlComponents.nScheme == INTERNET_SCHEME_HTTP || urlComponents.nScheme == INTERNET_SCHEME_HTTPS) { hFile = InternetOpenUrlW(hOpen, Info.szUrl, NULL, 0, dwUrlConnectFlags, 0); if (!hFile) { if (!ShowLastError(hMainWnd, TRUE, GetLastError())) { /* Workaround for CORE-17377 */ MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD2); } goto end; }
// query connection if (!HttpQueryInfoW(hFile, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &dwStatus, &dwStatusLen, NULL)) { ShowLastError(hMainWnd, TRUE, GetLastError()); goto end; }
if (dwStatus != HTTP_STATUS_OK) { MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD); goto end; }
// query content length HttpQueryInfoW(hFile, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwContentLen, &dwStatusLen, NULL); } else if (urlComponents.nScheme == INTERNET_SCHEME_FTP) { // force passive mode on FTP hFile = InternetOpenUrlW(hOpen, Info.szUrl, NULL, 0, dwUrlConnectFlags | INTERNET_FLAG_PASSIVE, 0); if (!hFile) { if (!ShowLastError(hMainWnd, TRUE, GetLastError())) { /* Workaround for CORE-17377 */ MessageBox_LoadString(hMainWnd, IDS_UNABLE_TO_DOWNLOAD2); } goto end; }
dwContentLen = FtpGetFileSize(hFile, &dwStatus); } else if (urlComponents.nScheme == INTERNET_SCHEME_FILE) { // Add support for the file scheme so testing locally is simpler WCHAR LocalFilePath[MAX_PATH]; DWORD cchPath = _countof(LocalFilePath); // Ideally we would use PathCreateFromUrlAlloc here, but that is not exported (yet) HRESULT hr = PathCreateFromUrlW(Info.szUrl, LocalFilePath, &cchPath, 0); if (SUCCEEDED(hr)) { if (CopyFileW(LocalFilePath, Path, FALSE)) { goto run; } else { ShowLastError(hMainWnd, FALSE, GetLastError()); goto end; } } else { ShowLastError(hMainWnd, FALSE, hr); goto end; } }
if (!dwContentLen) { // Someone was nice enough to add this, let's use it if (Info.SizeInBytes) { dwContentLen = Info.SizeInBytes; } else { // content-length is not known, enable marquee mode m_ProgressBar.SetMarquee(TRUE); } }
free(urlComponents.lpszScheme);
#ifdef USE_CERT_PINNING // are we using HTTPS to download the RAPPS update package? check if the certificate is original if ((urlComponents.nScheme == INTERNET_SCHEME_HTTPS) && (Info.DLType == DLTYPE_DBUPDATE)) { CLocalPtr<char> subjectName, issuerName; CStringA szMsgText; bool bAskQuestion = false; if (!CertGetSubjectAndIssuer(hFile, subjectName, issuerName)) { szMsgText.LoadStringW(IDS_UNABLE_TO_QUERY_CERT); bAskQuestion = true; } else if (!IsTrustedPinnedCert(subjectName, issuerName)) { szMsgText.Format(IDS_MISMATCH_CERT_INFO, (LPCSTR)subjectName, (LPCSTR)issuerName); bAskQuestion = true; }
if (bAskQuestion) { if (MessageBoxA(hDlg, szMsgText, NULL, MB_YESNO | MB_ICONERROR) != IDYES) { goto end; } } }#endif
hOut = CreateFileW(Path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); if (hOut == INVALID_HANDLE_VALUE) { ShowLastError(hDlg, FALSE, GetLastError()); goto end; }
dwCurrentBytesRead = 0; do { bCancelled = IsCancelled(); if (bCancelled) break;
if (!InternetReadFile(hFile, lpBuffer, _countof(lpBuffer), &dwBytesRead)) { ShowLastError(hDlg, TRUE, GetLastError()); goto end; }
if (!WriteFile(hOut, &lpBuffer[0], dwBytesRead, &dwBytesWritten, NULL)) { ShowLastError(hDlg, FALSE, GetLastError()); goto end; }
dwCurrentBytesRead += dwBytesRead; UpdateProgress(dwCurrentBytesRead, dwContentLen); } while (dwBytesRead);
CloseHandle(hOut); hOut = INVALID_HANDLE_VALUE;
if (bCancelled) { DPRINT1("Operation cancelled\n"); goto end; }
if (!dwContentLen) { // set progress bar to 100% m_ProgressBar.SetMarquee(FALSE);
dwContentLen = dwCurrentBytesRead; UpdateProgress(dwCurrentBytesRead, dwContentLen); }
/* if this thing isn't a RAPPS update and it has a SHA-1 checksum verify its integrity by using the native advapi32.A_SHA1 functions */ if ((Info.DLType == DLTYPE_APPLICATION) && Info.szSHA1[0] != 0) { CStringW szMsgText;
// change a few strings in the download dialog to reflect the verification process if (!szMsgText.LoadStringW(IDS_INTEG_CHECK_TITLE)) { DPRINT1("Unable to load string\n"); goto end; }
SetWindowTextW(hDlg, szMsgText); SetWindowTextW(hStatus, Path);
// this may take a while, depending on the file size if (!VerifyInteg(Info.szSHA1, Path)) { if (!szMsgText.LoadStringW(IDS_INTEG_CHECK_FAIL)) { DPRINT1("Unable to load string\n"); goto end; }
MessageBoxW(hDlg, szMsgText, NULL, MB_OK | MB_ICONERROR); goto end; } }
run: SendMessageW(hDlg, WM_SETSTATUS, DLSTATUS_WAITING_INSTALL, 0);
// run it if (Info.DLType == DLTYPE_APPLICATION) { BOOL bSilentInstall = Info.Flags & DAF_SILENT; CStringW app, params, tempdir; SHELLEXECUTEINFOW shExInfo = { sizeof(shExInfo), SEE_MASK_NOCLOSEPROCESS, hDlg }; shExInfo.lpVerb = L"open"; shExInfo.lpFile = Path; shExInfo.lpParameters = L""; shExInfo.nShow = SW_SHOW;
if (Info.IType == INSTALLER_GENERATE) { params = L"/" CMD_KEY_GENINST + CStringW(bSilentInstall ? L" /S" : L"") + L" \"" + Info.szPackageName + L"\" \"" + shExInfo.lpFile + L"\""; shExInfo.lpParameters = params; shExInfo.lpFile = app.GetBuffer(MAX_PATH); GetModuleFileNameW(NULL, const_cast<LPWSTR>(shExInfo.lpFile), MAX_PATH); app.ReleaseBuffer(); } else if (Info.IType == INSTALLER_EXEINZIP) { HRESULT hr = ExtractArchiveForExecution(Path, Info.szPackageName, tempdir, app); if (FAILED(hr)) { ShowLastError(hDlg, FALSE, hr); goto end; } shExInfo.lpFile = app; }
if (bSilentInstall) { if (!Info.szSilentInstallArgs.IsEmpty()) { // The package wants to force specific parameters shExInfo.lpParameters = Info.szSilentInstallArgs.GetString(); } else { UINT extrainfo = 0; InstallerType it = Info.IExecType;
if (it == INSTALLER_UNKNOWN || it == INSTALLER_EXEINZIP) it = GuessInstallerType(shExInfo.lpFile, extrainfo);
if (GetSilentInstallParameters(it, extrainfo, shExInfo.lpFile, params)) { shExInfo.lpParameters = params; if (it == INSTALLER_MSI) shExInfo.lpFile = L"msiexec.exe"; // params contains the .msi path } } }
/* FIXME: Do we want to log installer status? */ WriteLogMessage(EVENTLOG_SUCCESS, MSG_SUCCESS_INSTALL, Info.szName);
if (ShellExecuteExW(&shExInfo)) { // reflect installation progress in the titlebar // TODO: make a separate string with a placeholder to include app name? CStringW szMsgText = LoadStatusString(DLSTATUS_INSTALLING); SetWindowTextW(hDlg, szMsgText);
SendMessageW(hDlg, WM_SETSTATUS, DLSTATUS_INSTALLING, 0);
// TODO: issue an install operation separately so that the apps could be downloaded in the background if (shExInfo.hProcess) { WaitForSingleObject(shExInfo.hProcess, INFINITE); CloseHandle(shExInfo.hProcess); SendMessageW(hMainWnd, WM_NOTIFY_INSTALLERFINISHED, 0, (LPARAM)(PCWSTR)Info.szPackageName); } } else { ShowLastError(hMainWnd, FALSE, GetLastError()); }
if (!tempdir.IsEmpty()) { DeleteDirectoryTree(tempdir, hDlg); } }
end: if (hOut != INVALID_HANDLE_VALUE) CloseHandle(hOut);
if (hFile) InternetCloseHandle(hFile); InternetCloseHandle(hOpen);
if (bTempfile) { if (bCancelled || (SettingsInfo.bDelInstaller && Info.DLType == DLTYPE_APPLICATION)) { // Don't delete .zip/.cab files so the user can extract from them if (bCancelled || Info.IType == INSTALLER_GENERATE || Info.IType == INSTALLER_EXEINZIP || !OpensWithExplorer(Path) || HIBYTE(ClassifyFile(Path)) != PERCEIVED_TYPE_COMPRESSED) { DeleteFileW(Path); } } }
SendMessageW(hDlg, WM_SETSTATUS, DLSTATUS_FINISHED, 0);}
BOOLDownloadListOfApplications(const CAtlList<CAppInfo *> &AppsList, UINT Flags){ if (AppsList.IsEmpty()) return FALSE;
CComPtr<CDownloadManager> pDM; if (!CDownloadManager::CreateInstance(Flags, pDM)) return FALSE;
for (POSITION it = AppsList.GetHeadPosition(); it;) { const CAppInfo *Info = AppsList.GetNext(it); pDM->Add(DownloadInfo(*Info, Flags)); } pDM->Show(); return TRUE;}
BOOLDownloadApplication(CAppInfo *pAppInfo){ if (!pAppInfo) return FALSE;
CAtlList<CAppInfo*> list; list.AddTail(pAppInfo); return DownloadListOfApplications(list);}
VOIDDownloadApplicationsDB(LPCWSTR lpUrl, BOOL IsOfficial){ CComPtr<CDownloadManager> pDM; if (!CDownloadManager::CreateInstance(DAF_MODAL, pDM)) return;
DownloadInfo DatabaseDLInfo; DatabaseDLInfo.szUrl = lpUrl; DatabaseDLInfo.szName.LoadStringW(IDS_DL_DIALOG_DB_DISP); DatabaseDLInfo.DLType = IsOfficial ? DLTYPE_DBUPDATE : DLTYPE_DBUPDATE_UNOFFICIAL;
pDM->Add(DatabaseDLInfo); pDM->Show();}