Mercurial > trustbridge
diff ui/downloader_win.cpp @ 15:95e1b6edf2fc
Implement more downloader functionality for Windows
author | Andre Heinecke <aheinecke@intevation.de> |
---|---|
date | Wed, 19 Feb 2014 10:45:06 +0000 |
parents | 7e2f14c7aba2 |
children | c12825a651ed |
line wrap: on
line diff
--- a/ui/downloader_win.cpp Wed Feb 19 10:44:40 2014 +0000 +++ b/ui/downloader_win.cpp Wed Feb 19 10:45:06 2014 +0000 @@ -19,9 +19,18 @@ #include <winhttp.h> #include <QDebug> +#include <QDateTime> +#include <QSaveFile> #define DEBUG if (1) qDebug() << __PRETTY_FUNCTION__ +#define MAX_SW_SIZE 10485760 +#define MAX_LIST_SIZE 1048576 + +/** @brief Qt wrapper around FormatMessage + * + * @returns The error message of the error that occurred + */ const QString getLastErrorMsg() { LPWSTR bufPtr = NULL; DWORD err = GetLastError(); @@ -29,6 +38,16 @@ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, 0, (LPWSTR)&bufPtr, 0, NULL); + if (!bufPtr) { + HMODULE hWinhttp = GetModuleHandleW(L"winhttp"); + if (hWinhttp) { + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_HMODULE | + FORMAT_MESSAGE_IGNORE_INSERTS, + hWinhttp, HRESULT_CODE(err), 0, + (LPWSTR)&bufPtr, 0, NULL); + } + } const QString result = (bufPtr) ? QString::fromUtf16((const ushort*)bufPtr).trimmed() : QString("Unknown Error %1").arg(err); @@ -36,7 +55,8 @@ return result; } -/** @brief open a session with appropiate proxy settings + +/** @brief open a session with appropriate proxy settings * * @param[inout] *pHSession pointer to a HInternet structure * @@ -56,7 +76,6 @@ return false; } - qDebug() << "2"; memset(&proxyConfig, 0, sizeof (WINHTTP_CURRENT_USER_IE_PROXY_CONFIG)); if (WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig)) { @@ -144,6 +163,7 @@ bool createRequest(HINTERNET hSession, HINTERNET hConnect, HINTERNET *pHRequest, LPCWSTR requestType, LPCWSTR resource) { + DWORD dwSSLFlag; DEBUG; if (!hSession || !hConnect || !pHRequest) { SetLastError(ERROR_INVALID_PARAMETER); @@ -154,101 +174,342 @@ NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE); + + dwSSLFlag = SECURITY_FLAG_IGNORE_UNKNOWN_CA; + dwSSLFlag |= SECURITY_FLAG_IGNORE_CERT_DATE_INVALID; + dwSSLFlag |= SECURITY_FLAG_IGNORE_CERT_CN_INVALID; + dwSSLFlag |= SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE; + + WinHttpSetOption(*pHRequest, WINHTTP_OPTION_SECURITY_FLAGS, + &dwSSLFlag, sizeof(dwSSLFlag)); + return *pHRequest; } -void Downloader::run() { - BOOL bResults = FALSE; - HINTERNET hSession = NULL, - hConnect = NULL, - hRequest = NULL; +bool Downloader::verifyCertificate(HINTERNET hRequest) +{ + CERT_CONTEXT *certContext = NULL; + DWORD certContextLen = sizeof(CERT_CONTEXT); + bool retval = false; - SYSTEMTIME lastModified; - DWORD sizeOfSystemtime = sizeof (SYSTEMTIME); - - memset(&lastModified, 0, sizeof (SYSTEMTIME)); - - if (!openSession(&hSession)) { - DEBUG << "Failed to open session: " << getLastErrorMsg(); - return; + if (!WinHttpQueryOption(hRequest, + WINHTTP_OPTION_SERVER_CERT_CONTEXT, + &certContext, + &certContextLen)) { + DEBUG << "Unable to get server certificate"; + return false; } - if (!initializeConnection(hSession, &hConnect, L"www.intevation.de")) { - DEBUG << "Failed to initialize connection: " << getLastErrorMsg(); + QByteArray serverCert ((const char *) certContext->pbCertEncoded, + certContext->cbCertEncoded); + + retval = (serverCert == mCert); + + if (!retval) { + DEBUG << "Certificate is not the same as the pinned one!" + << "Base64 cert: " << serverCert.toBase64(); + } + + CertFreeCertificateContext(certContext); + return retval; +} + +QDateTime Downloader::getLastModifiedHeader(HINTERNET hSession, + HINTERNET hConnect, LPCWSTR resource) +{ + HINTERNET hRequest = NULL; + SYSTEMTIME lMod; + DWORD sizeOfSystemtime = sizeof (SYSTEMTIME); + QDateTime retval; + DWORD err = 0; + + memset(&lMod, 0, sizeof (SYSTEMTIME)); + + if (!hSession || !hConnect || !resource) { + SetLastError(ERROR_INVALID_PARAMETER); + return retval; + } + + if (!createRequest(hSession, hConnect, &hRequest, L"HEAD", + resource)) { + err = GetLastError(); goto cleanup; } - if (!createRequest(hSession, hConnect, &hRequest, L"GET", L"/")) { - DEBUG << "Failed to create the request: " << getLastErrorMsg(); + if (!WinHttpSendRequest(hRequest, + WINHTTP_NO_ADDITIONAL_HEADERS, + 0, WINHTTP_NO_REQUEST_DATA, 0, + 0, 0)) { + err = GetLastError(); goto cleanup; } - if (hRequest) { - DEBUG << "Doing Request"; - bResults = WinHttpSendRequest(hRequest, - WINHTTP_NO_ADDITIONAL_HEADERS, - 0, WINHTTP_NO_REQUEST_DATA, 0, - 0, 0); - } else { - DEBUG << "Error: " << GetLastError(); - } - - if (bResults) { - DEBUG << "Recieving Response"; - bResults = WinHttpReceiveResponse(hRequest, NULL); - } else { - DEBUG << "Error: " << GetLastError(); + if (!WinHttpReceiveResponse(hRequest, NULL)) { + err = GetLastError(); + goto cleanup; } - if (bResults) { - DEBUG << "Querying Headers"; - bResults = WinHttpQueryHeaders(hRequest, - WINHTTP_QUERY_LAST_MODIFIED | - WINHTTP_QUERY_FLAG_SYSTEMTIME, - NULL, - &lastModified, - &sizeOfSystemtime, - WINHTTP_NO_HEADER_INDEX); - } else { - DWORD errCode = GetLastError(); - switch (errCode) { - case ERROR_WINHTTP_HEADER_NOT_FOUND: - DEBUG << "Header not found"; - break; - case ERROR_WINHTTP_INCORRECT_HANDLE_STATE: - DEBUG << "Incorrect handle state"; - break; - case ERROR_WINHTTP_INCORRECT_HANDLE_TYPE: - DEBUG << "Incorrect handle type"; - break; - case ERROR_WINHTTP_INTERNAL_ERROR: - DEBUG << "Internal error"; - break; - case ERROR_NOT_ENOUGH_MEMORY: - DEBUG << "OOM"; - break; - default: - DEBUG << "Error: " << getLastErrorMsg(); - } + if (!verifyCertificate(hRequest)) { + DEBUG << "Certificate verification failed"; + // TODO error out } - DEBUG << "Last modified year: " << lastModified.wYear; - + if (!(WinHttpQueryHeaders(hRequest, + WINHTTP_QUERY_LAST_MODIFIED | + WINHTTP_QUERY_FLAG_SYSTEMTIME, + NULL, + &lMod, + &sizeOfSystemtime, + WINHTTP_NO_HEADER_INDEX))) { + err = GetLastError(); + goto cleanup; + } - if (!bResults) { - // Report any errors. - DEBUG << "Error" << GetLastError(); - emit error(tr("Unknown Problem when connecting"), Unknown); - } + retval = QDateTime(QDate(lMod.wYear, lMod.wMonth, lMod.wDay), + QTime(lMod.wHour, lMod.wMinute, lMod.wSecond, + lMod.wMilliseconds), + Qt::UTC); cleanup: if (hRequest) { WinHttpCloseHandle(hRequest); } + // Close handle might overwrite the last error. + SetLastError(err); + return retval; +} + +bool Downloader::downloadFile(HINTERNET hSession, HINTERNET hConnect, + LPCWSTR resource, const QString &filename, DWORD maxSize) +{ + HINTERNET hRequest = NULL; + bool retval = false; + DWORD bytesAvailable = 0, + err = 0, + bytesRead = 0, + totalDownloaded = 0; + + QSaveFile outputFile(filename); + + if (!hSession || !hConnect || !resource) { + SetLastError(ERROR_INVALID_PARAMETER); + return retval; + } + + if (!createRequest(hSession, hConnect, &hRequest, L"GET", + resource)) { + err = GetLastError(); + goto cleanup; + } + + if (!WinHttpSendRequest(hRequest, + WINHTTP_NO_ADDITIONAL_HEADERS, + 0, WINHTTP_NO_REQUEST_DATA, 0, + 0, 0)) { + err = GetLastError(); + goto cleanup; + } + + + if (!WinHttpReceiveResponse(hRequest, NULL)) { + err = GetLastError(); + goto cleanup; + } + + if (!verifyCertificate(hRequest)) { + DEBUG << "Certificate verification failed"; + // TODO error out + } + + // Open / Create the file to write to. + if (!outputFile.open(QIODevice::WriteOnly)) { + DEBUG << "Failed to open file"; + err = GetLastError(); + goto cleanup; + } + + do + { + char outBuf[8192]; // 8KB is the internal buffer size of winhttp + memset(outBuf, 0, sizeof(outBuf)); + bytesRead = 0; + + if (!WinHttpQueryDataAvailable(hRequest, &bytesAvailable)) { + DEBUG << "Querying for available data failed"; + retval = false; + err = GetLastError(); + break; + } + + if (!bytesAvailable) { + // Might indicate that we are done. + break; + } + + if (bytesAvailable > maxSize) { + DEBUG << "File to large"; + retval = false; + break; + } + + if (!WinHttpReadData(hRequest, (LPVOID)outBuf, + sizeof(outBuf), &bytesRead)) { + DEBUG << "Error reading data"; + err = GetLastError(); + break; + } else { + if (bytesRead) { + DEBUG << "Downloaded: " << bytesRead << "B"; + + // Write data to file. + if (outputFile.write(outBuf, bytesRead) != + bytesRead) { + err = GetLastError(); + DEBUG << "Error writing to file."; + retval = false; + } + // Completed a read / write cycle. If not error follows + // the download was successful. + retval = true; + } else { + // Should not happen as we queried for available + // bytes before and the function did not return an + // error. + DEBUG << "Unable to read available data"; + retval = false; + break; + } + } + totalDownloaded += bytesRead; + + if (totalDownloaded > maxSize) { + DEBUG << "Downloaded too much data. Breaking."; + retval = false; + break; + } + } while (bytesAvailable > 0); + +cleanup: + + if (retval) { + // Actually save the file to disk / move to homedir + retval = outputFile.commit(); + } + + if (hRequest) { + WinHttpCloseHandle(hRequest); + } + + // Close handle might overwrite the last error. + SetLastError(err); + return retval; +} + +void Downloader::run() { + bool results = false; + HINTERNET hSession = NULL, + hConnect = NULL; + wchar_t wUrl[mUrl.size() + 1]; + QDateTime lastModifiedSoftware; + QDateTime lastModifiedList; + + int rc = 0; + + memset(wUrl, 0, sizeof (wchar_t) * (mUrl.size() + 1)); + + rc = mUrl.toWCharArray(wUrl); + + if (rc != mUrl.size()) { + DEBUG << "Problem converting to wchar array"; + return; + } + + // Should not be necessary because we initialized the memory + wUrl[rc] = '\0'; + + // Initialize connection + if (!openSession(&hSession)) { + DEBUG << "Failed to open session: " << getLastErrorMsg(); + return; + } + if (!initializeConnection(hSession, &hConnect, wUrl)) { + DEBUG << "Failed to initialize connection: " << getLastErrorMsg(); + goto cleanup; + } + + + lastModifiedSoftware = getLastModifiedHeader(hSession, hConnect, + L"/incoming/aheinecke/test"); + + lastModifiedList = getLastModifiedHeader(hSession, hConnect, + L"/incoming/aheinecke/test"); + + if (!lastModifiedList.isValid() || !lastModifiedSoftware.isValid()) { + DEBUG << "Could not read headers: " << getLastErrorMsg(); + goto cleanup; + } + + if (!mLastModSW.isValid() || lastModifiedSoftware > mLastModSW) { + QString dataDirectory = getDataDirectory(); + + if (dataDirectory.isEmpty()) { + DEBUG << "Failed to get data directory"; + goto cleanup; + } + + QString fileName = dataDirectory.append("/SW-") + .append(lastModifiedSoftware.toString("yyyymmddHHmmss")) + .append(".exe"); + + DEBUG << "Filename: " << fileName; + + if (!downloadFile(hSession, hConnect, L"/incoming/aheinecke/test", + fileName, MAX_SW_SIZE)) { + DEBUG << "Error downloading File: " << getLastErrorMsg(); + goto cleanup; + } + + emit newSoftwareAvailable(fileName, lastModifiedSoftware); + } else if (!mLastModList.isValid() || lastModifiedList > mLastModList) { + QString dataDirectory = getDataDirectory(); + + if (dataDirectory.isEmpty()) { + DEBUG << "Failed to get data directory"; + goto cleanup; + } + + QString fileName = dataDirectory.append("/list-") + .append(lastModifiedSoftware.toString("yyyymmddHHmmss")) + .append(".txt"); + + DEBUG << "Filename: " << fileName; + + if (!downloadFile(hSession, hConnect, L"/incoming/aheinecke/test", + fileName, MAX_LIST_SIZE)) { + DEBUG << "Error downloading File: " << getLastErrorMsg(); + goto cleanup; + } + + emit newListAvailable(fileName, lastModifiedList); + } + + DEBUG << "SW date: " << lastModifiedSoftware; + DEBUG << "List date: " << lastModifiedList; + + /*if (!WinHttpQueryDataAvailable(hRequest, &dataAvaiable)) { + DEBUG << "Failed to query data Available: " << getLastErrorMsg(); + goto cleanup; + }*/ + + if (!results) { + // Report any errors. + DEBUG << "Error" << GetLastError(); + emit error(tr("Unknown Problem when connecting"), ErrUnknown); + } +cleanup: if (hConnect) { WinHttpCloseHandle(hConnect); - } if (hSession) {