Files
dnf-utils/sgutil/sgu_download_ant.cpp
2025-09-01 20:47:58 +08:00

651 lines
17 KiB
C++

#include "sgu_stdafx.h"
#include "sgu_download_ant.h"
#include "sgu_utils.h"
#include "libEncrypt_MD5.h"
#define debugLog(...) __noop
bool parseURL(const wchar_t* pstrURL, INTERNET_SCHEME& schemeType, wchar_t* strServer, wchar_t* strObject, INTERNET_PORT& nPort);
//--------------------------------------------------------------------------------------------
CDownloadAnt::CDownloadAnt()
{
m_hThread = 0;
m_theStateCategory = IDLE;
m_theState = IDLE_NOTINIT;
m_wszURLToDownload[0] = 0;
m_wszFileToDownloadInto[0] = 0;
m_pOwner = NULL;
m_schemeType = INTERNET_SCHEME_UNKNOWN;
m_wszServer[0] = 0;
m_wszObject[0] = 0;
m_nPort = 80;
m_hInternetSession = NULL;
m_hHttpConnection = NULL;
m_hHttpFile = NULL;
m_dwUserCmd = MANAGER_CMD_CONTINUE;
m_nOverTime = INFINITE;
m_bGotFileSize = FALSE;
m_dwFileSizeTotal = 0;
m_dwFileSizeRead = 0;
m_fpFileToWrite = NULL;
UserCallBack = NULL;
m_wszStatusInfor[0] = 0;
}
//--------------------------------------------------------------------------------------------
CDownloadAnt::~CDownloadAnt()
{
//关闭
UserCallBack = NULL;
if (m_hHttpFile)
{
::InternetCloseHandle(m_hHttpFile);
m_hHttpFile = NULL;
}
if (m_hHttpConnection)
{
::InternetCloseHandle(m_hHttpConnection);
m_hHttpConnection = NULL;
}
if (m_hInternetSession)
{
::InternetCloseHandle(m_hInternetSession);
m_hInternetSession = NULL;
}
}
//--------------------------------------------------------------------------------------------
bool CDownloadAnt::init(const wchar_t* wszURLToDownload, const wchar_t* wszFileToDownloadInto,
DOWNANT_STATUS_CALLBACK funcUserCallBack, void* pOwner, unsigned int nOverTime)
{
assert(m_theStateCategory != BUSY);
assert(wszURLToDownload && wszFileToDownloadInto);
StringCchCopyW(m_wszURLToDownload, INTERNET_MAX_URL_LENGTH, wszURLToDownload);
StringCchCopyW(m_wszFileToDownloadInto, MAX_PATH, wszFileToDownloadInto);
debugLog(L"build ant download \"%s\" to \"%s\"", m_wszURLToDownload, m_wszFileToDownloadInto);
m_pOwner = pOwner;
UserCallBack = funcUserCallBack;
if (wcslen(m_wszURLToDownload) > 0 && wcslen(m_wszFileToDownloadInto) > 0)
{
if (!parseURL(m_wszURLToDownload, m_schemeType, m_wszServer, m_wszObject, m_nPort) ||
m_schemeType == INTERNET_SCHEME_UNKNOWN)
{
const wchar_t* wsz = L"Parser URL Error: %s";
StringCchPrintfW(m_wszStatusInfor, MAX_PATH, L"Parser URL Error: %s", m_wszURLToDownload);
setStatus(IDLE_INIT_FAILED);
return FALSE;
}
setStatus(IDLE_INIT_SUCCESS);
return TRUE;
}
else
{
StringCchPrintfW(m_wszStatusInfor, MAX_PATH, L"url and local file address can't be empty!");
setStatus(IDLE_INIT_FAILED);
return FALSE;
}
m_nOverTime = nOverTime;
}
//--------------------------------------------------------------------------------------------
void CDownloadAnt::begin(bool bBlock)
{
assert(m_theState == IDLE_INIT_SUCCESS);
UINT nThreadID;
m_hThread = (HANDLE)::_beginthreadex(NULL, 0, _downloadThread, this, CREATE_SUSPENDED, &nThreadID);
if (m_hThread == NULL)
{
return;
}
setStatus(BUSY_BEGIN_THREAD);
m_beginTime = ::GetTickCount64();
ResumeThread(m_hThread);
if (bBlock)
{
::WaitForSingleObject(m_hThread, INFINITE);
//关闭句柄
CloseHandle(m_hThread); m_hThread = 0;
}
}
//--------------------------------------------------------------------------------------------
UINT CDownloadAnt::_downloadThread(void* pParam)
{
CDownloadAnt* pAnt = (CDownloadAnt*)pParam;
assert(pAnt);
try
{
pAnt->downloadThread();
}
catch (...)
{
}
return 0;
}
//--------------------------------------------------------------------------------------------
void CDownloadAnt::downloadThread(void)
{
assert(m_theState == BUSY_BEGIN_THREAD);
assert(m_hInternetSession == NULL);
assert(m_fpFileToWrite == NULL);
assert(m_hHttpConnection == NULL);
assert(m_hHttpFile == NULL);
_wfopen_s(&m_fpFileToWrite, m_wszFileToDownloadInto, L"wb");
if (m_fpFileToWrite == NULL)
{
StringCchPrintfW(m_wszStatusInfor, MAX_PATH, L"Create local file error: %s, Error:%d", m_wszFileToDownloadInto, ::GetLastError());
setStatus(FINISH_ERROR_TRY_OPEN_LOCALFILE);
return;
}
//打开Internet句柄
m_hInternetSession = InternetOpenW(NULL, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (m_hInternetSession == NULL)
{
StringCchPrintfW(m_wszStatusInfor, MAX_PATH, L"(::InternetOpen) Error:%d\n", ::GetLastError());
setStatus(FINISH_ERROR_INTERNET_OPEN);
return;
}
if (_checkAbort()) return;
DWORD dwTemp = 0, dwSize = sizeof(DWORD);
BOOL bRet = InternetQueryOptionW(m_hInternetSession, INTERNET_OPTION_MAX_CONNS_PER_SERVER, &dwTemp, &dwSize);
DWORD dwErr = GetLastError();
//设置状态回调函数
if (::InternetSetStatusCallbackW(m_hInternetSession, _onWininetStatusCallBack) == INTERNET_INVALID_STATUS_CALLBACK)
{
StringCchPrintfW(m_wszStatusInfor, MAX_PATH, L"(::InternetSetStatusCallback), Error:%d\n", ::GetLastError());
setStatus(FINISH_ERROR_SETSTATUSCALLBACK);
return;
}
if (_checkAbort()) return;
DWORD serviceType = 0;
if(m_schemeType == INTERNET_SCHEME_HTTP||m_schemeType== INTERNET_SCHEME_HTTPS)
{
serviceType = INTERNET_SERVICE_HTTP;
}
else if (m_schemeType == INTERNET_SCHEME_FTP)
{
serviceType = INTERNET_SERVICE_FTP;
}
else
{
StringCchPrintfW(m_wszStatusInfor, MAX_PATH, L"Unsupported scheme type: %d", m_schemeType);
setStatus(FINISH_ERROR_UNSUPPORTED_SCHEME);
return;
}
//打开Internet连接
m_hHttpConnection = InternetConnectW(m_hInternetSession, m_wszServer, m_nPort, NULL,
NULL, serviceType, 0, (DWORD_PTR)this);
if (m_hHttpConnection == NULL)
{
StringCchPrintfW(m_wszStatusInfor, MAX_PATH, L"(::InternetConnect), Error:%d\n", ::GetLastError());
setStatus(FINISH_ERROR_INTERNETCONNECT);
return;
}
if (_checkAbort()) return;
//生成Request
const wchar_t* ppszAcceptTypes[2];
ppszAcceptTypes[0] = L"*/*";
ppszAcceptTypes[1] = NULL;
DWORD dwFlags = INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES;
if(serviceType==INTERNET_SERVICE_HTTP && m_schemeType == INTERNET_SCHEME_HTTPS)
{
dwFlags |= INTERNET_FLAG_SECURE;
}
m_hHttpFile = HttpOpenRequestW(m_hHttpConnection,
L"GET", m_wszObject, NULL, NULL, ppszAcceptTypes,
dwFlags,
(DWORD_PTR)this);
if (m_hHttpFile == NULL)
{
StringCchPrintfW(m_wszStatusInfor, MAX_PATH, L"(::HttpOpenRequest), Error:%d\n", ::GetLastError());
setStatus(FINISH_ERROR_OPENREQUEST);
return;
}
if (_checkAbort()) return;
setStatus(BUSY_OPEN_REQUEST);
//发送Request
BOOL bSend = HttpSendRequestW(m_hHttpFile, NULL, 0, NULL, 0);
if (!bSend)
{
StringCchPrintfW(m_wszStatusInfor, MAX_PATH, L"(::HttpSendRequest), Error:%d\n", ::GetLastError());
setStatus(FINISH_ERROR_SENDREQUEST);
return;
}
if (_checkAbort()) return;
//查询状态
DWORD dwInfoSize = 64;
CHAR szStatusCode[64] = { 0 };
if (!::HttpQueryInfoW(m_hHttpFile, HTTP_QUERY_STATUS_CODE, szStatusCode, &dwInfoSize, NULL))
{
StringCchPrintfW(m_wszStatusInfor, MAX_PATH, L"Failed in call to HttpQueryInfo for HTTP query status code, Error:%d\n", ::GetLastError());
setStatus(FINISH_ERROR_QUERYINFO);
return;
}
else
{
long nStatusCode = atoi(szStatusCode);
if (nStatusCode != HTTP_STATUS_OK)
{
m_theState = FINISH_ERROR_INVALID_HTTP_RESPONSE;
if (nStatusCode == HTTP_STATUS_PROXY_AUTH_REQ)
{
StringCchPrintfW(m_wszStatusInfor, MAX_PATH, L"Authentication errors, Status Code:%d\n", nStatusCode);
setStatus(FINISH_ERROR_NEED_PROXY_AUTH_REQ);
return;
}
else if (nStatusCode == HTTP_STATUS_DENIED)
{
StringCchPrintfW(m_wszStatusInfor, MAX_PATH, L"Authentication errors, Status Code:%d\n", nStatusCode);
setStatus(FINISH_ERROR_DENIED);
return;
}
}
}
if (_checkAbort()) return;
// 取得文件大小
dwInfoSize = 64;
wchar_t wszContentLength[64] = { 0 };
if (HttpQueryInfoW(m_hHttpFile, HTTP_QUERY_CONTENT_LENGTH, wszContentLength, &dwInfoSize, NULL))
{
m_bGotFileSize = TRUE;
m_dwFileSizeTotal = _wtoi64(wszContentLength);
StringCchPrintfW(m_wszStatusInfor, MAX_PATH, L"FileSize: %I64d", m_dwFileSizeTotal);
}
debugLog(L"Get file size %I64d, begin download...", m_dwFileSizeTotal);
//得到文件大小,让Manager处理位置
setStatus(BUSY_GET_FILE_INFORMATION);
if (_checkAbort()) return;
//开始下载
ULONGLONG startTicks = ::GetTickCount64();
DWORD dwBytesRead = 0;
char szReadBuf[DOWNANT_MINSIZE] = { 0 };
DWORD dwBytesToRead = DOWNANT_MINSIZE;
m_dwFileSizeRead = 0;
MD5Context md5Ctx;
md5Init(&md5Ctx);
setStatus(BUSY_BEGIN_DOWNFILE);
do
{
if (!InternetReadFile(m_hHttpFile, szReadBuf, dwBytesToRead, &dwBytesRead))
{
StringCchPrintfW(m_wszStatusInfor, MAX_PATH, L"(::InternetReadFile), Error:%d\n", ::GetLastError());
setStatus(FINISH_ERROR_READFILE);
return;
}
else if (dwBytesRead && m_dwUserCmd == MANAGER_CMD_CONTINUE)
{
try
{
if (dwBytesRead != ::fwrite(szReadBuf, 1, dwBytesRead, m_fpFileToWrite))
{
throw FINISH_ERROR_EXCEPTION;
}
md5Update(&md5Ctx, (unsigned char*)szReadBuf, dwBytesRead);
}
catch (...)
{
StringCchPrintfW(m_wszStatusInfor, MAX_PATH, L"write local file error!, Error:%d", ::GetLastError());
setStatus(FINISH_ERROR_EXCEPTION);
return;
}
m_dwFileSizeRead += dwBytesRead;
onStatusCallback();
}
if (_checkAbort())
{
if (m_fpFileToWrite)::fclose(m_fpFileToWrite);
m_fpFileToWrite = NULL;
return;
}
} while (dwBytesRead && m_dwUserCmd == MANAGER_CMD_CONTINUE);
//生成MD5
m_fileMD5 = md5Finalize(&md5Ctx);
if (m_fpFileToWrite)::fclose(m_fpFileToWrite);
m_fpFileToWrite = NULL;
debugLog(L"Download success over!");
setStatus(FINISH_SUCCESS_OVER);
return;
}
//--------------------------------------------------------------------------------------------
bool CDownloadAnt::checkMD5(const char* md5_string) const
{
MD5Digest md5;
if (!string2MD5(md5_string, md5)) return false;
return (0 == memcmp(&md5, &m_fileMD5, md5.size()));
}
//--------------------------------------------------------------------------------------------
void CALLBACK CDownloadAnt::_onWininetStatusCallBack(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus,
LPVOID lpvStatusInformation, DWORD dwStatusInformationLength)
{
CDownloadAnt* pDownMan = (CDownloadAnt*)dwContext;
assert(pDownMan);
pDownMan->onWininetStatusCallBack(hInternet, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength);
}
//--------------------------------------------------------------------------------------------
void CDownloadAnt::onWininetStatusCallBack(HINTERNET /*hInternet*/, DWORD dwInternetStatus,
LPVOID lpvStatusInformation, DWORD /*dwStatusInformationLength*/)
{
switch (dwInternetStatus)
{
case INTERNET_STATUS_RESOLVING_NAME:
{
StringCchCopyW(m_wszStatusInfor, MAX_PATH, (LPCWSTR)lpvStatusInformation);
setStatus(BUSY_RESOLVING_NAME);
break;
}
case INTERNET_STATUS_NAME_RESOLVED:
{
StringCchCopyW(m_wszStatusInfor, MAX_PATH, (LPCWSTR)lpvStatusInformation);
setStatus(BUSY_RESOLVED_NAME);
break;
}
case INTERNET_STATUS_CONNECTING_TO_SERVER:
{
StringCchCopyW(m_wszStatusInfor, MAX_PATH, (LPCWSTR)lpvStatusInformation);
setStatus(BUSY_CONNECTING);
break;
}
case INTERNET_STATUS_CONNECTED_TO_SERVER:
{
StringCchCopyW(m_wszStatusInfor, MAX_PATH, (LPCWSTR)lpvStatusInformation);
setStatus(BUSY_CONNECTED);
break;
}
case INTERNET_STATUS_REDIRECT:
{
StringCchCopyW(m_wszStatusInfor, MAX_PATH, (LPCWSTR)lpvStatusInformation);
setStatus(BUSY_REDIRECTING);
break;
}
default: break;
}
}
//--------------------------------------------------------------------------------------------
void CDownloadAnt::setStatus(CDownloadAnt::DOWN_STATE newState)
{
m_theState = newState;
onStatusCallback();
}
//--------------------------------------------------------------------------------------------
void CDownloadAnt::onStatusCallback(void)
{
switch (m_theState)
{
case IDLE_NOTINIT:
case IDLE_INIT_SUCCESS:
case IDLE_INIT_FAILED:
{
m_theStateCategory = IDLE;
}
break;
case BUSY_BEGIN_THREAD:
case BUSY_OPEN_REQUEST:
case BUSY_RESOLVING_NAME:
case BUSY_RESOLVED_NAME:
case BUSY_CONNECTING:
case BUSY_CONNECTED:
case BUSY_REDIRECTING:
case BUSY_GET_FILE_INFORMATION:
case BUSY_BEGIN_DOWNFILE:
{
m_theStateCategory = BUSY;
}
break;
case FINISH_SUCCESS_OVER:
case FINISH_SUCCESS_STOPHERE:
{
m_theStateCategory = FINISH_SUCCESS;
}
break;
case FINISH_FORCEABORT:
case FINISH_ERROR_TIMEOUT:
case FINISH_ERROR_TRY_OPEN_LOCALFILE:
case FINISH_ERROR_INTERNET_OPEN:
case FINISH_ERROR_SETSTATUSCALLBACK:
case FINISH_ERROR_INTERNETCONNECT:
case FINISH_ERROR_OPENREQUEST:
case FINISH_ERROR_SENDREQUEST:
case FINISH_ERROR_QUERYINFO:
case FINISH_ERROR_NEED_PROXY_AUTH_REQ:
case FINISH_ERROR_DENIED:
case FINISH_ERROR_INVALID_HTTP_RESPONSE:
case FINISH_ERROR_READFILE:
case FINISH_ERROR_EXCEPTION:
{
m_theStateCategory = FINISH_NOT_SUCCESS;
}
break;
default:
assert(false);
break;
}
if (m_theStateCategory == FINISH_SUCCESS || m_theStateCategory == FINISH_NOT_SUCCESS)
{
if (m_fpFileToWrite)
{
::fclose(m_fpFileToWrite);
m_fpFileToWrite = NULL;
}
if (m_hHttpFile)
{
::InternetCloseHandle(m_hHttpFile);
m_hHttpFile = NULL;
}
if (m_hHttpConnection)
{
::InternetCloseHandle(m_hHttpConnection);
m_hHttpConnection = NULL;
}
if (m_hInternetSession)
{
::InternetCloseHandle(m_hInternetSession);
m_hInternetSession = NULL;
}
}
if (UserCallBack)
{
m_dwUserCmd = UserCallBack(m_pOwner, this);
}
}
//--------------------------------------------------------------------------------------------
BOOL CDownloadAnt::_checkAbort(void)
{
HANDLE hCloseMutex = OpenMutexW(SYNCHRONIZE, FALSE, L"Global\\8e931dcb-868e-4ba6-aff4-d1a22bc9d476");
if (hCloseMutex != 0)
{
CloseHandle(hCloseMutex);
debugLog(L"[ANT]Receive autoclose signal, quit!");
return TRUE;
}
if (MANAGER_CMD_FORCEABORT == m_dwUserCmd)
{
setStatus(FINISH_FORCEABORT);
return TRUE;
}
if (MANAGER_CMD_STOPHERE == m_dwUserCmd)
{
setStatus(FINISH_SUCCESS_STOPHERE);
return TRUE;
}
if (m_nOverTime != INFINITE && m_theStateCategory == BUSY)
{
ULONGLONG timeNow = ::GetTickCount64();
ULONGLONG timeRun;
if (timeNow >= m_beginTime) timeRun = timeNow - m_beginTime;
else timeRun = timeNow + (0XFFFFFFFFFFFFFFFFULL - m_beginTime);
if (timeRun > m_nOverTime * 1000ULL)
{
setStatus(FINISH_ERROR_TIMEOUT);
return TRUE;
}
}
return FALSE;
}
BOOL MyAfxIsValidAddress(const void* lp, UINT nBytes, BOOL bReadWrite /* = TRUE */)
{
// simple version using Win-32 APIs for pointer validation.
return (lp != NULL && !IsBadReadPtr(lp, nBytes) &&
(!bReadWrite || !IsBadWritePtr((LPVOID)lp, nBytes)));
}
bool _AfxParseURLWorker(const wchar_t* pstrURL, LPURL_COMPONENTSW lpComponents,
INTERNET_SCHEME& schemeType, INTERNET_PORT& nPort, DWORD dwFlags)
{
// this function will return bogus stuff if lpComponents
// isn't set up to copy the components
assert(lpComponents != NULL && pstrURL != NULL);
if (lpComponents == NULL || pstrURL == NULL)
return FALSE;
assert(lpComponents->dwHostNameLength == 0 ||
lpComponents->lpszHostName != NULL);
assert(lpComponents->dwUrlPathLength == 0 ||
lpComponents->lpszUrlPath != NULL);
assert(lpComponents->dwUserNameLength == 0 ||
lpComponents->lpszUserName != NULL);
assert(lpComponents->dwPasswordLength == 0 ||
lpComponents->lpszPassword != NULL);
assert(MyAfxIsValidAddress(lpComponents, sizeof(URL_COMPONENTS), TRUE));
wchar_t* pstrCanonicalizedURL;
wchar_t szCanonicalizedURL[INTERNET_MAX_URL_LENGTH];
DWORD dwNeededLength = INTERNET_MAX_URL_LENGTH;
BOOL bRetVal;
BOOL bMustFree = FALSE;
DWORD dwCanonicalizeFlags = dwFlags &
(ICU_NO_ENCODE | ICU_DECODE | ICU_NO_META |
ICU_ENCODE_SPACES_ONLY | ICU_BROWSER_MODE);
DWORD dwCrackFlags = dwFlags & (ICU_ESCAPE | ICU_USERNAME);
bRetVal = InternetCanonicalizeUrlW(pstrURL, szCanonicalizedURL,
&dwNeededLength, dwCanonicalizeFlags);
if (!bRetVal)
{
if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER)
return FALSE;
pstrCanonicalizedURL = new wchar_t[dwNeededLength];
bMustFree = TRUE;
bRetVal = InternetCanonicalizeUrlW(pstrURL, pstrCanonicalizedURL,
&dwNeededLength, dwCanonicalizeFlags);
if (!bRetVal)
{
delete[] pstrCanonicalizedURL;
return FALSE;
}
}
else
pstrCanonicalizedURL = szCanonicalizedURL;
// now that it's safely canonicalized, crack it
bRetVal = InternetCrackUrlW(pstrCanonicalizedURL, 0, dwCrackFlags, lpComponents);
if (bMustFree)
delete[] pstrCanonicalizedURL;
if (bRetVal)
{
nPort = lpComponents->nPort;
schemeType = lpComponents->nScheme;
}
else
{
schemeType = INTERNET_SCHEME_UNKNOWN;
}
return bRetVal == TRUE;
}
bool parseURL(const wchar_t* pstrURL, INTERNET_SCHEME& schemeType, wchar_t* strServer, wchar_t* strObject, INTERNET_PORT& nPort)
{
schemeType = INTERNET_SCHEME_UNKNOWN;
assert(pstrURL != NULL);
if (pstrURL == NULL)
return FALSE;
URL_COMPONENTSW urlComponents;
memset(&urlComponents, 0, sizeof(URL_COMPONENTS));
urlComponents.dwStructSize = sizeof(URL_COMPONENTS);
urlComponents.dwHostNameLength = MAX_PATH;
urlComponents.lpszHostName = strServer;
urlComponents.dwUrlPathLength = INTERNET_MAX_URL_LENGTH;
urlComponents.lpszUrlPath = strObject;
return _AfxParseURLWorker(pstrURL, &urlComponents, schemeType, nPort, ICU_BROWSER_MODE);
}