diff options
| author | Linus Jahn <lnj@kaidan.im> | 2022-10-02 19:47:09 +0200 |
|---|---|---|
| committer | Linus Jahn <lnj@kaidan.im> | 2022-10-02 23:01:13 +0200 |
| commit | ada1907bb5f22e0820e1d1d8004cc23f2246e74b (patch) | |
| tree | 9d533ea41e91ad657a777f5a09536514b9042c8a /src/client | |
| parent | a58f415f064a34136545da3333f96d2e83b23b29 (diff) | |
| download | qxmpp-ada1907bb5f22e0820e1d1d8004cc23f2246e74b.tar.gz | |
FileSharingManager: Verify hashes of downloaded files
Can only handle QFiles currently.
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/QXmppDownload.cpp | 19 | ||||
| -rw-r--r-- | src/client/QXmppDownload.h | 20 | ||||
| -rw-r--r-- | src/client/QXmppFileSharingManager.cpp | 75 |
3 files changed, 111 insertions, 3 deletions
diff --git a/src/client/QXmppDownload.cpp b/src/client/QXmppDownload.cpp index 0ece1906..e57b1956 100644 --- a/src/client/QXmppDownload.cpp +++ b/src/client/QXmppDownload.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2022 Jonah BrĂ¼chert <jbb@kaidan.im> +// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im> // // SPDX-License-Identifier: LGPL-2.1-or-later @@ -13,6 +14,24 @@ /// /// +/// \enum QXmppDownload::HashVerificationResult +/// +/// Describes the result of the hash verification. +/// + +/// +/// \struct QXmppDownload::Downloaded +/// +/// Indicates that the file could be downloaded. +/// + +/// +/// \var QXmppDownload::Downloaded::hashVerificationResult +/// +/// Describes the result of the hash verification. +/// + +/// /// \typedef QXmppDownload::Result /// /// \brief Contains QXmpp::Success (successfully finished), QXmpp::Cancelled (manually cancelled) diff --git a/src/client/QXmppDownload.h b/src/client/QXmppDownload.h index 3256aaea..a1dffb3d 100644 --- a/src/client/QXmppDownload.h +++ b/src/client/QXmppDownload.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2022 Jonah BrĂ¼chert <jbb@kaidan.im> +// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im> // // SPDX-License-Identifier: LGPL-2.1-or-later @@ -14,7 +15,24 @@ class QXMPP_EXPORT QXmppDownload : public QXmppFileTransfer { Q_OBJECT public: - using Result = std::variant<QXmpp::Success, QXmpp::Cancelled, QXmppError>; + enum HashVerificationResult { + /// + /// \brief File did not contain strong hashes (or no hashes at all) and no verification + /// was done. + /// + /// This value is not used when a hash value did not match. In that case the whole file + /// download returns an error. + /// + NoStrongHashes, + /// \brief The file integrity could be proved using a strong hash algorithm. + HashVerified, + }; + + struct Downloaded { + HashVerificationResult hashVerificationResult; + }; + + using Result = std::variant<Downloaded, QXmpp::Cancelled, QXmppError>; Q_SIGNAL void finished(QXmppDownload::Result); }; diff --git a/src/client/QXmppFileSharingManager.cpp b/src/client/QXmppFileSharingManager.cpp index fc73099c..0d4b780c 100644 --- a/src/client/QXmppFileSharingManager.cpp +++ b/src/client/QXmppFileSharingManager.cpp @@ -48,6 +48,16 @@ static std::vector<HashAlgorithm> hashAlgorithms() }; } +template<typename T, typename Converter> +auto transform(T &input, Converter convert) +{ + using Output = std::decay_t<decltype(convert(input.front()))>; + std::vector<Output> output; + output.reserve(input.size()); + std::transform(input.begin(), input.end(), std::back_inserter(output), std::move(convert)); + return output; +} + class UploadImpl : public QXmppUpload { Q_OBJECT @@ -92,6 +102,7 @@ class DownloadImpl : public QXmppDownload if (m_providerDownload) { m_providerDownload->cancel(); } + m_hashesFuture.cancel(); } bool isFinished() const override { return m_finished; } quint64 bytesTransferred() const override { return m_bytesReceived; } @@ -111,6 +122,8 @@ public: } std::shared_ptr<QXmppFileSharingProvider::Download> m_providerDownload; + QFuture<HashVerificationResultPtr> m_hashesFuture; + QVector<QXmppHash> m_hashes; quint64 m_bytesReceived = 0; quint64 m_bytesTotal = 0; bool m_finished = false; @@ -303,12 +316,70 @@ std::shared_ptr<QXmppDownload> QXmppFileSharingManager::downloadFile( std::unique_ptr<QIODevice> output) { auto download = std::make_shared<DownloadImpl>(); + download->m_hashes = fileShare.metadata().hashes(); + + // currently hashing does only work with QFiles + auto filePath = [&]() -> QString { + if (auto *file = dynamic_cast<QFile *>(output.get())) { + return file->fileName(); + } + return {}; + }(); auto onProgress = [download](quint64 received, quint64 total) { download->reportProgress(received, total); }; - auto onFinished = [download](QXmppFileSharingProvider::DownloadResult result) { - download->reportFinished(std::move(result)); + auto onFinished = [this, download, filePath](QXmppFileSharingProvider::DownloadResult result) mutable { + // reduce ref count + download->m_providerDownload.reset(); + + // pass errors directly + if (std::holds_alternative<Cancelled>(result)) { + download->reportFinished(Cancelled()); + return; + } + if (std::holds_alternative<QXmppError>(result)) { + download->reportFinished(std::get<QXmppError>(std::move(result))); + return; + } + + // try to do hash verification + if (filePath.isEmpty()) { + warning(QStringLiteral("Can't verify hashes of other io devices than QFile!")); + download->reportFinished(QXmppDownload::Downloaded { QXmppDownload::NoStrongHashes }); + return; + } + + auto file = std::make_unique<QFile>(filePath); + if (!file->open(QIODevice::ReadOnly)) { + download->reportFinished(QXmppError::fromFileDevice(*file)); + return; + } + + download->m_hashesFuture = verifyHashes( + std::move(file), + transform(download->m_hashes, [](auto hash) { return hash; })); + + await(download->m_hashesFuture, this, [download = std::move(download)](HashVerificationResultPtr hashResult) { + download->reportFinished(visitForward<QXmppDownload::Result>(hashResult->result, overloaded { + [](HashVerificationResult::NoStrongHashes) { + return QXmppDownload::Downloaded { + QXmppDownload::NoStrongHashes + }; + }, + [](HashVerificationResult::NotMatching) { + return QXmppError { + QStringLiteral("Checksum does not match"), + {} + }; + }, + [](HashVerificationResult::Verified) { + return QXmppDownload::Downloaded { + QXmppDownload::HashVerified + }; + } + })); + }); }; fileShare.visitSources([&](const std::any &source) { |
