summaryrefslogtreecommitdiffstats
path: root/installerbuilder/libinstaller/getrepositorymetainfojob.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'installerbuilder/libinstaller/getrepositorymetainfojob.cpp')
-rw-r--r--installerbuilder/libinstaller/getrepositorymetainfojob.cpp425
1 files changed, 425 insertions, 0 deletions
diff --git a/installerbuilder/libinstaller/getrepositorymetainfojob.cpp b/installerbuilder/libinstaller/getrepositorymetainfojob.cpp
new file mode 100644
index 000000000..ffeace700
--- /dev/null
+++ b/installerbuilder/libinstaller/getrepositorymetainfojob.cpp
@@ -0,0 +1,425 @@
+/**************************************************************************
+**
+** This file is part of Qt SDK**
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).*
+**
+** Contact: Nokia Corporation qt-info@nokia.com**
+**
+** No Commercial Usage
+**
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception version
+** 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you are unsure which license is appropriate for your use, please contact
+** (qt-info@nokia.com).
+**
+**************************************************************************/
+#include "getrepositorymetainfojob.h"
+
+#include "common/errors.h"
+#include "common/utils.h"
+#include "cryptosignatureverifier.h"
+#include "lib7z_facade.h"
+#include "messageboxhandler.h"
+
+#include <KDUpdater/FileDownloader>
+#include <KDUpdater/FileDownloaderFactory>
+#include <KDUpdater/SignatureVerifier>
+#include <KDUpdater/SignatureVerificationResult>
+#include <KDUpdater/kdupdatercrypto.h>
+
+#include <QtCore/QFile>
+#include <QtCore/QTimer>
+#include <QtCore/QUrl>
+
+#include <QtGui/QMessageBox>
+
+#include <QtXml/QDomDocument>
+#include <QtXml/QDomElement>
+
+#include <cassert>
+
+using namespace KDUpdater;
+using namespace QInstaller;
+
+GetRepositoryMetaInfoJob::GetRepositoryMetaInfoJob(const QByteArray &publicKey,
+ bool packageManager, QObject *parent)
+ : KDJob(parent),
+ m_canceled(false),
+ m_silentRetries(3),
+ m_retriesLeft(m_silentRetries),
+ m_packageManager(packageManager),
+ m_publicKey(publicKey),
+ m_downloader()
+{
+ setCapabilities(Cancelable);
+}
+
+GetRepositoryMetaInfoJob::~GetRepositoryMetaInfoJob()
+{
+ delete m_downloader;
+}
+
+Repository GetRepositoryMetaInfoJob::repository() const
+{
+ return m_repository;
+}
+
+void GetRepositoryMetaInfoJob::setRepository(const Repository &r)
+{
+ m_repository = r;
+ verbose() << "Setting repository with URL : " << r.url().toString() << std::endl;
+}
+
+int GetRepositoryMetaInfoJob::silentRetries() const
+{
+ return m_silentRetries;
+}
+
+void GetRepositoryMetaInfoJob::setSilentRetries(int retries)
+{
+ m_silentRetries = retries;
+}
+
+void GetRepositoryMetaInfoJob::doStart()
+{
+ m_retriesLeft = m_silentRetries;
+ startUpdatesXmlDownload();
+}
+
+void GetRepositoryMetaInfoJob::doCancel()
+{
+ m_canceled = true;
+ if (m_downloader)
+ m_downloader->cancelDownload();
+}
+
+QString GetRepositoryMetaInfoJob::temporaryDirectory() const
+{
+ return m_temporaryDirectory;
+}
+
+QString GetRepositoryMetaInfoJob::releaseTemporaryDirectory() const
+{
+ m_tempDirDeleter.releaseAll();
+ return m_temporaryDirectory;
+}
+
+// updates.xml download
+
+void GetRepositoryMetaInfoJob::startUpdatesXmlDownload()
+{
+ if (m_downloader) {
+ m_downloader->deleteLater();
+ m_downloader = 0;
+ }
+
+ const QUrl url = m_repository.url();
+ if (url.isEmpty()) {
+ emitFinishedWithError(InvalidUrl, tr("empty repository URL"));
+ return;
+ }
+ if (!url.isValid()) {
+ emitFinishedWithError(InvalidUrl, tr("invalid repository URL"));
+ return;
+ }
+ m_downloader = FileDownloaderFactory::instance().create(url.scheme(), 0, QUrl(), this);
+ if (!m_downloader) {
+ emitFinishedWithError(InvalidUrl, tr("URL scheme not supported: %1 (%2)").arg(url.scheme(),
+ url.toString()));
+ return;
+ }
+
+ m_downloader->setUrl(QUrl(url.toString() + QLatin1String("/Updates.xml")));
+ m_downloader->setAutoRemoveDownloadedFile(false);
+ connect(m_downloader, SIGNAL(downloadCompleted()), this, SLOT(updatesXmlDownloadFinished()));
+ connect(m_downloader, SIGNAL(downloadCanceled()), this, SLOT(updatesXmlDownloadCanceled()));
+ connect(m_downloader, SIGNAL(downloadAborted(QString)), this,
+ SLOT(updatesXmlDownloadError(QString)), Qt::QueuedConnection);
+ m_downloader->download();
+}
+
+void GetRepositoryMetaInfoJob::updatesXmlDownloadCanceled()
+{
+ emitFinishedWithError(Canceled, m_downloader->errorString());
+}
+
+void GetRepositoryMetaInfoJob::updatesXmlDownloadFinished()
+{
+ const QString fn = m_downloader->downloadedFileName();
+
+ assert(!fn.isEmpty());
+ assert(QFile::exists(fn));
+ try {
+ m_temporaryDirectory = createTemporaryDirectory(QLatin1String("remoterepo"));
+ m_tempDirDeleter.add(m_temporaryDirectory);
+ } catch (const QInstaller::Error& e) {
+ emitFinishedWithError(ExtractionError, e.message());
+ return;
+ }
+ QFile file(fn);
+ const QString updatesXmlPath = m_temporaryDirectory + QLatin1String("/Updates.xml");
+ if (!file.rename(updatesXmlPath)) {
+ emitFinishedWithError(DownloadError,
+ tr("Could not move Updates.xml to target location: %1").arg(file.errorString()));
+ return;
+ }
+
+ QFile updatesFile(updatesXmlPath);
+ if (!updatesFile.open(QIODevice::ReadOnly)) {
+ emitFinishedWithError(DownloadError,
+ tr("Could not open Updates.xml for reading: %1").arg(updatesFile.errorString()));
+ return;
+ }
+
+ QDomDocument doc;
+ QString err;
+ int line = 0;
+ int col = 0;
+ if (!doc.setContent(&updatesFile, &err, &line, &col)) {
+ QMessageBox::StandardButtons buttons = QMessageBox::Cancel;
+ QString packageManagerMessage;
+ if (m_packageManager) {
+ buttons |= QMessageBox::Ok;
+ packageManagerMessage = tr("\nOnly uninstallation is possible.");
+ }
+ const QMessageBox::StandardButton b =
+ MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(),
+ QLatin1String("updatesXmlDownloadError"), tr("Download Error"), tr("Could not fetch a "
+ "valid version of Updates.xml: %1%2").arg(err, packageManagerMessage), buttons);
+
+ if (b == QMessageBox::Cancel) {
+ emitFinishedWithError(KDJob::Canceled, tr("Could not fetch Updates.xml: %1").arg(err));
+ return;
+ }
+
+ if (b == QMessageBox::Ok) {
+ emitFinishedWithError(GetRepositoryMetaInfoJob::UserIgnoreError, tr("Could not fetch "
+ "Updates.xml: %1").arg(err));
+ return;
+ }
+ //emitFinishedWithError(InvalidMetaInfo, tr("Could not parse component index: %1:%2: %3")
+ // .arg(QString::number(line), QString::number(col), err));
+ }
+
+ const QDomElement root = doc.documentElement();
+ const QDomNodeList children = root.childNodes();
+ for (int i = 0; i < children.count(); ++i) {
+ const QDomElement el = children.at(i).toElement();
+ if (el.isNull())
+ continue;
+ if (el.tagName() == QLatin1String("PackageUpdate")) {
+ const QDomNodeList c2 = el.childNodes();
+ for (int j = 0; j < c2.count(); ++j)
+ if (c2.at(j).toElement().tagName() == QLatin1String("Name"))
+ m_packageNames << c2.at(j).toElement().text();
+ else if (c2.at(j).toElement().tagName() == QLatin1String("Version"))
+ m_packageVersions << c2.at(j).toElement().text();
+ else if (c2.at(j).toElement().tagName() == QLatin1String("SHA1"))
+ m_packageHash << c2.at(j).toElement().text();
+ }
+ }
+
+ setTotalAmount(m_packageNames.count() + 1);
+ setProcessedAmount(1);
+ if (m_packageNames.isEmpty()) {
+ emitFinished();
+ return;
+ }
+ emit infoMessage(this, tr("Retrieving component meta information..."));
+ fetchNextMetaInfo();
+}
+
+void GetRepositoryMetaInfoJob::updatesXmlDownloadError(const QString &err)
+{
+ if (m_retriesLeft == 0) {
+ QMessageBox::StandardButtons buttons = QMessageBox::Retry | QMessageBox::Cancel;
+ QString packageManagerMessage;
+ if (m_packageManager) {
+ buttons |= QMessageBox::Ok;
+ packageManagerMessage = tr("\nOnly uninstallation is possible.");
+ }
+ const QMessageBox::StandardButton b =
+ MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(),
+ QLatin1String("updatesXmlDownloadError"), tr("Download Error"), tr("Could not fetch "
+ "Updates.xml: %1%2").arg(err, packageManagerMessage), buttons);
+
+ if (b == QMessageBox::Cancel || b == QMessageBox::NoButton) {
+ emitFinishedWithError(KDJob::Canceled, tr("Could not fetch Updates.xml: %1").arg(err));
+ return;
+ }
+
+ if (b == QMessageBox::Ok) {
+ emitFinishedWithError(GetRepositoryMetaInfoJob::UserIgnoreError, tr("Could not fetch "
+ "Updates.xml: %1").arg(err));
+ return;
+ }
+ }
+
+ m_retriesLeft--;
+ QTimer::singleShot(1500, this, SLOT(startUpdatesXmlDownload()));
+}
+
+// meta data download
+
+void GetRepositoryMetaInfoJob::fetchNextMetaInfo()
+{
+ if (m_canceled)
+ return;
+
+ if (m_packageNames.isEmpty() && m_currentPackageName.isEmpty()) {
+ emitFinished();
+ return;
+ }
+
+ QString next = m_currentPackageName;
+ QString nextVersion = m_currentPackageVersion;
+ if (next.isEmpty()) {
+ next = m_packageNames.back();
+ m_packageNames.pop_back();
+ m_retriesLeft = m_silentRetries;
+ nextVersion = m_packageVersions.back();
+ m_packageVersions.pop_back();
+ }
+
+ verbose() << "fetching metadata of " << next << " in version " << nextVersion << std::endl;
+
+ bool online = true;
+ if (m_repository.url().scheme().isEmpty())
+ online = false;
+
+ const QString repoUrl = m_repository.url().toString();
+ const QUrl url = QString::fromLatin1("%1/%2/%3meta.7z").arg(repoUrl, next,
+ online ? nextVersion : QLatin1String(""));
+
+ // must be deleteLater: this code is executed from slots listening to m_downloader, avoid
+ // reentrancy issues
+ m_downloader->deleteLater();
+ if (!m_publicKey.isEmpty()) {
+ const CryptoSignatureVerifier verifier(m_publicKey);
+ const QUrl sigUrl = QString::fromLatin1("%1/%2/meta.7z.sig").arg(repoUrl, next);
+ m_downloader = FileDownloaderFactory::instance().create(url.scheme(), &verifier, sigUrl, this);
+ } else {
+ m_downloader = FileDownloaderFactory::instance().create(url.scheme(), this);
+ }
+
+ if (!m_downloader) {
+ qWarning() << "Scheme not supported:" << next;
+ m_currentPackageName.clear();
+ m_currentPackageVersion.clear();
+ QMetaObject::invokeMethod(this, "fetchNextMetaInfo", Qt::QueuedConnection);
+ return;
+ }
+
+ m_currentPackageName = next;
+ m_currentPackageVersion = nextVersion;
+ m_downloader->setUrl(url);
+ m_downloader->setAutoRemoveDownloadedFile(true);
+
+ connect(m_downloader, SIGNAL(downloadCanceled()), this, SLOT(metaDownloadCanceled()));
+ connect(m_downloader, SIGNAL(downloadCompleted()), this, SLOT(metaDownloadFinished()));
+ connect(m_downloader, SIGNAL(downloadAborted(QString)), this, SLOT(metaDownloadError(QString)),
+ Qt::QueuedConnection);
+
+ m_downloader->download();
+
+ emit infoMessage(this, tr("Retrieving component information from remote repositories..."));
+}
+
+void GetRepositoryMetaInfoJob::metaDownloadCanceled()
+{
+ updatesXmlDownloadCanceled();
+}
+
+void GetRepositoryMetaInfoJob::metaDownloadFinished()
+{
+ const QString fn = m_downloader->downloadedFileName();
+ assert(!fn.isEmpty());
+
+ QFile arch(fn);
+ if (!arch.open(QIODevice::ReadOnly)) {
+ emitFinishedWithError(ExtractionError, tr("Could not open meta info archive %1: %2").arg(fn,
+ arch.errorString()));
+ return;
+ }
+
+ if (!m_packageHash.isEmpty()) {
+ //verify file hash
+ QByteArray expectedFileHash = m_packageHash.back().toLatin1();
+ QByteArray archContent = arch.readAll();
+ QByteArray realFileHash = QString::fromLatin1(QCryptographicHash::hash(archContent,
+ QCryptographicHash::Sha1).toHex()).toLatin1();
+ if (expectedFileHash != realFileHash) {
+ metaDownloadError(tr("Bad hash"));
+ return;
+ }
+ m_currentPackageName.clear();
+ m_packageHash.pop_back();
+ }
+
+ try {
+ // TODO: make this threaded
+ Lib7z::extractArchive(&arch, m_temporaryDirectory);
+ if (!arch.remove())
+ qWarning("Could not delete file %s: %s", qPrintable(fn), qPrintable(arch.errorString()));
+ } catch (const Lib7z::SevenZipException& e) {
+ emitFinishedWithError(ExtractionError, tr("Could not open meta info archive %1: %2").arg(fn,
+ e.message()));
+ return;
+ }
+
+ fetchNextMetaInfo();
+}
+
+void GetRepositoryMetaInfoJob::metaDownloadError(const QString &err)
+{
+ if (err == QObject::tr("Bad signature"))
+ emit infoMessage(this, tr("The RSA signature of one component could not be verified."));
+
+ if (err == QObject::tr("Bad hash"))
+ emit infoMessage(this, tr("The hash of one component does not match the expected one."));
+
+ if (m_retriesLeft == 0) {
+ QMessageBox::StandardButtons buttons = QMessageBox::Retry | QMessageBox::Cancel;
+ QString packageManagerMessage;
+ if (m_packageManager) {
+ buttons |= QMessageBox::Ok;
+ packageManagerMessage = tr("\nOnly uninstallation is possible.");
+ }
+
+ const QMessageBox::StandardButton b =
+ MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(),
+ QLatin1String("updatesXmlDownloadError"), tr("Download Error"), tr("Could not download "
+ "information for component %1: %2%3") .arg(m_currentPackageName, err,
+ packageManagerMessage), buttons);
+
+ if (b == QMessageBox::Cancel) {
+ emitFinishedWithError(Canceled, tr("Canceled"));
+ return;
+ }
+
+ if (b == QMessageBox::Ok) {
+ emitFinishedWithError(GetRepositoryMetaInfoJob::UserIgnoreError, tr("%1").arg(err));
+ return;
+ }
+ }
+
+ m_retriesLeft--;
+ QTimer::singleShot(1500, this, SLOT(fetchNextMetaInfo()));
+}