view ui/downloader.cpp @ 502:e551de11d8b6

Properly handle the case that the file does not exist. TRUNCATE makes create file fail if the file does not exist but we need TRUNCATE in the case that the file already exists
author Andre Heinecke <aheinecke@intevation.de>
date Mon, 28 Apr 2014 09:18:07 +0000
parents 5834b340c54c
children cb044efdaf0d
line wrap: on
line source
/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */
#include "downloader.h"

#ifndef DOWNLOAD_SERVER
#define DOWNLOAD_SERVER "https://www.intevation.de"
#endif

#include <QFile>
#include <QDir>
#include <QDebug>
#include <QStandardPaths>
#include <QLocale>
#include <QSaveFile>

#include <polarssl/net.h>
#include <polarssl/ssl.h>
#include <polarssl/entropy.h>
#include <polarssl/ctr_drbg.h>
#include <polarssl/error.h>
#include <polarssl/certs.h>

#define MAX_SW_SIZE 10485760
#define MAX_LIST_SIZE 1048576


Downloader::Downloader(QObject* parent, const QString& url,
                       const QByteArray& certificate,
                       const QDateTime& newestSW,
                       const QDateTime& newestList,
                       const QString& resourceSW,
                       const QString& resourceList,
                       bool downloadSW):
    QThread(parent),
    mLastModSW(newestSW),
    mLastModList(newestList),
    mResourceSW(resourceSW),
    mResourceList(resourceList),
    mDownloadSW(downloadSW),
    mSSLConnection(url, certificate)
{
}


Downloader::~Downloader() {
}

QString Downloader::getDataDirectory()
{
    QString candidate =
        QStandardPaths::writableLocation(QStandardPaths::DataLocation);

    if (candidate.isEmpty()) {
        qDebug() << "Could not find writeable locaction for me";
        return QString();
    }

    QDir cDir(candidate);

    if (!cDir.exists()) {
        if (!cDir.mkpath(candidate)) {
            qDebug() << "Could not create path to: " << candidate;
            return QString();
        }
    }
    return cDir.absolutePath();
}

QMap<QString, QString> Downloader::parseHeaders(QByteArray *data)
{
    int bodyStart = data->indexOf("\r\n\r\n");
    QMap<QString, QString> 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<QString, QString> 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);

    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");
    qDebug() << "Headers: " << headers;
    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);
    qDebug() << "Response from server was: " << response;
    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 <QString, QString> 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;
    QDateTime remoteModList;
    QDateTime remoteModSW;

    if (!mSSLConnection.initialized()) {
        emit error(tr("Failed to initialize SSL Module."), SSLConnection::ErrUnknown);
        return;
    }

    ret = mSSLConnection.connect();

    if (ret != 0) {
        emit error(tr("Failed to connect."),
                   mSSLConnection.getLastError());
        return;
    }

    emit progress(tr("Connected"), 1, -1);

    remoteModSW = getLastModifiedHeader(mResourceSW);
    emit lastModifiedDate(remoteModSW);

    if (!remoteModSW.isValid()) {
        qDebug() << "Could not parse headers for Software";
        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 (mDownloadSW) {
            if (!downloadFile(mResourceSW, fileName, MAX_SW_SIZE)) {
                return;
            }
        }

        emit newSoftwareAvailable(fileName, remoteModSW);
        return;
    }

    remoteModList = getLastModifiedHeader(mResourceList);
    if (!remoteModList.isValid()) {
        qDebug() << "Could not parse headers for List";
        return;
    }

    if (!mLastModList.isValid() || remoteModList > mLastModList) {
        QString dataDirectory = getDataDirectory();

        if (dataDirectory.isEmpty()) {
            qDebug() << "Failed to get data directory";
            return;
        }

        QString fileName = dataDirectory.append("/list-")
            .append(remoteModList.toString("yyyyMMddHHmmss"))
            .append(".txt");

        qDebug() << "fileName: " << fileName;

        if (!downloadFile(mResourceList, fileName, MAX_LIST_SIZE)) {
            return;
        }

        emit newListAvailable(fileName, remoteModList);
    }

    emit progress(tr("Closing"), 1, -1);
}

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