diff options
Diffstat (limited to 'src/libs/installer/metadata.cpp')
-rw-r--r-- | src/libs/installer/metadata.cpp | 139 |
1 files changed, 132 insertions, 7 deletions
diff --git a/src/libs/installer/metadata.cpp b/src/libs/installer/metadata.cpp index 9ae817127..2eccb020e 100644 --- a/src/libs/installer/metadata.cpp +++ b/src/libs/installer/metadata.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -46,6 +46,52 @@ 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; + + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(&file); + + const QByteArray checksum = hash.result().toHex(); + if (!QFileInfo::exists(dir.absolutePath() + QDir::separator() + + QString::fromLatin1(checksum) + QLatin1String(".sha1"))) { + qCWarning(QInstaller::lcInstallerInstallLog) + << "Unexpected checksum for file" << file.fileName(); + return false; + } + } + return true; +} + /*! Constructs a new metadata object. */ @@ -122,18 +168,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 +333,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 : 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 |