windows-nt/Source/XPSP1/NT/enduser/windows.com/wuv3/cdmlib/download.cpp

520 lines
14 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
//=======================================================================
//
// Copyright (c) 1998-1999 Microsoft Corporation. All Rights Reserved.
//
// File: download.cpp
//
// Owner: YanL
//
// Description:
//
// Internet download implementation
//
//=======================================================================
#include <windows.h>
#include <wininet.h>
#include <shlwapi.h>
#include <tchar.h>
#include <wustl.h>
#define LOGGING_LEVEL 2
#include <log.h>
#include <download.h>
static void InfiniteWaitForSingleObject(HANDLE hHandle);
CDownload::CDownload(
IN OPTIONAL int cnMaxThreads /*= 1*/
) :
m_cnMaxThreads(min(cnMaxThreads, MAX_THREADS_LIMIT)),
m_cnThreads(0)
{
LOG_block("CDownload::CDownload");
// syncronization
m_hEventExit = CreateEvent(NULL, /*bManualReset*/ TRUE, /*bInitialState*/ FALSE, NULL);
m_hEventJob = CreateEvent(NULL, /*bManualReset*/ TRUE, /*bInitialState*/ FALSE, NULL);
m_hEventDone = CreateEvent(NULL, /*bManualReset*/ FALSE, /*bInitialState*/ FALSE, NULL);
m_hSession = InternetOpen(_T("Windows Update Catalog"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
}
CDownload::~CDownload(
void
) {
LOG_block("CDownload::~CDownload");
if (m_cnThreads)
{
// Wait for all threads to exit:
HANDLE ahThreads[MAX_THREADS_LIMIT];
for (int i = 0; i < m_cnThreads; i ++)
ahThreads[i] = m_ahThreads[i];
SetEvent(m_hEventExit);
WaitForMultipleObjects(m_cnThreads, ahThreads, /*bWaitAll*/TRUE, INFINITE);
}
// to insure that m_hConnection is released before m_hSession
m_hConnection.release();
m_hSession.release();
}
bool CDownload::Connect(
IN LPCTSTR szURL
) {
LOG_block("CDownload::Connect");
// check that constructor created everything we need
if (
!m_hEventExit.valid() ||
!m_hEventJob.valid() ||
!m_hEventDone.valid() ||
!m_hSession.valid()
) {
return false;
}
// check parameters
if (NULL == szURL)
return false;
// Prepare to crack URL
URL_COMPONENTS url;
ZeroMemory(&url, sizeof(url));
url.dwStructSize = sizeof(url);
TCHAR szServerName[INTERNET_MAX_HOST_NAME_LENGTH];
url.lpszHostName = szServerName;
url.dwHostNameLength = sizeOfArray(szServerName);
m_szRootPath[0] = 0;
url.lpszUrlPath = m_szRootPath;
url.dwUrlPathLength = sizeOfArray(m_szRootPath);
TCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH];
url.lpszUserName = szUserName;
url.dwUserNameLength = sizeOfArray(szUserName);
TCHAR szPassword[INTERNET_MAX_PASSWORD_LENGTH];
url.lpszPassword = szPassword;
url.dwPasswordLength = sizeOfArray(szPassword);
if (!InternetCrackUrl(szURL, 0, 0, &url))
{
LOG_error("InternetCrackUrl(%s) failed, last error %d", szURL, GetLastError());
return false;
}
if (url.nScheme != INTERNET_SCHEME_HTTP && url.nScheme != INTERNET_SCHEME_HTTPS)
{
LOG_error("http or https only");
return false;
}
if (_T('/') == m_szRootPath[lstrlen(m_szRootPath) - 1])
m_szRootPath[lstrlen(m_szRootPath) - 1] = 0;
LOG_out("connecting to %s at %s", szServerName, m_szRootPath);
m_hConnection = InternetConnect(m_hSession, szServerName, url.nPort, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 1);
return m_hConnection.valid();
}
bool CDownload::Copy(IN LPCTSTR szSourceFile, IN LPCTSTR szDestFile, IN OPTIONAL IDownloadAdviceSink* pSink /*= 0*/)
{
LOG_block("CDownload::Copy");
JOB job = { szSourceFile, szDestFile, 0, NO_ERROR, 0 };
CopyEx(&job, 1, pSink);
SetLastError(job.dwResult);
return NO_ERROR == job.dwResult || ERROR_ALREADY_EXISTS == job.dwResult ? true : false;
}
bool CDownload::Copy(IN LPCTSTR szSourceFile, IN byte_buffer& bufDest, IN OPTIONAL IDownloadAdviceSink* pSink /*= 0*/)
{
LOG_block("CDownload::Copy");
JOB job = { szSourceFile, 0, &bufDest, NO_ERROR, 0 };
CopyEx(&job, 1, pSink);
SetLastError(job.dwResult);
return NO_ERROR == job.dwResult || ERROR_ALREADY_EXISTS == job.dwResult ? true : false;
}
void CDownload::CopyEx(IN PJOB pJobs, IN int cnJobs, IN OPTIONAL IDownloadAdviceSink* pSink /*= 0*/, IN bool fWaitComplete /*= true*/)
{
LOG_block("CDownload::CopyEx");
// Init jobs queue
m_pJobsQueue = pJobs;
m_cnJobsTotal = cnJobs;
m_cnJobsToComplete = cnJobs;
m_nJobTop = 0;
// init status
m_pSink = pSink;
// Create more threads if we need to
int cnThreadsNeed = min(m_cnMaxThreads, cnJobs);
while (m_cnThreads < cnThreadsNeed)
{
DWORD dwThreadId;
m_ahThreads[m_cnThreads] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)StaticThreadProc, this, 0, &dwThreadId);
if (!m_ahThreads[m_cnThreads].valid())
break;
m_cnThreads ++;
}
if (0 == m_cnThreads) // No threads to work on
{
// This case is only if we are in new device wizard on 98
DWORD dwError = GetLastError();
LOG_out("No thread to work on (last error = %d), will work on the main one", dwError);
// turn proxy autodetection off
INTERNET_PER_CONN_OPTION option = { INTERNET_PER_CONN_FLAGS, 0 };
INTERNET_PER_CONN_OPTION_LIST list = { sizeof(INTERNET_PER_CONN_OPTION_LIST), 0, 1, 0, &option };
DWORD dwBufLen = sizeof(list);
BOOL fQueryOK = InternetQueryOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, &dwBufLen);
DWORD dwConnFlags = option.dwOption;
if (fQueryOK)
{
option.dwOption &= ~PROXY_TYPE_AUTO_DETECT;
InternetSetOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, sizeof(list));
}
while (m_nJobTop < m_cnJobsTotal)
{
DoJob(m_nJobTop);
m_nJobTop ++;
}
// restore proxy autodetection
if (fQueryOK)
{
option.dwOption = dwConnFlags;
InternetSetOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, sizeof(list));
}
}
else
{
// Normal case
// Start
ResetEvent(m_hEventDone);
SetEvent(m_hEventJob);
// Wait for all jobs to complete
if (fWaitComplete)
{
while(0 < m_cnJobsToComplete)
InfiniteWaitForSingleObject(m_hEventDone);
}
}
}
DWORD WINAPI CDownload::StaticThreadProc(IN LPVOID lpParameter)
{
// just call member function so we can access members
CDownload* pThis = static_cast<CDownload*>(lpParameter);
pThis->ThreadProc();
return 0;
}
void CDownload::ThreadProc()
{
LOG_block("CDownload::ThreadProc");
HANDLE ahevents[2] = { (HANDLE)m_hEventJob, (HANDLE)m_hEventExit };
while(true)
{
// wait for job or exit
DWORD dwRet = WaitForMultipleObjects(2, ahevents, FALSE, INFINITE);
if (WAIT_OBJECT_0+1 == dwRet)
{
return; // called to exit
}
else if (WAIT_OBJECT_0 == dwRet)
{
int nJob = m_lock.Increment(&m_nJobTop) - 1;
if (nJob < m_cnJobsTotal) // Job is waiting
{
DoJob(nJob);
// Increment complete counter
m_lock.Decrement(&m_cnJobsToComplete);
SetEvent(m_hEventDone);
}
else
{
ResetEvent(m_hEventJob); // queue is empty
}
}
}
}
void CDownload::DoJob(int nJob)
{
LOG_block("CDownload::DoJob");
DWORD dwStartTime;
if (NULL != m_pSink)
{
m_pSink->JobStarted(nJob);// Notify start
if (NO_ERROR != m_pJobsQueue[nJob].dwResult) // this will inforce abort on all jobs
{
LOG_error("Job result is set to %d", m_pJobsQueue[nJob].dwResult);
m_pSink->JobDone(nJob);
return;
}
if (m_pSink->WantToAbortJob(nJob)) // this will inforce abort on all jobs
{
LOG_error("Job %d aborted", nJob);
m_pJobsQueue[nJob].dwResult = ERROR_OPERATION_ABORTED;
m_pSink->JobDone(nJob);
return;
}
dwStartTime = GetTickCount(); // for progress
}
//Make Full path
TCHAR szFullSourcePath[MAX_PATH];
lstrcpy(szFullSourcePath, m_szRootPath);
lstrcat(szFullSourcePath, _T("/"));
lstrcat(szFullSourcePath, m_pJobsQueue[nJob].pszSrvFile);
// Do one of two copy functions
if (NULL != m_pJobsQueue[nJob].pszDstFile)
{
m_pJobsQueue[nJob].dwResult = DoCopyToFile(nJob, szFullSourcePath, m_pJobsQueue[nJob].pszDstFile); // to file
}
else
{
m_pJobsQueue[nJob].dwResult = DoCopyToBuf(nJob, szFullSourcePath, *(m_pJobsQueue[nJob].pDstBuf)); // to buffer
}
if (NULL != m_pSink)
{
if (NO_ERROR == m_pJobsQueue[nJob].dwResult)
m_pSink->JobDownloadTime(nJob, GetTickCount() - dwStartTime); // only if success
m_pSink->JobDone(nJob);
}
}
DWORD CDownload::DoCopyToFile(
IN int nJob, // for notifications
IN LPCTSTR szSourceFile,
IN LPCTSTR szDestFile
) {
LOG_block("CDownload::DoCopyToFile");
LOG_out("source %s, destination %s", szSourceFile, szDestFile);
// If destination file exist let see if we even need to download - compare it's date and size with server
FILETIME ftLocal = {0};
SYSTEMTIME stLocal = {0};
auto_hfile hFileOut = CreateFile(szDestFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFileOut.valid())
{
GetFileTime(hFileOut, NULL, NULL, &ftLocal);
FileTimeToSystemTime(&ftLocal, &stLocal);
}
auto_hinet hInetFile;
DWORD dwServerFileSize;
SYSTEMTIME stServer;
DWORD dwError = OpenInetFile(szSourceFile, hFileOut.valid() ? &stLocal: 0, hInetFile, &dwServerFileSize, &stServer);
if (NO_ERROR != dwError)
{
if (ERROR_ALREADY_EXISTS != dwError)
LOG_error("OpenInetFile failed %d", dwError);
return dwError;
}
if (NULL != m_pSink)
m_pSink->JobDownloadSize(nJob, dwServerFileSize);// Notify start
// get server file time to compare with existing local and timestamp it if replaced
FILETIME ftServer = {0};
SystemTimeToFileTime(&stServer, &ftServer);
// If destination file exist let see if we even need to download - compare it's date and size with server
if (hFileOut.valid())
{
//#ifdef _WUV3TEST
DWORD dwLocalFileSize = GetFileSize(hFileOut, NULL);
LOG_out("'%s' \t server (%d bytes) %2d/%02d %2d:%02d:%02d:%03d \t local (%d bytes) %2d/%02d %2d:%02d:%02d:%03d", szSourceFile,
dwServerFileSize, (int)stServer.wMonth, (int)stServer.wDay, (int)stServer.wHour, (int)stServer.wMinute, (int)stServer.wSecond, (int)stServer.wMilliseconds,
dwLocalFileSize, (int)stLocal.wMonth, (int)stLocal.wDay, (int)stLocal.wHour, (int)stLocal.wMinute, (int)stLocal.wSecond, (int)stLocal.wMilliseconds
);
//#endif
hFileOut.release();
}
// coping through 8K buffer
byte_buffer bufTmp;
bufTmp.resize(min(dwServerFileSize, 8 * 1024)); // use 8K buffer
if (!bufTmp.valid())
{
LOG_error("Out of memory");
return ERROR_NOT_ENOUGH_MEMORY;
}
hFileOut = CreateFile(szDestFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
return_error_if_false(hFileOut.valid());
dwError = NO_ERROR;
DWORD dwFileWritten = 0;
while(dwFileWritten < dwServerFileSize)
{
DWORD dwReadStartTime;
if (NULL != m_pSink)
{
if (m_pSink->WantToAbortJob(nJob))
{
LOG_error("Job %d aborted", nJob);
dwError = ERROR_OPERATION_ABORTED;
break;
}
dwReadStartTime = GetTickCount(); // for progress
}
DWORD dwNeedToRead = dwServerFileSize - dwFileWritten;
dwNeedToRead = min(dwNeedToRead, bufTmp.size());
DWORD dwReadSize;
if (!InternetReadFile(hInetFile, bufTmp, dwNeedToRead, &dwReadSize))
{
dwError = GetLastError();
LOG_error("InternetReadFile(hInetFile... failed %d", dwError);
break;
}
DWORD dwWritten;
if (!WriteFile(hFileOut, bufTmp, dwReadSize, &dwWritten, NULL))
{
dwError = GetLastError();
LOG_error("WriteFile(hFileOut... failed %d", dwError);
break;
}
dwFileWritten += dwReadSize;
if (NULL != m_pSink)
m_pSink->BlockDownloaded(nJob, dwReadSize, GetTickCount() - dwReadStartTime);
}
if(dwError)
{
hFileOut.release();
DeleteFile(szDestFile); // file is not complete
return dwError;
}
// timestamp destination file
SetFileTime(hFileOut, NULL, NULL, &ftServer);
return NO_ERROR;
}
DWORD CDownload::DoCopyToBuf(
IN int nJob, // for notifications
IN LPCTSTR szSourceFile,
IN byte_buffer& bufDest
) {
LOG_block("CDownload::DoCopyToBuf");
LOG_out("source %s", szSourceFile);
auto_hinet hInetFile;
DWORD dwServerFileSize;
DWORD dwError = OpenInetFile(szSourceFile, NULL, hInetFile, &dwServerFileSize, NULL);
if (NO_ERROR != dwError)
{
if (ERROR_ALREADY_EXISTS != dwError)
LOG_error("OpenInetFile failed %d", dwError);
return dwError;
}
if (NULL != m_pSink)
m_pSink->JobDownloadSize(nJob, dwServerFileSize);// Notify start
bufDest.resize(dwServerFileSize);
if (!bufDest.valid())
{
LOG_error("Out of memory");
return ERROR_NOT_ENOUGH_MEMORY;
}
DWORD dwReadSize;
return_error_if_false(InternetReadFile(hInetFile, bufDest, dwServerFileSize, &dwReadSize));
return NO_ERROR;
}
// commont part for both downloads
DWORD CDownload::OpenInetFile(
LPCTSTR szFullSourcePath,
SYSTEMTIME* pstIfModifiedSince,
auto_hinet& hInetFile,
DWORD* pdwServerFileSize,
SYSTEMTIME* pstServer
) {
LOG_block("CDownload::OpenInetFile");
hInetFile = HttpOpenRequest(m_hConnection, NULL, szFullSourcePath, _T("HTTP/1.0"), NULL, NULL, INTERNET_FLAG_DONT_CACHE|INTERNET_FLAG_KEEP_CONNECTION, 1);
return_error_if_false(hInetFile.valid());
if (pstIfModifiedSince) // local file exist co send If-Modified-Since: header
{
TCHAR szHttpTime[INTERNET_RFC1123_BUFSIZE + 1];
DWORD dwSize = sizeof(szHttpTime);
return_error_if_false(InternetTimeFromSystemTime(pstIfModifiedSince, INTERNET_RFC1123_FORMAT, szHttpTime, dwSize));
//prepare and set the header
static const TCHAR szFormat[] = _T("If-Modified-Since: %s\r\n");
TCHAR szHeader[INTERNET_RFC1123_BUFSIZE + sizeOfArray(szFormat)];
wsprintf(szHeader, szFormat, szHttpTime);
return_error_if_false(HttpAddRequestHeaders(hInetFile, szHeader, -1L, HTTP_ADDREQ_FLAG_ADD));
}
return_error_if_false(HttpSendRequest(hInetFile, NULL, 0, NULL, 0));
DWORD dwStatus;
DWORD dwLength = sizeof(dwStatus);
return_error_if_false(HttpQueryInfo(hInetFile, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE, &dwStatus, &dwLength, NULL));
if (HTTP_STATUS_NOT_MODIFIED == dwStatus)
{
LOG_out("file '%s' is the same as on server", szFullSourcePath);
return ERROR_ALREADY_EXISTS;
}
else if (HTTP_STATUS_OK != dwStatus)
{
LOG_error("file '%s' is not found", szFullSourcePath);
return ERROR_FILE_NOT_FOUND;
}
dwLength = sizeof(DWORD);
return_error_if_false(HttpQueryInfo(hInetFile, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_CONTENT_LENGTH, pdwServerFileSize, &dwLength, NULL));
if (0 == *pdwServerFileSize)
{ // 0 length files are not supported
LOG_error("file '%s' is 0 length", szFullSourcePath);
return ERROR_FILE_NOT_FOUND;
}
if (pstServer)
{
dwLength = sizeof(SYSTEMTIME);
return_error_if_false(HttpQueryInfo(hInetFile, HTTP_QUERY_FLAG_SYSTEMTIME | HTTP_QUERY_LAST_MODIFIED, pstServer, (ULONG *)&dwLength, NULL));
}
return NO_ERROR;
}
// wait forever until hHandle signals
static void InfiniteWaitForSingleObject(
HANDLE hHandle
) {
while (WAIT_OBJECT_0 + 1 == MsgWaitForMultipleObjects(1, &hHandle, FALSE, INFINITE, QS_ALLINPUT))
{
// Process messasges
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}