changeset 32:d8e93fa1fc93

Downloader logic
author Andre Heinecke <andre.heinecke@intevation.de>
date Wed, 12 Mar 2014 21:26:07 +0100
parents 37fc66967517
children 25c08d63d2b7
files ui/downloader.cpp ui/downloader.h
diffstat 2 files changed, 279 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/ui/downloader.cpp	Thu Mar 13 18:12:16 2014 +0000
+++ b/ui/downloader.cpp	Wed Mar 12 21:26:07 2014 +0100
@@ -10,6 +10,9 @@
 #include <QStandardPaths>
 #include <QUuid>
 #include <QApplication>
+#include <QTextStream>
+#include <QLocale>
+#include <QSaveFile>
 
 #include <polarssl/net.h>
 #include <polarssl/ssl.h>
@@ -20,6 +23,19 @@
 
 #define MAX_SW_SIZE 10485760
 #define MAX_LIST_SIZE 1048576
+#define MAX_IO_TRIES 10
+
+#define LIST_RESOURCE "/incoming/aheinecke/test"
+#define SW_RESOURCE "/incoming/aheinecke/test"
+
+
+#ifdef CONNECTION_DEBUG
+static void my_debug(void *ctx, int level, const char *str)
+{
+    fprintf((FILE *) ctx, "%s", str);
+    fflush((FILE *) ctx);
+}
+#endif
 
 QString getErrorMsg(int ret)
 {
@@ -108,6 +124,13 @@
     ssl_set_ca_chain(&mSSL, &mX509PinnedCert, NULL, NULL);
     ssl_set_renegotiation(&mSSL, SSL_RENEGOTIATION_DISABLED);
     ssl_set_rng(&mSSL, ctr_drbg_random, &mCtr_drbg);
+#ifdef RELEASE_BUILD
+    ssl_set_min_version(&mSSL, SSL_MAJOR_VERSION_3, SSL_MINOR_VERSION_3);
+#endif
+
+#ifdef CONNECTION_DEBUG
+    ssl_set_dbg(&mSSL, my_debug, stdout);
+#endif
 
     return 0;
 }
@@ -145,9 +168,8 @@
     int ret = -1;
     const x509_crt *peerCert;
 
-    mErrorState = ErrUnknown;
-
-    if (mServerFD == -1) {
+    if (mServerFD == -1 || !mInitialized) {
+        mErrorState = ErrUnknown;
         return -1;
     }
 
@@ -159,27 +181,29 @@
                 ret != POLARSSL_ERR_NET_WANT_WRITE) {
             qDebug() << "SSL Handshake failed: "
                  << getErrorMsg(ret);
+            mErrorState = SSLHandshakeFailed;
             return ret;
         }
     }
 
+    /* we might want to set the verify function
+     * with ssl_set_verify before to archive the
+     * certificate pinning. */
+
     ret = ssl_get_verify_result(&mSSL);
 
     if (ret != 0 ) {
-
-        if( ( ret & BADCERT_EXPIRED ) != 0 )
-            qDebug() << "server certificate has expired";
-
-        if( ( ret & BADCERT_REVOKED ) != 0 )
-            qDebug() << "server certificate has been revoked";
-
-        if( ( ret & BADCERT_CN_MISMATCH ) != 0 )
-            qDebug() << "CN mismatch";
-
-        if( ( ret & BADCERT_NOT_TRUSTED ) != 0 )
+        if((ret & BADCERT_EXPIRED) != 0)
+           qDebug() << "server certificate has expired";
+        if((ret & BADCERT_REVOKED) != 0)
+           qDebug() << "server certificate has been revoked";
+        if((ret & BADCERT_CN_MISMATCH) != 0)
+           qDebug() << "CN mismatch";
+        if((ret & BADCERT_NOT_TRUSTED) != 0)
             qDebug() << "self-signed or not signed by a trusted CA";
-
+        ret = -1;
 #ifdef RELEASE_BUILD
+        mErrorState = InvalidCertificate;
         return -1;
 #endif
     }
@@ -214,13 +238,169 @@
             return -1;
         }
     }
-    mErrorState = NoError;
     return 0;
 }
 
+/* Helper around polarssl bare bone api */
+int polarSSLWrite (ssl_context *ssl, const QByteArray& request)
+{
+    unsigned int tries = 0;
+    int ret = -1;
+
+    const unsigned char *buf = (const unsigned char *) request.constData();
+    size_t len = (size_t) request.size();
+
+    qDebug() << "Seinding request: " << request;
+    /* According to doc for ssl_write:
+     *
+     * When this function returns POLARSSL_ERR_NET_WANT_WRITE,
+     * it must be called later with the same arguments,
+     * until it returns a positive value.
+     */
+    do {
+        ret = ssl_write(ssl, buf, len);
+        if (ret >= 0) {
+            if ((unsigned int) ret == len) {
+                return 0;
+            } else {
+                qDebug() << "Write failed to write everything";
+                return -1;
+            }
+        }
+
+        if (ret != POLARSSL_ERR_NET_WANT_WRITE) {
+            return ret;
+        }
+        tries++;
+        net_usleep(100000); /* sleep 100ms to give the socket a chance
+                               to clean up. */
+    } while (tries < MAX_IO_TRIES);
+
+    return ret;
+}
+
+/* Helper around polarssl bare bone api read at most len bytes
+ * and return them as a byte array returns a NULL byte array on error*/
+QByteArray polarSSLRead (ssl_context *ssl, size_t len)
+{
+    unsigned char buf[len];
+    QByteArray retval("");
+    int ret = -1;
+
+    do {
+        memset (buf, 0, sizeof(buf));
+        ret = ssl_read(ssl, buf, len);
+        if (ret == 0 ||
+            ret == POLARSSL_ERR_SSL_CONN_EOF) {
+            /* EOF */
+            return retval;
+        }
+        if (ret <= 0) {
+            qDebug() << "Read failed: " << getErrorMsg(ret);
+            return QByteArray();
+        }
+        if (len < (len - (unsigned int) ret)) {
+            /* Should never happen if ssl_read behaves */
+            qDebug() << "integer overflow in polarSSLRead";
+            return QByteArray();
+        }
+        len -= (unsigned int) ret;
+        retval.append((const char *)buf, len);
+    } while (len > 0);
+
+    return retval;
+}
+
+
+QDateTime Downloader::getLastModifiedHeader(const QString &resource) {
+    int ret = -1;
+    QByteArray response;
+    QTextStream responseStream(&response);
+    QLocale cLocale = QLocale::c();
+    QString headRequest =
+        QString::fromLatin1("HEAD %1 HTTP/1.1\r\n"
+                "Connection: Keep-Alive\r\n"
+                "\r\n\r\n").arg(resource);
+
+    ret = polarSSLWrite (&mSSL, headRequest.toUtf8());
+    if (ret != 0) {
+        mErrorState = ConnectionLost;
+        return QDateTime();
+    }
+
+    response = polarSSLRead(&mSSL, 1024);
+
+    if (response.isNull()) {
+        qDebug() << "No response";
+        mErrorState = ConnectionLost;
+        return QDateTime();
+    }
+
+    while (1) {
+        QString line = responseStream.readLine();
+        if (line.isNull()) {
+            break;
+        }
+        if (line.startsWith("Last-Modified:")) {
+            QDateTime candidate = cLocale.toDateTime(line, "'Last-Modified: 'ddd, dd MMM yyyy HH:mm:ss' GMT'");
+            qDebug() << "Parsed line : " << line << " to  " << candidate;
+            if (candidate.isValid()) {
+                return candidate;
+            }
+        }
+    }
+
+    mErrorState = InvalidResponse;
+    return QDateTime();
+}
+
+bool Downloader::downloadFile(const QString &resource,
+                              const QString &fileName,
+                              size_t maxSize)
+{
+    int ret = -1;
+    size_t bytesRead = 0;
+    QString getRequest =
+        QString::fromLatin1("GET %1 HTTP/1.1\r\n\r\n").arg(resource);
+
+    QSaveFile outputFile(fileName);
+
+    ret = polarSSLWrite (&mSSL, getRequest.toUtf8());
+
+    // Open / Create the file to write to.
+    if (!outputFile.open(QIODevice::WriteOnly)) {
+        qDebug() << "Failed to open file";
+        return false;
+    }
+
+    if (ret != 0) {
+        mErrorState = ConnectionLost;
+        return false;
+    }
+
+    do {
+        QByteArray response = polarSSLRead(&mSSL, 8192);
+        if (response.isNull()) {
+            qDebug() << "Error reading response";
+            mErrorState = ConnectionLost;
+            return false;
+        }
+        if (response.isEmpty()) {
+            /* We have read everything there is to read */
+            break;
+        }
+
+        outputFile.write(response);
+        bytesRead += response.size();
+    } while (bytesRead < maxSize);
+
+    return outputFile.commit();
+}
 
 void Downloader::run() {
     int ret;
+    QDateTime remoteModList;
+    QDateTime remoteModSW;
 
     if (!mInitialized) {
         emit error(tr("Failed to initialize SSL Module."), ErrUnknown);
@@ -228,7 +408,8 @@
     }
 
     ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(),
-            mUrl.port(443));
+                      mUrl.port(443));
+
     if (ret != 0) {
         mErrorState = NoConnection;
         emit error(tr("Failed to connect to %1.").arg(mUrl.host()),
@@ -246,8 +427,55 @@
         return;
     }
 
-    qDebug() << "Connected to: " << mUrl.host();
-    // TODO
+    remoteModSW = getLastModifiedHeader(QString::fromLatin1("/incoming/aheinecke/test"));
+    remoteModList = getLastModifiedHeader(QString::fromLatin1("/incoming/aheinecke/test"));
+
+    if (!remoteModSW.isValid() || !remoteModList.isValid()) {
+        qDebug() << "Could not read headers";
+        return;
+    }
+
+    if (!mLastModSW.isValid() || remoteModSW > mLastModSW) {
+        QString dataDirectory = getDataDirectory();
+
+        if (dataDirectory.isEmpty()) {
+            qDebug() << "Failed to get data directory";
+            return;
+        }
+
+        QString fileName = dataDirectory.append("/SW-")
+            .append(remoteModSW.toString("yyyymmddHHmmss"))
+            .append(".exe");
+
+        qDebug() << "fileName: " << fileName;
+
+        if (!downloadFile(QString::fromLatin1(SW_RESOURCE),
+                   fileName, MAX_SW_SIZE)) {
+            return;
+        }
+
+        emit newSoftwareAvailable(fileName, remoteModSW);
+    } else if (!mLastModList.isValid() || remoteModList > mLastModList) {
+        QString dataDirectory = getDataDirectory();
+
+        if (dataDirectory.isEmpty()) {
+            qDebug() << "Failed to get data directory";
+            return;
+        }
+
+        QString fileName = dataDirectory.append("/list-")
+            .append(remoteModSW.toString("yyyymmddHHmmss"))
+            .append(".txt");
+
+        qDebug() << "fileName: " << fileName;
+
+        if (!downloadFile(QString::fromLatin1(LIST_RESOURCE),
+                   fileName, MAX_LIST_SIZE)) {
+            return;
+        }
+
+        emit newListAvailable(fileName, remoteModList);
+    }
 
     emit progress(tr("Closing"), 1, -1);
     ssl_close_notify (&mSSL);
--- a/ui/downloader.h	Thu Mar 13 18:12:16 2014 +0000
+++ b/ui/downloader.h	Wed Mar 12 21:26:07 2014 +0100
@@ -49,8 +49,10 @@
     enum ErrorCode {
         NoError,
         NoConnection,
+        SSLHandshakeFailed,
         InvalidCertificate,
         InvalidPinnedCertificate,
+        InvalidResponse,
         ConnectionLost,
         Timeout,
         ErrUnknown
@@ -92,6 +94,7 @@
     ErrorCode mErrorState;
 
     bool mInitialized;
+    bool mConnectionEstablished;
 
     int mServerFD;
 
@@ -106,13 +109,39 @@
      */
     int init();
 
-    /** @brief: Establish the connection
+    /** @brief: Establish the ssl connection
+     *
+     * Modifies the error state. Before calling this the mServerFD should
+     * be set to a valid socket.
      *
      * @returns 0 on success otherwise a polarssl error or -1 is returned
      */
     int establishSSLConnection();
-#ifdef Q_OS_WIN
-#endif
+
+    /** @brief get the last modified header of a resource.
+     *
+     * Connection should be established beforehand.
+     * Modifies the error state.
+     *
+     * @param[in] resource The resource to check
+     *
+     * @returns the last modified date or a null datetime in case of errors
+     */
+    QDateTime getLastModifiedHeader(const QString &resource);
+
+    /** @brief Download resource
+     *
+     * Download a resource with the established connection.
+     * Modifies the error state.
+     *
+     * @param[in] resource the resource to download
+     * @param[in] filename where the file should be saved.
+     * @param[in] maxSize maximum amount of bytes to download
+     *
+     * @returns True if the download was successful.
+     */
+    bool downloadFile(const QString &resource, const QString &filename,
+                      size_t maxSize);
 
 Q_SIGNALS:
     /**

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