#include "inspch.h" #include "util2.h" #include "download.h" #define BUFFERSIZE 4096 char g_szBuffer[BUFFERSIZE]; #define TIMEOUT_PERIOD 120 #define PATCHWIN9xKEY "SOFTWARE\\Microsoft\\Advanced INF Setup" //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // CDownloader::CDownloader() : CTimeTracker(0) { _pCb = NULL; _pBnd = NULL; _cRef = 1; _pStm = NULL; _pMkr = NULL; _uFlags = NULL; _hDL = CreateEvent(NULL, TRUE, FALSE, NULL); DllAddRef(); } //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // CDownloader::~CDownloader() { if(_hDL) CloseHandle(_hDL); DllRelease(); } //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // STDMETHODIMP CDownloader::QueryInterface(const GUID &riid,void **ppv ) { *ppv = NULL ; if( IsEqualGUID(riid,IID_IUnknown) ) { *ppv = (IUnknown *) (IBindStatusCallback *)this; } else if (IsEqualGUID(riid,IID_IBindStatusCallback) ) { *ppv = (IBindStatusCallback *) this; } else if (IsEqualGUID(riid, IID_IAuthenticate)) *ppv = (IAuthenticate *) this; if (*ppv) { // increment our reference count before we hand out our interface ((LPUNKNOWN)*ppv)->AddRef(); return(NOERROR); } return( E_NOINTERFACE ); } //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // STDMETHODIMP_(ULONG) CDownloader::AddRef() { return(++_cRef); } //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // STDMETHODIMP_(ULONG) CDownloader::Release() { if(!--_cRef) { delete this; return(0); } return( _cRef ); } //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // STDMETHODIMP CDownloader::GetBindInfo( DWORD *grfBINDF, BINDINFO *pbindInfo) { // clear BINDINFO but keep its size DWORD cbSize = pbindInfo->cbSize; ZeroMemory( pbindInfo, cbSize ); pbindInfo->cbSize = cbSize; *grfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA | BINDF_RESYNCHRONIZE | BINDF_PREFERDEFAULTHANDLER; pbindInfo->dwBindVerb = BINDVERB_GET; return(NOERROR); } //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // STDMETHODIMP CDownloader::OnStartBinding(DWORD /*grfBSCOption*/,IBinding *p) { // BUGBUG: should check to see options are what we think they are EnterCriticalSection(&g_cs); _pBnd = p; _pBnd->AddRef(); LeaveCriticalSection(&g_cs); return(NOERROR); } //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // STDMETHODIMP CDownloader::GetPriority(LONG *pnPriority) { return(E_NOTIMPL); } //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // STDMETHODIMP CDownloader::OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR pwzStatusText) { IndicateWinsockActivity(); return NOERROR; } //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // STDMETHODIMP CDownloader::OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC *pFmtetc, STGMEDIUM *pstgmed) { // bring in major changes here HRESULT hr = NOERROR; DWORD dwRead = 0; DWORD dwReadThisCall = 0; DWORD dwWritten = 0; if(!_pStm) { _pStm = pstgmed->pstm; _pStm->AddRef(); } // should ignore WAIT_TIMEOUT while getting bytes from urlmon _fTimeoutValid = FALSE; do { hr = _pStm->Read(g_szBuffer, BUFFERSIZE, &dwRead); if( SUCCEEDED(hr) || ( (hr == E_PENDING) && (dwRead > 0) ) ) { if(_hFile) if(WriteFile(_hFile, g_szBuffer, dwRead, &dwWritten, NULL)) { _uBytesSoFar += dwRead; dwReadThisCall += dwRead; if(_pCb) _pCb->OnProgress(_uBytesSoFar >> 10, NULL); } else { hr = E_FAIL; Abort(); } } } while (hr == NOERROR); // SetInstallBytes SetBytes(dwReadThisCall, TRUE); _uTickCount = 0; _fTimeoutValid = TRUE; // should increment dwTickCount if WAIT_TIMEOUT occurs now return NOERROR; } //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // STDMETHODIMP CDownloader::OnObjectAvailable(REFIID riid, IUnknown *punk) { return(E_NOTIMPL); } //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // STDMETHODIMP CDownloader::OnLowResource(DWORD reserved) { // BUGBUG: really should have this kind of harsh policy on this ... _pBnd->Abort(); return(S_OK); } //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // STDMETHODIMP CDownloader::OnStopBinding(HRESULT hrError, LPCWSTR szError) { _fTimeoutValid = FALSE; StopClock(); if((hrError == E_ABORT) && _fTimeout) { // This is the timeout case _hDLResult = INET_E_CONNECTION_TIMEOUT; } else { // this is all other cases _hDLResult = hrError; } SetEvent(_hDL); return(NOERROR); } /* IAuthenticate::Authenticate */ STDMETHODIMP CDownloader::Authenticate(HWND *phwnd, LPWSTR *pszUserName, LPWSTR *pszPassword) { if (!phwnd || !pszUserName || !pszPassword) return E_POINTER; *pszUserName = NULL; *pszPassword = NULL; // BUGBUG: Need to have our own window! NULL does not work! // *phwnd = NULL; *phwnd = GetDesktopWindow() ; return S_OK; } //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // HRESULT GetAMoniker( LPOLESTR url, IMoniker ** ppmkr ) { // FUTURE: This really should be a call to MkParseDisplayNameEx!!! HRESULT hr = CreateURLMoniker(0,url,ppmkr); // hr = ::MkParseDisplayNameEx(0, url, 0, ppmkr); return( hr ); } //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // HRESULT CDownloader::SetupDownload(LPCSTR pszUrl, IMyDownloadCallback *pcb, DWORD dwFlags, LPCSTR pszFilenameToUse) { LPOLESTR pwszUrl; LPSTR pszFilename; if(!pszUrl) return E_INVALIDARG; lstrcpyn(_szURL, pszUrl, INTERNET_MAX_URL_LENGTH); pwszUrl = OLESTRFROMANSI(pszUrl); if(!pwszUrl) return E_OUTOFMEMORY; IMoniker *ptmpmkr; HRESULT hr = GetAMoniker( pwszUrl, &ptmpmkr ); IBindCtx * pBindCtx = 0; if( SUCCEEDED(hr) ) { if(SUCCEEDED(::CreateBindCtx(0,&pBindCtx))) hr = ::RegisterBindStatusCallback(pBindCtx, (IBindStatusCallback *) this, 0, 0) ; } if( SUCCEEDED(hr) ) { AddRef(); // setup path for download if(FAILED( CreateTempDirOnMaxDrive(_szDest, sizeof(_szDest)))) goto GetOut; if(pszFilenameToUse) { SafeAddPath(_szDest, pszFilenameToUse, sizeof(_szDest)); } else { pszFilename = ParseURLA(pszUrl); SafeAddPath(_szDest, pszFilename, sizeof(_szDest)); } _pMkr = ptmpmkr; _pCb = pcb; _uFlags = dwFlags; _pBndContext = pBindCtx; _fTimeout = FALSE; _fTimeoutValid = TRUE; _uBytesSoFar = 0; _uTickCount = 0; _pStm = 0; } GetOut: if(pwszUrl) CoTaskMemFree(pwszUrl); return hr; } HRESULT CDownloader::DoDownload(LPSTR pszPath, DWORD dwBufSize) { HRESULT hr = NOERROR; BOOL fQuit = FALSE; DWORD dwRet; if(!_pMkr) return E_UNEXPECTED; pszPath[0] = 0; StartClock(); // Create the file _hFile = CreateFile(_szDest, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(_hFile == INVALID_HANDLE_VALUE) hr = E_FAIL; if( SUCCEEDED(hr) ) hr = _pMkr->BindToStorage( _pBndContext, 0, IID_IStream, (void**)&_pStm ); // we need this here because it synchronus *FAIL* case, // we Set the event in onstopbinding, but we skip the loop below so it // never gets reset. // If BindToStorage fails without even sending onstopbinding, we are resetting // an unsignalled event, which is OK. if(FAILED(hr)) ResetEvent(_hDL); _pBndContext->Release(); _pBndContext = 0; // here we wait for Bind to complete //Wait for download event or abort while(SUCCEEDED(hr) && !fQuit) { dwRet = MsgWaitForMultipleObjects(1, &_hDL, FALSE, 1000, QS_ALLINPUT); if(dwRet == WAIT_OBJECT_0) { // Download is finished hr = _hDLResult; ResetEvent(_hDL); break; } else if(dwRet == WAIT_TIMEOUT) // our wait has expired { if(_fTimeoutValid) _uTickCount++; // if our tick count is past threshold, abort the download // BUGBUG: What about synch. case? We can't time out if(_uTickCount >= TIMEOUT_PERIOD) { _fTimeout = TRUE; Abort(); } } else { MSG msg; // read all of the messages in this next loop // removing each message as we read it while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // if it's a quit message we're out of here if (msg.message == WM_QUIT) fQuit = TRUE; else { // otherwise dispatch it DispatchMessage(&msg); } // end of PeekMessage while loop } } } // clean up all our stuff if(_hFile != INVALID_HANDLE_VALUE) CloseHandle(_hFile); _hFile = INVALID_HANDLE_VALUE; // if we are not using cache and download succeeded, delete from cache if(SUCCEEDED(hr) && !(_uFlags & DOWNLOADFLAGS_USEWRITECACHE)) DeleteUrlCacheEntry(_szURL); if(FAILED(hr)) { GetParentDir(_szDest); DelNode(_szDest,0); } EnterCriticalSection(&g_cs); if(_pBnd) { _pBnd->Release(); _pBnd = 0; } LeaveCriticalSection(&g_cs); _pCb = 0; if(_pStm) { _pStm->Release(); _pStm = 0; } if(SUCCEEDED(hr)) lstrcpyn(pszPath, _szDest, dwBufSize); _szDest[0] = 0; _szURL[0] = 0; Release(); return hr; } HRESULT CDownloader::Suspend() { // in theory, we could call _pBnd->Suspend here return NOERROR; } HRESULT CDownloader::Resume() { // in theory, we could call _pBnd->Resume here return NOERROR; } HRESULT CDownloader::Abort() { EnterCriticalSection(&g_cs); if(_pBnd) { _pBnd->Abort(); } LeaveCriticalSection(&g_cs); return NOERROR; } //=--------------------------------------------------------------------------= // Function name here //=--------------------------------------------------------------------------= // Function description // // Parameters: // // Returns: // // Notes: // /* HRESULT GetAMoniker( LPOLESTR url, IMoniker ** ppmkr ) { // FUTURE: This really should be a call to MkParseDisplayNameEx!!! HRESULT hr = CreateURLMoniker(0,url,ppmkr); // hr = ::MkParseDisplayNameEx(0, url, 0, ppmkr); return( hr ); } */ CPatchDownloader::CPatchDownloader(BOOL fEnable=FALSE) : CTimeTracker(0), _fEnable(fEnable) { } CPatchDownloader::~CPatchDownloader() { ; } HRESULT CPatchDownloader::SetupDownload(DWORD dwFullTotalSize, UINT uPatchCount, IMyDownloadCallback *pcb, LPCSTR pszDLDir) { _dwFullTotalSize = dwFullTotalSize; _pCb = pcb; _uNumDownloads = uPatchCount; if (pszDLDir) lstrcpyn(_szPath, pszDLDir, sizeof(_szPath)); else lstrcpy(_szPath, ""); return S_OK; } HRESULT CPatchDownloader::DoDownload(LPCTSTR szFile) { HINF hInf = NULL; HRESULT hr = S_OK; // We shouldn't be called if patching isn't available. if (!IsEnabled()) return E_FAIL; // TODO: Advpext currently behaves as a synchronous call, so // right now we can't do timeouts and progress bar ticks. StartClock(); if(!IsNT()) { DWORD fWin9x = 1; HKEY hKey; if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, PATCHWIN9xKEY, 0, KEY_READ | KEY_WRITE, &hKey) == ERROR_SUCCESS) { RegSetValueEx(hKey, "Usewin9xDirectory", 0, REG_DWORD, (LPBYTE)&fWin9x, sizeof(DWORD)); RegCloseKey(hKey); } } if (SUCCEEDED(hr = OpenINFEngine(szFile, NULL, 0, &hInf, NULL))) { hr = g_pfnProcessFileSection(hInf, NULL, TRUE, "DefaultInstall", _szPath, CPatchDownloader::Callback, (LPVOID) this); CloseINFEngine(hInf); } _pCb = NULL; return hr; } BOOL CPatchDownloader::Callback(PATCH_DOWNLOAD_REASON Reason, PVOID lpvInfo, PVOID pvContext) { if (!pvContext) { return FALSE; } CPatchDownloader *pPatchInst = (CPatchDownloader *) pvContext; switch (Reason) { case PATCH_DOWNLOAD_ENDDOWNLOADINGDATA: break; case PATCH_DOWNLOAD_CONNECTING: case PATCH_DOWNLOAD_FINDINGSITE: case PATCH_DOWNLOAD_DOWNLOADINGDATA: // Not interesting here... break; case PATCH_DOWNLOAD_PROGRESS: { PDOWNLOAD_INFO ProgressInfo = (PDOWNLOAD_INFO)lpvInfo; DWORD dwBytesDownloaded = ProgressInfo->dwBytesToDownload - ProgressInfo->dwBytesRemaining; // Convert to KB dwBytesDownloaded = dwBytesDownloaded >> 10; // Adjust because the progress needs to be reflected as if // it was a full download. dwBytesDownloaded *= pPatchInst->GetFullDownloadSize(); if (ProgressInfo->dwBytesToDownload != 0) dwBytesDownloaded /= ProgressInfo->dwBytesToDownload >> 10; // BUGBUG: We have to handle more than 1 patching INF. // This hack divides up the progress across // multiple downloads. if (pPatchInst->GetDownloadCount() > 0) dwBytesDownloaded /= pPatchInst->GetDownloadCount(); pPatchInst->GetCallback()->OnProgress(dwBytesDownloaded, NULL); break; } case PATCH_DOWNLOAD_FILE_COMPLETED: // AdditionalInfo is Source file downloaded { TCHAR szDstFile[MAX_PATH+1]; lstrcpyn(szDstFile, pPatchInst->GetPath(), MAX_PATH); SafeAddPath(szDstFile, ParseURLA((LPCTSTR) lpvInfo), sizeof(szDstFile)); // advpext cleans up for us when it's finished downloading all the files. CopyFile((LPCTSTR)lpvInfo, szDstFile, FALSE); } break; case PATCH_DOWNLOAD_FILE_FAILED: // advpext automatically retries failures 3 times return PATCH_DOWNLOAD_FLAG_RETRY; default: break; } return TRUE; }