aheinecke@404: /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik aheinecke@404: * Software engineering by Intevation GmbH aheinecke@404: * aheinecke@404: * This file is Free Software under the GNU GPL (v>=2) aheinecke@404: * and comes with ABSOLUTELY NO WARRANTY! aheinecke@404: * See LICENSE.txt for details. aheinecke@404: */ 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 aheinecke@15: #include aheinecke@10: #include aheinecke@15: #include andre@32: #include andre@32: #include andre@27: andre@27: #include andre@27: #include andre@27: #include andre@27: #include andre@27: #include andre@27: #include andre@27: andre@808: #define MAX_SW_SIZE 15728640 andre@27: #define MAX_LIST_SIZE 1048576 andre@32: andre@908: #include "sslconnection_curl.h" andre@908: #include "sslconnection_bare.h" 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, rrenkert@460: const QString& resourceList, rrenkert@460: bool downloadSW): aheinecke@12: QThread(parent), aheinecke@15: mLastModSW(newestSW), andre@27: mLastModList(newestList), aheinecke@45: mResourceSW(resourceSW), aheinecke@45: mResourceList(resourceList), andre@908: mDownloadSW(downloadSW) aheinecke@10: { andre@908: #ifdef USE_CURL andre@908: mSSLConnection = new SSLConnectionCurl(url, certificate); andre@908: #else andre@908: mSSLConnection = new SSLConnectionBare(url, certificate); andre@908: #endif andre@27: } andre@27: andre@27: andre@27: Downloader::~Downloader() { andre@908: delete mSSLConnection; 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 Downloader::parseHeaders(QByteArray *data) aheinecke@54: { aheinecke@54: int bodyStart = data->indexOf("\r\n\r\n"); aheinecke@54: QMap 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 headers; andre@32: QString headRequest = aheinecke@46: QString::fromLatin1("HEAD %1 HTTP/1.0\r\n\r\n").arg(resource); andre@32: andre@908: 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: andre@908: response = mSSLConnection->read(1024); andre@32: aheinecke@553: qDebug() << "Response from server was: " << response; aheinecke@553: 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: 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); 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: andre@908: 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 headers; andre@32: do { aheinecke@46: /* Read the response in 8KiB chunks */ aheinecke@54: int responseSize = 0; andre@908: 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: andre@908: if (!mSSLConnection->initialized()) { aheinecke@45: emit error(tr("Failed to initialize SSL Module."), SSLConnection::ErrUnknown); andre@27: return; andre@27: } andre@27: andre@908: ret = mSSLConnection->connect(); andre@32: andre@27: if (ret != 0) { aheinecke@45: emit error(tr("Failed to connect."), andre@908: mSSLConnection->getLastError()); andre@27: return; andre@27: } andre@27: andre@27: emit progress(tr("Connected"), 1, -1); andre@27: aheinecke@45: remoteModSW = getLastModifiedHeader(mResourceSW); rrenkert@485: emit lastModifiedDate(remoteModSW); andre@32: aheinecke@459: if (!remoteModSW.isValid()) { aheinecke@459: qDebug() << "Could not parse headers for Software"; 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-") aheinecke@442: .append(remoteModSW.toString("yyyyMMddHHmmss")) andre@809: #ifdef WIN32 andre@32: .append(".exe"); andre@809: #else andre@809: .append(".sh"); andre@809: #endif andre@32: andre@32: qDebug() << "fileName: " << fileName; andre@32: rrenkert@460: if (mDownloadSW) { rrenkert@460: if (!downloadFile(mResourceSW, fileName, MAX_SW_SIZE)) { andre@809: qDebug() << "Failed to download software update."; rrenkert@460: return; rrenkert@460: } andre@809: QFile::setPermissions(fileName, QFileDevice::ReadOwner | andre@809: QFileDevice::WriteOwner | andre@809: QFileDevice::ExeOwner); andre@32: } andre@32: andre@32: emit newSoftwareAvailable(fileName, remoteModSW); aheinecke@458: return; aheinecke@458: } aheinecke@458: aheinecke@458: remoteModList = getLastModifiedHeader(mResourceList); aheinecke@459: if (!remoteModList.isValid()) { aheinecke@459: qDebug() << "Could not parse headers for List"; aheinecke@459: return; aheinecke@459: } aheinecke@458: aheinecke@458: 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-") rrenkert@443: .append(remoteModList.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: }