diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/libs/installer/metadata.cpp | 145 | ||||
-rw-r--r-- | src/libs/installer/metadata.h | 5 | ||||
-rw-r--r-- | src/libs/installer/metadatajob.cpp | 3 | ||||
-rw-r--r-- | src/libs/installer/metadatajob_p.h | 39 |
4 files changed, 184 insertions, 8 deletions
diff --git a/src/libs/installer/metadata.cpp b/src/libs/installer/metadata.cpp index 9ae817127..e0ff7f614 100644 --- a/src/libs/installer/metadata.cpp +++ b/src/libs/installer/metadata.cpp @@ -46,6 +46,60 @@ namespace QInstaller { \brief The Metadata class represents fetched metadata from a repository. */ + +/*! + \internal +*/ +static bool verifyFileIntegrityFromElement(const QDomElement &element, const QString &childNodeName, + const QString &attribute, const QString &metaDirectory, bool testChecksum) +{ + const QDomNodeList nodes = element.childNodes(); + for (int i = 0; i < nodes.count(); ++i) { + const QDomNode node = nodes.at(i); + if (node.nodeName() != childNodeName) + continue; + + const QDir dir(metaDirectory); + const QString filename = attribute.isEmpty() + ? node.toElement().text() + : node.toElement().attribute(attribute); + + if (filename.isEmpty()) + continue; + + QFile file(dir.absolutePath() + QDir::separator() + filename); + if (!file.open(QIODevice::ReadOnly)) { + qCWarning(QInstaller::lcInstallerInstallLog) + << "Cannot open" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + + if (!testChecksum) + continue; + + QFile hashFile(file.fileName() + QLatin1String(".sha1")); + if (!hashFile.open(QIODevice::ReadOnly)) { + qCWarning(QInstaller::lcInstallerInstallLog) + << "Cannot open" << hashFile.fileName() + << "for reading:" << hashFile.errorString(); + return false; + } + + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(&file); + + const QByteArray checksum = hash.result().toHex(); + const QByteArray expectedChecksum = hashFile.readAll(); + if (checksum != expectedChecksum) { + qCWarning(QInstaller::lcInstallerInstallLog) + << "Unexpected checksum for file" << file.fileName(); + return false; + } + } + return true; +} + /*! Constructs a new metadata object. */ @@ -122,18 +176,23 @@ QDomDocument Metadata::updatesDocument() const } /*! - Returns \c true if the \c Updates.xml document of this metadata - exists, \c false otherwise. + Returns \c true if the \c Updates.xml document of this metadata exists, and that all + meta files referenced in the document exist. If the \c Updates.xml contains a \c Checksum + element with a value of \c true, the integrity of the files is also verified. + + Returns \c false otherwise. */ bool Metadata::isValid() const { - const QString updateFile(path() + QLatin1String("/Updates.xml")); - if (!QFileInfo::exists(updateFile)) { + QFile updateFile(path() + QLatin1String("/Updates.xml")); + if (!updateFile.open(QIODevice::ReadOnly)) { qCWarning(QInstaller::lcInstallerInstallLog) - << "File" << updateFile << "does not exist."; + << "Cannot open" << updateFile.fileName() + << "for reading:" << updateFile.errorString(); return false; } - return true; + + return verifyMetaFiles(&updateFile); } /*! @@ -282,4 +341,78 @@ bool Metadata::containsRepositoryUpdates() const return false; } +/*! + Verifies that the files referenced in \a updateFile document exist + on disk. If the document contains a \c Checksum element with a value + of \c true, the integrity of the files is also verified. + + Returns \c true if the meta files are valid, \c false otherwise. +*/ +bool Metadata::verifyMetaFiles(QFile *updateFile) const +{ + QDomDocument doc; + QString errorString; + if (!doc.setContent(updateFile, &errorString)) { + qCWarning(QInstaller::lcInstallerInstallLog) + << "Cannot set document content:" << errorString; + return false; + } + + const QDomElement rootElement = doc.documentElement(); + const QDomNodeList childNodes = rootElement.childNodes(); + + bool testChecksum = true; + const QDomElement checksumElement = rootElement.firstChildElement(QLatin1String("Checksum")); + if (!checksumElement.isNull()) + testChecksum = (checksumElement.text().toLower() == scTrue); + + for (int i = 0; i < childNodes.count(); ++i) { + const QDomElement element = childNodes.at(i).toElement(); + if (element.isNull() || element.tagName() != QLatin1String("PackageUpdate")) + continue; + + const QDomNodeList c2 = element.childNodes(); + QString packageName; + QString unused1; + QString unused2; + + // Only need the package name, so values for "online" and "testCheckSum" do not matter + if (!MetadataJob::parsePackageUpdate(c2, packageName, unused1, unused2, true, true)) + continue; // nothing to check for this package + + const QString packagePath = QString::fromLatin1("%1/%2/").arg(path(), packageName); + for (auto &metaTagName : qAsConst(*scMetaElements)) { + const QDomElement metaElement = element.firstChildElement(metaTagName); + if (metaElement.isNull()) + continue; + + if (metaElement.tagName() == QLatin1String("Licenses")) { + if (!verifyFileIntegrityFromElement(metaElement, QLatin1String("License"), + QLatin1String("file"), packagePath, testChecksum)) { + return false; + } + } else if (metaElement.tagName() == QLatin1String("UserInterfaces")) { + if (!verifyFileIntegrityFromElement(metaElement, QLatin1String("UserInterface"), + QString(), packagePath, testChecksum)) { + return false; + } + } else if (metaElement.tagName() == QLatin1String("Translations")) { + if (!verifyFileIntegrityFromElement(metaElement, QLatin1String("Translation"), + QString(), packagePath, testChecksum)) { + return false; + } + } else if (metaElement.tagName() == QLatin1String("Script")) { + if (!verifyFileIntegrityFromElement(metaElement.parentNode().toElement(), + QLatin1String("Script"), QString(), packagePath, testChecksum)) { + return false; + } + } else { + Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown meta element."); + } + } + } + + return true; +} + } // namespace QInstaller diff --git a/src/libs/installer/metadata.h b/src/libs/installer/metadata.h index 3063be829..7312d106b 100644 --- a/src/libs/installer/metadata.h +++ b/src/libs/installer/metadata.h @@ -35,6 +35,8 @@ #include <QDomDocument> +class QFile; + namespace QInstaller { class INSTALLER_EXPORT Metadata : public CacheableItem @@ -64,6 +66,9 @@ public: bool containsRepositoryUpdates() const; private: + bool verifyMetaFiles(QFile *updateFile) const; + +private: Repository m_repository; QString m_persistentRepositoryPath; mutable QByteArray m_checksum; diff --git a/src/libs/installer/metadatajob.cpp b/src/libs/installer/metadatajob.cpp index ff15d7b0d..663b209f7 100644 --- a/src/libs/installer/metadatajob.cpp +++ b/src/libs/installer/metadatajob.cpp @@ -193,7 +193,7 @@ bool MetadataJob::resetCache(bool init) m_metaFromCache.setPath(m_core->settings().localCachePath()); m_metaFromCache.setType(QLatin1String("Metadata")); - m_metaFromCache.setVersion(QLatin1String(QUOTE(IFW_REPOSITORY_FORMAT_VERSION))); + m_metaFromCache.setVersion(QLatin1String(QUOTE(IFW_CACHE_FORMAT_VERSION))); if (!init) return true; @@ -630,6 +630,7 @@ void MetadataJob::metadataTaskFinished() UnzipArchiveTask *task = new UnzipArchiveTask(result.target(), item.value(TaskRole::UserRole).toString()); task->setRemoveArchive(true); + task->setStoreChecksums(true); QFutureWatcher<void> *watcher = new QFutureWatcher<void>(); m_unzipTasks.insert(watcher, qobject_cast<QObject*> (task)); diff --git a/src/libs/installer/metadatajob_p.h b/src/libs/installer/metadatajob_p.h index 5340e5d1c..8ab2d9e7d 100644 --- a/src/libs/installer/metadatajob_p.h +++ b/src/libs/installer/metadatajob_p.h @@ -62,11 +62,16 @@ class UnzipArchiveTask : public AbstractTask<void> public: UnzipArchiveTask(const QString &arcive, const QString &target) - : m_archive(arcive), m_targetDir(target), m_removeArchive(false) + : m_archive(arcive) + , m_targetDir(target) + , m_removeArchive(false) + , m_storeChecksums(false) {} + QString target() { return m_targetDir; } QString archive() { return m_archive; } void setRemoveArchive(bool remove) { m_removeArchive = remove; } + void setStoreChecksums(bool store) { m_storeChecksums = store; } void doTask(QFutureInterface<void> &fi) override { @@ -82,12 +87,43 @@ public: if (!archive) { fi.reportException(UnzipArchiveException(MetadataJob::tr("Unsupported archive \"%1\": no handler " "registered for file suffix \"%2\".").arg(m_archive, QFileInfo(m_archive).suffix()))); + return; } else if (!archive->open(QIODevice::ReadOnly)) { fi.reportException(UnzipArchiveException(MetadataJob::tr("Cannot open file \"%1\" for " "reading: %2").arg(QDir::toNativeSeparators(m_archive), archive->errorString()))); + return; } else if (!archive->extract(m_targetDir)) { fi.reportException(UnzipArchiveException(MetadataJob::tr("Error while extracting " "archive \"%1\": %2").arg(QDir::toNativeSeparators(m_archive), archive->errorString()))); + return; + } + + if (m_storeChecksums) { + // Calculate and store checksums of extracted files for later use + const QVector<ArchiveEntry> entries = archive->list(); + for (auto &entry : entries) { + if (entry.isDirectory) + continue; + + QFile file(m_targetDir + QDir::separator() + entry.path); + if (!file.open(QIODevice::ReadOnly)) { + fi.reportException(UnzipArchiveException(MetadataJob::tr("Cannot open extracted file \"%1\" for " + "reading: %2").arg(QDir::toNativeSeparators(file.fileName()), file.errorString()))); + break; + } + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(&file); + + const QByteArray hexChecksum = hash.result().toHex(); + QFile hashFile(file.fileName() + QLatin1String(".sha1")); + if (!hashFile.open(QIODevice::WriteOnly)) { + fi.reportException(UnzipArchiveException(MetadataJob::tr("Cannot open file \"%1\" for " + "writing: %2").arg(QDir::toNativeSeparators(hashFile.fileName()), hashFile.errorString()))); + break; + } + QTextStream stream(&hashFile); + stream << hexChecksum; + } } archive->close(); @@ -101,6 +137,7 @@ private: QString m_archive; QString m_targetDir; bool m_removeArchive; + bool m_storeChecksums; }; class CacheTaskException : public QException |