aheinecke@10: #include "downloader.h"
aheinecke@10: 
aheinecke@10: #ifndef DOWNLOAD_SERVER
aheinecke@10: #define DOWNLOAD_SERVER "https://www.intevation.de"
aheinecke@10: #endif
aheinecke@10: 
aheinecke@10: #include <QFile>
aheinecke@15: #include <QDir>
aheinecke@10: #include <QDebug>
aheinecke@15: #include <QStandardPaths>
andre@32: #include <QLocale>
andre@32: #include <QSaveFile>
andre@27: 
andre@27: #include <polarssl/net.h>
andre@27: #include <polarssl/ssl.h>
andre@27: #include <polarssl/entropy.h>
andre@27: #include <polarssl/ctr_drbg.h>
andre@27: #include <polarssl/error.h>
andre@27: #include <polarssl/certs.h>
andre@27: 
andre@27: #define MAX_SW_SIZE 10485760
andre@27: #define MAX_LIST_SIZE 1048576
andre@32: 
aheinecke@10: 
aheinecke@15: Downloader::Downloader(QObject* parent, const QString& url,
aheinecke@15:                        const QByteArray& certificate,
aheinecke@15:                        const QDateTime& newestSW,
aheinecke@45:                        const QDateTime& newestList,
aheinecke@45:                        const QString& resourceSW,
aheinecke@45:                        const QString& resourceList):
aheinecke@12:     QThread(parent),
aheinecke@15:     mLastModSW(newestSW),
andre@27:     mLastModList(newestList),
aheinecke@45:     mResourceSW(resourceSW),
aheinecke@45:     mResourceList(resourceList),
aheinecke@45:     mSSLConnection(url, certificate)
aheinecke@10: {
andre@27: }
andre@27: 
andre@27: 
andre@27: Downloader::~Downloader() {
aheinecke@10: }
aheinecke@10: 
aheinecke@15: QString Downloader::getDataDirectory()
aheinecke@10: {
aheinecke@15:     QString candidate =
aheinecke@15:         QStandardPaths::writableLocation(QStandardPaths::DataLocation);
aheinecke@12: 
aheinecke@15:     if (candidate.isEmpty()) {
aheinecke@15:         qDebug() << "Could not find writeable locaction for me";
aheinecke@15:         return QString();
aheinecke@15:     }
aheinecke@15: 
aheinecke@15:     QDir cDir(candidate);
aheinecke@15: 
aheinecke@15:     if (!cDir.exists()) {
aheinecke@15:         if (!cDir.mkpath(candidate)) {
aheinecke@15:             qDebug() << "Could not create path to: " << candidate;
aheinecke@15:             return QString();
aheinecke@15:         }
aheinecke@15:     }
aheinecke@15:     return cDir.absolutePath();
aheinecke@10: }
andre@27: 
aheinecke@54: QMap<QString, QString> Downloader::parseHeaders(QByteArray *data)
aheinecke@54: {
aheinecke@54:     int bodyStart = data->indexOf("\r\n\r\n");
aheinecke@54:     QMap<QString, QString> retval;
aheinecke@54:     QByteArray headers;
aheinecke@76:     QString response(*data);
aheinecke@54:     if (bodyStart == -1) {
aheinecke@54:         qDebug() << "Could not find header end.";
aheinecke@54:         emit error(tr("Invalid response"),
aheinecke@54:                 SSLConnection::InvalidResponse);
aheinecke@54:         return retval;
aheinecke@54:     }
aheinecke@54: 
aheinecke@54:     /* Take the headers with one additional line break */
aheinecke@54:     headers = data->left(bodyStart + 2);
aheinecke@54:     /* Chop off the head */
aheinecke@54: 
aheinecke@76:     foreach (const QString& line, response.split("\r\n")) {
aheinecke@54:         int sepPos = -1;
aheinecke@54:         sepPos = line.indexOf(": ");
aheinecke@54:         if (sepPos == -1) {
aheinecke@54:             continue;
aheinecke@54:         }
aheinecke@54:         QString key = line.left(sepPos);
aheinecke@54:         QString value = line.right(line.size() - sepPos - 2);
aheinecke@54: 
aheinecke@54:         retval.insert(key, value);
aheinecke@54:     }
aheinecke@54: 
aheinecke@54:     *data = data->right(data->size() - bodyStart - 4);
aheinecke@54:     return retval;
aheinecke@54: }
andre@32: 
andre@32: QDateTime Downloader::getLastModifiedHeader(const QString &resource) {
andre@32:     int ret = -1;
andre@32:     QByteArray response;
andre@32:     QLocale cLocale = QLocale::c();
aheinecke@54:     QMap<QString, QString> headers;
andre@32:     QString headRequest =
aheinecke@46:         QString::fromLatin1("HEAD %1 HTTP/1.0\r\n\r\n").arg(resource);
andre@32: 
aheinecke@45:     ret = mSSLConnection.write(headRequest.toUtf8());
andre@32:     if (ret != 0) {
aheinecke@45:         emit error (tr("Connection lost"), SSLConnection::ConnectionLost);
andre@32:         return QDateTime();
andre@32:     }
andre@32: 
aheinecke@45:     response = mSSLConnection.read(1024);
andre@32: 
andre@32:     if (response.isNull()) {
andre@32:         qDebug() << "No response";
aheinecke@45:         emit error (tr("Connection lost"), SSLConnection::ConnectionLost);
andre@32:         return QDateTime();
andre@32:     }
andre@32: 
aheinecke@54:     headers = parseHeaders(&response);
aheinecke@54:     const QString lastModified = headers.value("Last-Modified");
aheinecke@54:     qDebug() << "Headers: " << headers;
aheinecke@54:     if (!lastModified.isEmpty()) {
aheinecke@54:         QDateTime candidate = cLocale.toDateTime(lastModified,
aheinecke@54:                 "ddd, dd MMM yyyy HH:mm:ss' GMT'");
aheinecke@54:         if (candidate.isValid()) {
aheinecke@54:             return candidate;
andre@32:         }
andre@32:     }
aheinecke@45:     emit error (tr("Invalid response from the server"), SSLConnection::InvalidResponse);
aheinecke@54:     qDebug() << "Response from server was: " << response;
andre@32:     return QDateTime();
andre@32: }
andre@32: 
andre@32: bool Downloader::downloadFile(const QString &resource,
andre@32:                               const QString &fileName,
andre@32:                               size_t maxSize)
andre@32: {
andre@32:     int ret = -1;
andre@32:     size_t bytesRead = 0;
andre@32:     QString getRequest =
aheinecke@46:         QString::fromLatin1("GET %1 HTTP/1.0\r\n\r\n").arg(resource);
andre@32: 
andre@32:     QSaveFile outputFile(fileName);
andre@32: 
aheinecke@45:     ret = mSSLConnection.write(getRequest.toUtf8());
andre@32: 
andre@32:     // Open / Create the file to write to.
andre@32:     if (!outputFile.open(QIODevice::WriteOnly)) {
andre@32:         qDebug() << "Failed to open file";
andre@32:         return false;
andre@32:     }
andre@32: 
andre@32:     if (ret != 0) {
aheinecke@45:         emit error(tr("Connection lost"), SSLConnection::ConnectionLost);
andre@32:         return false;
andre@32:     }
andre@32: 
aheinecke@54:     bool inBody = false;
aheinecke@54:     QMap <QString, QString> headers;
andre@32:     do {
aheinecke@46:         /* Read the response in 8KiB chunks */
aheinecke@54:         int responseSize = 0;
aheinecke@45:         QByteArray response = mSSLConnection.read(8192);
andre@32:         if (response.isNull()) {
andre@32:             qDebug() << "Error reading response";
aheinecke@45:             emit error(tr("Connection lost"), SSLConnection::ConnectionLost);
andre@32:             return false;
andre@32:         }
aheinecke@54:         responseSize = response.size();
aheinecke@54:         if (!inBody) {
aheinecke@54:             headers = parseHeaders(&response);
aheinecke@54:             inBody = true;
aheinecke@54:         }
aheinecke@46:         outputFile.write(response);
aheinecke@54:         bytesRead += responseSize;
aheinecke@54:         if (responseSize < 8192) {
aheinecke@54:             /* Nothing more to read */
andre@32:             break;
andre@32:         }
aheinecke@54:         /* TODO Emit progress */
andre@32:     } while (bytesRead < maxSize);
andre@32: 
andre@32:     return outputFile.commit();
andre@32: }
andre@27: 
andre@27: void Downloader::run() {
andre@27:     int ret;
andre@32:     QDateTime remoteModList;
andre@32:     QDateTime remoteModSW;
andre@27: 
aheinecke@45:     if (!mSSLConnection.initialized()) {
aheinecke@45:         emit error(tr("Failed to initialize SSL Module."), SSLConnection::ErrUnknown);
andre@27:         return;
andre@27:     }
andre@27: 
aheinecke@45:     ret = mSSLConnection.connect();
andre@32: 
andre@27:     if (ret != 0) {
aheinecke@45:         emit error(tr("Failed to connect."),
aheinecke@45:                    mSSLConnection.getLastError());
andre@27:         return;
andre@27:     }
andre@27: 
andre@27:     emit progress(tr("Connected"), 1, -1);
andre@27: 
aheinecke@45:     remoteModSW = getLastModifiedHeader(mResourceSW);
aheinecke@45:     remoteModList = getLastModifiedHeader(mResourceList);
andre@32: 
andre@32:     if (!remoteModSW.isValid() || !remoteModList.isValid()) {
aheinecke@72:         qDebug() << "Could not parse headers";
andre@32:         return;
andre@32:     }
andre@32: 
andre@32:     if (!mLastModSW.isValid() || remoteModSW > mLastModSW) {
andre@32:         QString dataDirectory = getDataDirectory();
andre@32: 
andre@32:         if (dataDirectory.isEmpty()) {
andre@32:             qDebug() << "Failed to get data directory";
andre@32:             return;
andre@32:         }
andre@32: 
andre@32:         QString fileName = dataDirectory.append("/SW-")
andre@32:             .append(remoteModSW.toString("yyyymmddHHmmss"))
andre@32:             .append(".exe");
andre@32: 
andre@32:         qDebug() << "fileName: " << fileName;
andre@32: 
aheinecke@46:         if (!downloadFile(mResourceSW, fileName, MAX_SW_SIZE)) {
andre@32:             return;
andre@32:         }
andre@32: 
andre@32:         emit newSoftwareAvailable(fileName, remoteModSW);
andre@32:     } else if (!mLastModList.isValid() || remoteModList > mLastModList) {
andre@32:         QString dataDirectory = getDataDirectory();
andre@32: 
andre@32:         if (dataDirectory.isEmpty()) {
andre@32:             qDebug() << "Failed to get data directory";
andre@32:             return;
andre@32:         }
andre@32: 
andre@32:         QString fileName = dataDirectory.append("/list-")
andre@32:             .append(remoteModSW.toString("yyyymmddHHmmss"))
andre@32:             .append(".txt");
andre@32: 
andre@32:         qDebug() << "fileName: " << fileName;
andre@32: 
aheinecke@46:         if (!downloadFile(mResourceList, fileName, MAX_LIST_SIZE)) {
andre@32:             return;
andre@32:         }
andre@32: 
andre@32:         emit newListAvailable(fileName, remoteModList);
andre@32:     }
andre@27: 
andre@27:     emit progress(tr("Closing"), 1, -1);
andre@27: }