#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); }