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) {

http://wald.intevation.org/projects/trustbridge/