# HG changeset patch # User Andre Heinecke # Date 1408380693 -7200 # Node ID eaed02defe6af3d1260c341ba3962f05e2214ff6 # Parent 18e3ad073b38b3480a43bdc232f7f83bb9877456 More SSLConnection refactoring. Fixes curl downloader. diff -r 18e3ad073b38 -r eaed02defe6a ui/CMakeLists.txt --- a/ui/CMakeLists.txt Thu Aug 14 11:24:13 2014 +0200 +++ b/ui/CMakeLists.txt Mon Aug 18 18:51:33 2014 +0200 @@ -26,11 +26,13 @@ ${CMAKE_CURRENT_SOURCE_DIR}/sslhelp.cpp ) -if (${CURL_FOUND}) +if (${CURL_FOUND} AND ${USE_CURL}) set(UICOMMON_SOURCES ${UICOMMON_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/sslconnection_curl.cpp) add_definitions(-DUSE_CURL) +elseif (${USE_CURL}) + MESSAGE(FATAL_ERROR "libcurl not found but usage of curl is requested.") else() - MESSAGE(STATUS "Warning curl not found only bare polarssl ssl will be supported.") + MESSAGE(FATAL_ERROR "${CURL_FOUND} ${USE_CURL}") endif() # Cmake does not correctly identify gcc windres when cross compiling diff -r 18e3ad073b38 -r eaed02defe6a ui/downloader.cpp --- a/ui/downloader.cpp Thu Aug 14 11:24:13 2014 +0200 +++ b/ui/downloader.cpp Mon Aug 18 18:51:33 2014 +0200 @@ -78,127 +78,6 @@ return cDir.absolutePath(); } -QMap Downloader::parseHeaders(QByteArray *data) -{ - int bodyStart = data->indexOf("\r\n\r\n"); - QMap retval; - QByteArray headers; - QString response(*data); - if (bodyStart == -1) { - qDebug() << "Could not find header end."; - emit error(tr("Invalid response"), - SSLConnection::InvalidResponse); - return retval; - } - - /* Take the headers with one additional line break */ - headers = data->left(bodyStart + 2); - /* Chop off the head */ - - foreach (const QString& line, response.split("\r\n")) { - int sepPos = -1; - sepPos = line.indexOf(": "); - if (sepPos == -1) { - continue; - } - QString key = line.left(sepPos); - QString value = line.right(line.size() - sepPos - 2); - - retval.insert(key, value); - } - - *data = data->right(data->size() - bodyStart - 4); - return retval; -} - -QDateTime Downloader::getLastModifiedHeader(const QString &resource) { - int ret = -1; - QByteArray response; - QLocale cLocale = QLocale::c(); - QMap headers; - QString headRequest = - QString::fromLatin1("HEAD %1 HTTP/1.0\r\n\r\n").arg(resource); - - ret = mSSLConnection->write(headRequest.toUtf8()); - if (ret != 0) { - emit error (tr("Connection lost"), SSLConnection::ConnectionLost); - return QDateTime(); - } - - response = mSSLConnection->read(1024); - - qDebug() << "Response from server was: " << response; - - if (response.isNull()) { - qDebug() << "No response"; - emit error (tr("Connection lost"), SSLConnection::ConnectionLost); - return QDateTime(); - } - - headers = parseHeaders(&response); - const QString lastModified = headers.value("Last-Modified"); - if (!lastModified.isEmpty()) { - QDateTime candidate = cLocale.toDateTime(lastModified, - "ddd, dd MMM yyyy HH:mm:ss' GMT'"); - if (candidate.isValid()) { - return candidate; - } - } - emit error (tr("Invalid response from the server"), SSLConnection::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.0\r\n\r\n").arg(resource); - - QSaveFile outputFile(fileName); - - ret = mSSLConnection->write(getRequest.toUtf8()); - - // Open / Create the file to write to. - if (!outputFile.open(QIODevice::WriteOnly)) { - qDebug() << "Failed to open file"; - return false; - } - - if (ret != 0) { - emit error(tr("Connection lost"), SSLConnection::ConnectionLost); - return false; - } - - bool inBody = false; - QMap headers; - do { - /* Read the response in 8KiB chunks */ - int responseSize = 0; - QByteArray response = mSSLConnection->read(8192); - if (response.isNull()) { - qDebug() << "Error reading response"; - emit error(tr("Connection lost"), SSLConnection::ConnectionLost); - return false; - } - responseSize = response.size(); - if (!inBody) { - headers = parseHeaders(&response); - inBody = true; - } - outputFile.write(response); - bytesRead += responseSize; - if (responseSize < 8192) { - /* Nothing more to read */ - break; - } - /* TODO Emit progress */ - } while (bytesRead < maxSize); - - return outputFile.commit(); -} void Downloader::run() { int ret; @@ -220,10 +99,11 @@ emit progress(tr("Connected"), 1, -1); - remoteModSW = getLastModifiedHeader(mResourceSW); + remoteModSW = mSSLConnection->getLastModifiedHeader(mResourceSW); emit lastModifiedDate(remoteModSW); if (!remoteModSW.isValid()) { + emit error (tr("Connection failed."), SSLConnection::InvalidResponse); qDebug() << "Could not parse headers for Software"; return; } @@ -247,7 +127,8 @@ qDebug() << "fileName: " << fileName; if (mDownloadSW) { - if (!downloadFile(mResourceSW, fileName, MAX_SW_SIZE)) { + if (!mSSLConnection->downloadFile(mResourceSW, fileName, MAX_SW_SIZE)) { + emit error(tr("Failed to download File"), SSLConnection::ConnectionLost); qDebug() << "Failed to download software update."; return; } @@ -260,8 +141,9 @@ return; } - remoteModList = getLastModifiedHeader(mResourceList); + remoteModList = mSSLConnection->getLastModifiedHeader(mResourceList); if (!remoteModList.isValid()) { + emit error (tr("Connection failed."), SSLConnection::InvalidResponse); qDebug() << "Could not parse headers for List"; return; } @@ -280,7 +162,7 @@ qDebug() << "fileName: " << fileName; - if (!downloadFile(mResourceList, fileName, MAX_LIST_SIZE)) { + if (!mSSLConnection->downloadFile(mResourceList, fileName, MAX_LIST_SIZE)) { return; } diff -r 18e3ad073b38 -r eaed02defe6a ui/downloader.h --- a/ui/downloader.h Thu Aug 14 11:24:13 2014 +0200 +++ b/ui/downloader.h Mon Aug 18 18:51:33 2014 +0200 @@ -85,42 +85,6 @@ SSLConnection *mSSLConnection; - /** @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); - /** - * @brief parses the Headers of a repsonse. - * - * This removes the headers from the byte array passed as - * parameter. - * - * @param[inout] data: The response to parse. - * - * @returns: A map of the header fields. Or an empty map on error. - */ - QMap parseHeaders(QByteArray *data); - Q_SIGNALS: /** * @brief software update is available diff -r 18e3ad073b38 -r eaed02defe6a ui/sslconnection.h --- a/ui/sslconnection.h Thu Aug 14 11:24:13 2014 +0200 +++ b/ui/sslconnection.h Mon Aug 18 18:51:33 2014 +0200 @@ -51,18 +51,6 @@ virtual ~SSLConnection() {}; - /** @brief write */ - virtual int write(const QByteArray& request) = 0; - - /** - * @brief read at most len bytes and reset the connection - * - * @param [in] len Amount of bytes to read. - * - * @returns a byte array containing the data or - * a NULL byte array on error*/ - virtual QByteArray read(size_t len) = 0; - bool initialized() { return mInitialized; } bool connected() { return mConnected; } @@ -74,6 +62,30 @@ */ virtual int connect() = 0; + /** @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 + */ + virtual QDateTime getLastModifiedHeader(const QString &resource) = 0; + + /** @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. + */ + virtual bool downloadFile(const QString &resource, const QString &filename, + size_t maxSize) = 0; protected: QUrl mUrl; QByteArray mPinnedCert; diff -r 18e3ad073b38 -r eaed02defe6a ui/sslconnection_bare.cpp --- a/ui/sslconnection_bare.cpp Thu Aug 14 11:24:13 2014 +0200 +++ b/ui/sslconnection_bare.cpp Mon Aug 18 18:51:33 2014 +0200 @@ -11,6 +11,7 @@ #include "sslhelp.h" #include +#include #include #include @@ -359,4 +360,113 @@ return retval; } +QMap SSLConnectionBare::parseHeaders(QByteArray *data) +{ + int bodyStart = data->indexOf("\r\n\r\n"); + QMap retval; + QByteArray headers; + QString response(*data); + if (bodyStart == -1) { + qDebug() << "Could not find header end."; + return retval; + } + /* Take the headers with one additional line break */ + headers = data->left(bodyStart + 2); + /* Chop off the head */ + + foreach (const QString& line, response.split("\r\n")) { + int sepPos = -1; + sepPos = line.indexOf(": "); + if (sepPos == -1) { + continue; + } + QString key = line.left(sepPos); + QString value = line.right(line.size() - sepPos - 2); + + retval.insert(key, value); + } + + *data = data->right(data->size() - bodyStart - 4); + return retval; +} + +QDateTime SSLConnectionBare::getLastModifiedHeader(const QString &resource) { + int ret = -1; + QByteArray response; + QLocale cLocale = QLocale::c(); + QMap headers; + QString headRequest = + QString::fromLatin1("HEAD %1 HTTP/1.0\r\n\r\n").arg(resource); + + ret = write(headRequest.toUtf8()); + if (ret != 0) { + return QDateTime(); + } + + response = read(1024); + + qDebug() << "Response from server was: " << response; + + if (response.isNull()) { + qDebug() << "No response"; + return QDateTime(); + } + + headers = parseHeaders(&response); + const QString lastModified = headers.value("Last-Modified"); + if (!lastModified.isEmpty()) { + QDateTime candidate = cLocale.toDateTime(lastModified, + "ddd, dd MMM yyyy HH:mm:ss' GMT'"); + if (candidate.isValid()) { + return candidate; + } + } + return QDateTime(); +} + +bool SSLConnectionBare::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.0\r\n\r\n").arg(resource); + + QSaveFile outputFile(fileName); + + ret = write(getRequest.toUtf8()); + + // Open / Create the file to write to. + if (!outputFile.open(QIODevice::WriteOnly)) { + qDebug() << "Failed to open file"; + return false; + } + + bool inBody = false; + QMap headers; + do { + /* Read the response in 8KiB chunks */ + int responseSize = 0; + QByteArray response = read(8192); + if (response.isNull()) { + qDebug() << "Error reading response"; + return false; + } + responseSize = response.size(); + if (!inBody) { + headers = parseHeaders(&response); + inBody = true; + } + outputFile.write(response); + bytesRead += responseSize; + if (responseSize < 8192) { + /* Nothing more to read */ + break; + } + } while (bytesRead < maxSize); + + return outputFile.commit(); +} + diff -r 18e3ad073b38 -r eaed02defe6a ui/sslconnection_bare.h --- a/ui/sslconnection_bare.h Thu Aug 14 11:24:13 2014 +0200 +++ b/ui/sslconnection_bare.h Mon Aug 18 18:51:33 2014 +0200 @@ -17,6 +17,8 @@ #include #include +#include + /** * @file sslconnection_bare.h * @brief SSLConnection doing bare SSL over PolarSSL @@ -30,23 +32,12 @@ ~SSLConnectionBare(); - /** @brief write */ - int write(const QByteArray& request); + int connect(); - /** - * @brief read at most len bytes and reset the connection - * - * @param [in] len Amount of bytes to read. - * - * @returns a byte array containing the data or - * a NULL byte array on error*/ - QByteArray read(size_t len); + QDateTime getLastModifiedHeader(const QString &resource); - /** @brief: Establish the connection - * - * @returns 0 on success otherwise an error or -1 is returned - */ - int connect(); + bool downloadFile(const QString &resource, const QString &filename, + size_t maxSize); private: x509_crt mX509PinnedCert; @@ -82,6 +73,31 @@ /* @brief disconnects the connection */ void disconnect(); + + /** + * @brief parses the Headers of a repsonse. + * + * This removes the headers from the byte array passed as + * parameter. + * + * @param[inout] data: The response to parse. + * + * @returns: A map of the header fields. Or an empty map on error. + */ + QMap parseHeaders(QByteArray *data); + + /** @brief write */ + int write(const QByteArray& request); + + /** + * @brief read at most len bytes and reset the connection + * + * @param [in] len Amount of bytes to read. + * + * @returns a byte array containing the data or + * a NULL byte array on error*/ + QByteArray read(size_t len); + }; #endif // UI_SSLCONNECTION_BARE_H diff -r 18e3ad073b38 -r eaed02defe6a ui/sslconnection_curl.cpp --- a/ui/sslconnection_curl.cpp Thu Aug 14 11:24:13 2014 +0200 +++ b/ui/sslconnection_curl.cpp Mon Aug 18 18:51:33 2014 +0200 @@ -7,9 +7,17 @@ */ #include "sslconnection_curl.h" +#include #define CONNECTION_DEBUG +/**@def Wrapper macro around curl_easy_setopt invocation */ +#define CURL_SETOPT(x, y, z) \ + if (curl_easy_setopt(mCurl, x, y) != CURLE_OK) { \ + qDebug() << "Setopt failed"; \ + z; \ + } + SSLConnectionCurl::SSLConnectionCurl(const QString& url, const QByteArray& certificate): SSLConnection (url, certificate), @@ -23,11 +31,6 @@ return; } - if (curl_easy_setopt(mCurl, CURLOPT_URL, QUrl(url).toEncoded().constData()) != CURLE_OK) { - qDebug() << "Setting url failed"; - return; - } - if (curl_easy_setopt(mCurl, CURLOPT_SSL_VERIFYPEER, 1L) != CURLE_OK) { /* Should be default anyway */ qDebug() << "Setting verifypeer failed"; @@ -40,6 +43,11 @@ return; } + if (curl_easy_setopt(mCurl, CURLOPT_ERRORBUFFER, mErrBuf) != CURLE_OK) { + qDebug() << "Setting errorbuf failed"; + return; + } + mCertFile.open(); if (mCertFile.write(mPinnedCert) != mPinnedCert.size()) { qDebug() << "Failed to write temporary certificate"; @@ -70,12 +78,25 @@ } int SSLConnectionCurl::connect() { + CURLcode retval; + + if (curl_easy_setopt(mCurl, CURLOPT_URL, mUrl.toEncoded().constData()) != CURLE_OK) { + qDebug() << "Failed to set URL"; + return -1; + } + if (curl_easy_setopt(mCurl, CURLOPT_CONNECT_ONLY, 1L) != CURLE_OK) { qDebug() << "Failed to set connect only option"; return -1; } - if (curl_easy_perform (mCurl) != CURLE_OK) { - qDebug() << "Failed to connect"; + retval = curl_easy_perform(mCurl); + if (retval != CURLE_OK) { + qDebug() << "Failed to connect: " << mErrBuf << " retval: " << retval; + if (retval == CURLE_PEER_FAILED_VERIFICATION) { + mErrorState = InvalidCertificate; + return -1; + } + mErrorState = NoConnection; return -1; } @@ -83,51 +104,128 @@ return 0; } -int SSLConnectionCurl::write(const QByteArray& request) { - size_t written = 0; +/* Globally do this as we can't pass this to the c function */ +size_t ssl_curl_max_write, ssl_curl_written; - if (curl_easy_send (mCurl, request.constData(), request.size(), &written) != CURLE_OK) { - qDebug() << "Failed to send request"; - return -1; +size_t write_data(void *ptr, size_t size, size_t nmemb, + QSaveFile *fp) +{ + qDebug() << "Writing size: " << size << " * " << nmemb; + if (ssl_curl_max_write < ssl_curl_written) { + qDebug() << "Aborting write. Too much data."; + return 0; } - if (written != (size_t)request.size()) { - qDebug() << "Failed to write everything"; - return -1; + size_t written = fp->write((const char *)ptr, size * nmemb); + if (written != size * nmemb) { + qDebug() << "Failed to write data. Written: " << written + << " requested: " << size * nmemb; + return 0; } - return 0; + ssl_curl_written += written; + return written; } -QByteArray SSLConnectionCurl::read(size_t len) +bool SSLConnectionCurl::downloadFile(const QString &resource, + const QString &fileName, + size_t maxSize) { - unsigned char buf[len]; - QByteArray retval(""); - CURLcode ret; - size_t read = 0; - unsigned int tries = 0; + QSaveFile outputFile(fileName); + ssl_curl_written = 0; + ssl_curl_max_write = maxSize; + // Open / Create the file to write to. + if (!outputFile.open(QIODevice::WriteOnly)) { + qDebug() << "Failed to open file"; + return false; + } + QUrl urlCopy = mUrl; + urlCopy.setPath(resource); - do { - memset (buf, 0, sizeof(buf)); - ret = curl_easy_recv (mCurl, buf, len, &read); - if (ret == CURLE_OK && read == 0) { - return retval; - } - if (ret == CURLE_AGAIN) { - tries++; - continue; - } - if (ret != CURLE_OK) { - qDebug() << "Read failed."; - return QByteArray(); - } - if (len < (len - read)) { - /* Should never happen if ssl_read behaves */ - qDebug() << "integer overflow in polarSSLRead"; - return QByteArray(); - } + if (curl_easy_setopt(mCurl, CURLOPT_URL, urlCopy.toEncoded().constData()) != CURLE_OK) { + qDebug() << "Failed to set URL"; + return false; + } - len -= read; - retval.append((const char *)buf, read); - } while (len > 0 && tries < 10); + if (curl_easy_setopt(mCurl, CURLOPT_CONNECT_ONLY, 0L) != CURLE_OK) { + qDebug() << "Failed to set connect"; + return false; + } - return retval; + if (curl_easy_setopt(mCurl, CURLOPT_HEADER, 0L) != CURLE_OK) { + qDebug() << "Failed to set header"; + return false; + } + + if (curl_easy_setopt(mCurl, CURLOPT_NOBODY, 0L) != CURLE_OK) { + qDebug() << "Failed to set no body"; + return false; + } + + if (curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, write_data) != CURLE_OK) { + qDebug() << "Failed to set write function"; + return false; + } + + if (curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, &outputFile) != CURLE_OK) { + qDebug() << "Failed to set write function"; + return false; + } + + if (curl_easy_perform (mCurl) != CURLE_OK) { + qDebug() << "Failed to perform download."; + return false; + } + + if (!outputFile.commit()) { + qDebug() << "Failed to commit data to filesystem."; + return false; + } + + return true; } + +QDateTime SSLConnectionCurl::getLastModifiedHeader(const QString &resource) { + QUrl urlCopy = mUrl; + urlCopy.setPath(resource); + + if (curl_easy_setopt(mCurl, CURLOPT_URL, urlCopy.toEncoded().constData()) != CURLE_OK) { + qDebug() << "Failed to set URL"; + return QDateTime(); + } + + if (curl_easy_setopt(mCurl, CURLOPT_CONNECT_ONLY, 0L) != CURLE_OK) { + qDebug() << "Failed to set connect"; + return QDateTime(); + } + + if (curl_easy_setopt(mCurl, CURLOPT_HEADER, 1L) != CURLE_OK) { + qDebug() << "Failed to set header"; + return QDateTime(); + } + + if (curl_easy_setopt(mCurl, CURLOPT_NOBODY, 1L) != CURLE_OK) { + qDebug() << "Failed to set no body"; + return QDateTime(); + } + + if (curl_easy_setopt(mCurl, CURLOPT_FILETIME, 1L) != CURLE_OK) { + qDebug() << "Failed to set filetime"; + return QDateTime(); + } + + if (curl_easy_perform (mCurl) != CURLE_OK) { + qDebug() << "Failed to perform last modified check."; + return QDateTime(); + } + long filetime = 0; + + if (curl_easy_getinfo (mCurl, CURLINFO_FILETIME, &filetime) != CURLE_OK) { + qDebug() << "Failed to get filetime"; + return QDateTime(); + } + + if (filetime == -1) { + qDebug() << "Invalid Time"; + return QDateTime(); + } + return QDateTime::fromTime_t(filetime); +} diff -r 18e3ad073b38 -r eaed02defe6a ui/sslconnection_curl.h --- a/ui/sslconnection_curl.h Thu Aug 14 11:24:13 2014 +0200 +++ b/ui/sslconnection_curl.h Mon Aug 18 18:51:33 2014 +0200 @@ -8,11 +8,19 @@ * See LICENSE.txt for details. */ +/** + * @file sslconnection_curl.h + * @brief SSLConnection utilizing libcurl for http. + */ + #include "sslconnection.h" #include +#include #include +class QSaveFile; + class SSLConnectionCurl : public SSLConnection { public: @@ -21,30 +29,20 @@ ~SSLConnectionCurl(); - /** @brief write */ - int write(const QByteArray& request); + int connect(); - /** - * @brief read at most len bytes and reset the connection - * - * @param [in] len Amount of bytes to read. - * - * @returns a byte array containing the data or - * a NULL byte array on error*/ - QByteArray read(size_t len); + QDateTime getLastModifiedHeader(const QString &resource); - /** @brief: Establish the connection - * - * @returns 0 on success otherwise an error or -1 is returned - */ - int connect(); + bool downloadFile(const QString &resource, const QString &filename, + size_t maxSize); private: CURL *mCurl; QTemporaryFile mCertFile; + char mErrBuf[CURL_ERROR_SIZE + 1]; + + /** @brief Internal write function for curl */ }; -/** - * @file sslconnection_curl.h - * @brief SSLConnection utilizing libcurl for http. - */ +size_t write_data(void *ptr, size_t size, size_t nmemb, QSaveFile *fp); + #endif // UI_SSLCONNECTION_CURL_H diff -r 18e3ad073b38 -r eaed02defe6a ui/tests/downloadertest.cpp --- a/ui/tests/downloadertest.cpp Thu Aug 14 11:24:13 2014 +0200 +++ b/ui/tests/downloadertest.cpp Mon Aug 18 18:51:33 2014 +0200 @@ -94,7 +94,7 @@ QFileInfo fi(getRandomDataFile(200)); Downloader* downloader = new Downloader(this, - QString::fromLatin1("https://localhost:44443"), + QString::fromLatin1("https://127.0.0.1:44443"), otherCert.readAll(), QDateTime::currentDateTime(), // Last installed SW QDateTime::fromString("2010", "YYYY"), @@ -155,7 +155,7 @@ validCert.open(QIODevice::ReadOnly); Downloader* downloader = new Downloader(this, - QString::fromLatin1("https://localhost:44443"), + QString::fromLatin1("https://127.0.0.1:44443"), validCert.readAll(), QDateTime::fromString("2010", "YYYY"), QDateTime::currentDateTime(), @@ -196,7 +196,7 @@ validCert.open(QIODevice::ReadOnly); Downloader* downloader = new Downloader(this, - QString::fromLatin1("https://localhost:44443"), + QString::fromLatin1("https://127.0.0.1:44443"), validCert.readAll(), QDateTime::currentDateTime(), // Last installed SW QDateTime::fromString("2010", "YYYY"),