From 4bab0f2ef2c68b478d669f90557d6bef6332e823 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 31 Aug 2021 21:47:10 +0200 Subject: Implement the mxc protocol in the NetworkAccessManager Allows images to be loaded using the NetworkAccessManager instead of an ImageProvider --- lib/mxcreply.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 lib/mxcreply.cpp (limited to 'lib/mxcreply.cpp') diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp new file mode 100644 index 00000000..49ebe603 --- /dev/null +++ b/lib/mxcreply.cpp @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Tobias Fella +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "mxcreply.h" + +#include +#include "connection.h" +#include "room.h" +#include "networkaccessmanager.h" +#include "events/stickerevent.h" + +using namespace Quotient; + +class MxcReply::Private +{ +public: + QNetworkReply *m_reply = nullptr; +}; + +MxcReply::MxcReply(QNetworkReply* reply) +{ + reply->setParent(this); + d->m_reply = reply; + connect(d->m_reply, &QNetworkReply::finished, this, [this]() { + setError(d->m_reply->error(), d->m_reply->errorString()); + Q_EMIT finished(); + }); +} + +MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) + : d(std::make_unique()) +{ + reply->setParent(this); + d->m_reply = reply; + connect(d->m_reply, &QNetworkReply::finished, this, [this, eventId]() { + setError(d->m_reply->error(), d->m_reply->errorString()); + Q_EMIT finished(); + }); +} + +bool MxcReply::isSequential() const +{ + return true; +} + +qint64 MxcReply::readData(char *data, qint64 maxSize) +{ + return d->m_reply->read(data, maxSize); +} + +void MxcReply::abort() +{ + d->m_reply->abort(); +} \ No newline at end of file -- cgit v1.2.3 From 6d3bc1667e084fa2fc7b2c547374d2c62a29e8df Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 7 Sep 2021 16:17:23 +0200 Subject: Fix showing non-animated Images --- lib/mxcreply.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/mxcreply.cpp') diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 49ebe603..f389ac85 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -23,6 +23,7 @@ MxcReply::MxcReply(QNetworkReply* reply) d->m_reply = reply; connect(d->m_reply, &QNetworkReply::finished, this, [this]() { setError(d->m_reply->error(), d->m_reply->errorString()); + setOpenMode(ReadOnly); Q_EMIT finished(); }); } @@ -34,6 +35,7 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) d->m_reply = reply; connect(d->m_reply, &QNetworkReply::finished, this, [this, eventId]() { setError(d->m_reply->error(), d->m_reply->errorString()); + setOpenMode(ReadOnly); Q_EMIT finished(); }); } -- cgit v1.2.3 From 8dfa505066a03cc8450527699634fda71cbd8915 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 10 Sep 2021 17:55:20 +0200 Subject: Return a failed MxcReply on invalid requests --- lib/mxcreply.cpp | 15 +++++++++++++++ lib/mxcreply.h | 1 + lib/networkaccessmanager.cpp | 8 ++++---- 3 files changed, 20 insertions(+), 4 deletions(-) (limited to 'lib/mxcreply.cpp') diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index f389ac85..7819367e 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -4,6 +4,7 @@ #include "mxcreply.h" #include +#include #include "connection.h" #include "room.h" #include "networkaccessmanager.h" @@ -40,6 +41,20 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) }); } +MxcReply::MxcReply() +{ + QTimer::singleShot(0, this, [this](){ + setError(QNetworkReply::ProtocolInvalidOperationError, QStringLiteral("Invalid Request")); + setFinished(true); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + Q_EMIT errorOccurred(QNetworkReply::ProtocolInvalidOperationError); +#else + Q_EMIT error(QNetworkReply::ProtocolInvalidOperationError); +#endif + Q_EMIT finished(); + }); +} + bool MxcReply::isSequential() const { return true; diff --git a/lib/mxcreply.h b/lib/mxcreply.h index 26dea2d0..ac3ac4f4 100644 --- a/lib/mxcreply.h +++ b/lib/mxcreply.h @@ -14,6 +14,7 @@ class MxcReply : public QNetworkReply public: MxcReply(QNetworkReply* reply, Room* room, const QString &eventId); MxcReply(QNetworkReply* reply); + MxcReply(); bool isSequential() const override; diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index 710ade4e..f37e26b6 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -73,12 +73,12 @@ QNetworkReply* NetworkAccessManager::createRequest( auto connection = AccountRegistry::instance().get(fragmentParts[0]); if(!connection) { qWarning() << "Connection not found"; - return nullptr; + return new MxcReply(); } auto room = connection->room(fragmentParts[1]); if(!room) { qWarning() << "Room not found"; - return nullptr; + return new MxcReply(); } QNetworkRequest r(request); r.setUrl(QUrl(QStringLiteral("%1/_matrix/media/r0/download/%2").arg(connection->homeserver().toString(), mediaId))); @@ -88,7 +88,7 @@ QNetworkReply* NetworkAccessManager::createRequest( auto connection = AccountRegistry::instance().get(fragment); if(!connection) { qWarning() << "Connection not found"; - return nullptr; + return new MxcReply(); } QNetworkRequest r(request); r.setUrl(QUrl(QStringLiteral("%1/_matrix/media/r0/download/%2").arg(connection->homeserver().toString(), mediaId))); @@ -96,7 +96,7 @@ QNetworkReply* NetworkAccessManager::createRequest( return new MxcReply(reply); } else { qWarning() << "Invalid request"; - return nullptr; + return new MxcReply(); } } auto reply = QNetworkAccessManager::createRequest(op, request, outgoingData); -- cgit v1.2.3 From 6597866ead7a3eb03cfcbbd99b547de1bb72867e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 11 Sep 2021 13:05:40 +0200 Subject: Further tweaks to MxcReply - QNetworkReply::isSequential() already returns `true`, there's no need to overload it again. - Use `Q_SLOTS` instead of `slots` because it's an external library interface and clients may use other libraries using `slots` identifier; - Use `emit` instead of `Q_EMIT` because this is a part of internal implementation and if we ever use a library that has an `emit` identifier, a massive search-replace will be in order anyway. - Use `QMetaObject::invokeMethod()` with a queued connection as a clearer way to achieve the same goal as `QTimer::singleShot(0, ...)`. --- lib/mxcreply.cpp | 37 +++++++++++++++++-------------------- lib/mxcreply.h | 15 +++++++-------- 2 files changed, 24 insertions(+), 28 deletions(-) (limited to 'lib/mxcreply.cpp') diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 7819367e..daa4af9a 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -3,12 +3,7 @@ #include "mxcreply.h" -#include -#include -#include "connection.h" #include "room.h" -#include "networkaccessmanager.h" -#include "events/stickerevent.h" using namespace Quotient; @@ -34,30 +29,32 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) { reply->setParent(this); d->m_reply = reply; - connect(d->m_reply, &QNetworkReply::finished, this, [this, eventId]() { + connect(d->m_reply, &QNetworkReply::finished, this, [this, room, eventId]() { setError(d->m_reply->error(), d->m_reply->errorString()); setOpenMode(ReadOnly); - Q_EMIT finished(); + emit finished(); }); } -MxcReply::MxcReply() -{ - QTimer::singleShot(0, this, [this](){ - setError(QNetworkReply::ProtocolInvalidOperationError, QStringLiteral("Invalid Request")); - setFinished(true); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - Q_EMIT errorOccurred(QNetworkReply::ProtocolInvalidOperationError); +#define ERROR_SIGNAL errorOccurred #else - Q_EMIT error(QNetworkReply::ProtocolInvalidOperationError); +#define ERROR_SIGNAL error #endif - Q_EMIT finished(); - }); -} -bool MxcReply::isSequential() const +MxcReply::MxcReply() { - return true; + static const auto BadRequestPhrase = tr("Bad Request"); + QMetaObject::invokeMethod(this, [this]() { + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 400); + setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, + BadRequestPhrase); + setError(QNetworkReply::ProtocolInvalidOperationError, + BadRequestPhrase); + setFinished(true); + emit ERROR_SIGNAL(QNetworkReply::ProtocolInvalidOperationError); + emit finished(); + }, Qt::QueuedConnection); } qint64 MxcReply::readData(char *data, qint64 maxSize) @@ -68,4 +65,4 @@ qint64 MxcReply::readData(char *data, qint64 maxSize) void MxcReply::abort() { d->m_reply->abort(); -} \ No newline at end of file +} diff --git a/lib/mxcreply.h b/lib/mxcreply.h index ac3ac4f4..efaf01c6 100644 --- a/lib/mxcreply.h +++ b/lib/mxcreply.h @@ -8,23 +8,22 @@ namespace Quotient { class Room; -class Connection; + class MxcReply : public QNetworkReply { public: - MxcReply(QNetworkReply* reply, Room* room, const QString &eventId); - MxcReply(QNetworkReply* reply); - MxcReply(); - - bool isSequential() const override; + explicit MxcReply(); + explicit MxcReply(QNetworkReply *reply); + MxcReply(QNetworkReply* reply, Room* room, const QString& eventId); -public slots: +public Q_SLOTS: void abort() override; protected: qint64 readData(char *data, qint64 maxSize) override; + private: class Private; std::unique_ptr d; }; -} \ No newline at end of file +} -- cgit v1.2.3 From 77b69eb370e1cdbc33e44121f4f8483e19cad7d8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 12 Sep 2021 04:37:08 +0200 Subject: MxcReply: make sure to create a Private object --- lib/mxcreply.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'lib/mxcreply.cpp') diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index daa4af9a..0b6643fc 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -10,13 +10,16 @@ using namespace Quotient; class MxcReply::Private { public: - QNetworkReply *m_reply = nullptr; + explicit Private(QNetworkReply* r = nullptr) + : m_reply(r) + {} + QNetworkReply* m_reply; }; MxcReply::MxcReply(QNetworkReply* reply) + : d(std::make_unique(reply)) { reply->setParent(this); - d->m_reply = reply; connect(d->m_reply, &QNetworkReply::finished, this, [this]() { setError(d->m_reply->error(), d->m_reply->errorString()); setOpenMode(ReadOnly); @@ -25,10 +28,9 @@ MxcReply::MxcReply(QNetworkReply* reply) } MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) - : d(std::make_unique()) + : d(std::make_unique(reply)) { reply->setParent(this); - d->m_reply = reply; connect(d->m_reply, &QNetworkReply::finished, this, [this, room, eventId]() { setError(d->m_reply->error(), d->m_reply->errorString()); setOpenMode(ReadOnly); -- cgit v1.2.3 From 8636c7028b45ee8de3125bcf4df40ad60ed949a0 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 31 Aug 2021 00:09:59 +0200 Subject: Add mxc protocol to the networkaccessmanager --- lib/connection.cpp | 6 ++---- lib/connection.h | 3 +-- lib/jobs/downloadfilejob.cpp | 48 +++++++++++--------------------------------- lib/jobs/downloadfilejob.h | 3 ++- lib/mxcreply.cpp | 36 +++++++++++++++++++++++++++++++-- lib/networkaccessmanager.cpp | 6 ++++++ lib/networkaccessmanager.h | 2 +- lib/room.cpp | 2 +- 8 files changed, 59 insertions(+), 47 deletions(-) (limited to 'lib/mxcreply.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4a1130ae..d8e98bb0 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1019,15 +1019,13 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, #ifdef Quotient_E2EE_ENABLED DownloadFileJob* Connection::downloadFile(const QUrl& url, - const QString& key, - const QString& iv, - const QString& sha256, + const EncryptedFile file, const QString& localFilename) { auto mediaId = url.authority() + url.path(); auto idParts = splitMediaId(mediaId); auto* job = - callApi(idParts.front(), idParts.back(), key, iv, sha256, localFilename); + callApi(idParts.front(), idParts.back(), file, localFilename); return job; } #endif diff --git a/lib/connection.h b/lib/connection.h index f9143d3e..d0945aa4 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -568,8 +568,7 @@ public Q_SLOTS: const QString& localFilename = {}); #ifdef Quotient_E2EE_ENABLED - DownloadFileJob* downloadFile(const QUrl& url, const QString &key, - const QString& iv, const QString& sha256, + DownloadFileJob* downloadFile(const QUrl& url, const EncryptedFile file, const QString& localFilename = {}); #endif /** diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index e82271eb..2fba1973 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -9,20 +9,8 @@ #ifdef Quotient_E2EE_ENABLED # include -# include - -QByteArray decrypt(const QByteArray &ciphertext, const QByteArray &key, const QByteArray &iv) -{ - QByteArray plaintext(ciphertext.size(), 0); - EVP_CIPHER_CTX *ctx; - int length; - ctx = EVP_CIPHER_CTX_new(); - EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)key.data(), (const unsigned char *)iv.data()); - EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); - EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); - EVP_CIPHER_CTX_free(ctx); - return plaintext; -} +# include "encryptionmanager.h" +# include "events/encryptedfile.h" #endif using namespace Quotient; @@ -39,9 +27,7 @@ public: QScopedPointer tempFile; #ifdef Quotient_E2EE_ENABLED - QByteArray key; - QByteArray iv; - QByteArray sha256; + Omittable encryptedFile; #endif }; @@ -63,18 +49,13 @@ DownloadFileJob::DownloadFileJob(const QString& serverName, #ifdef Quotient_E2EE_ENABLED DownloadFileJob::DownloadFileJob(const QString& serverName, const QString& mediaId, - const QString& key, - const QString& iv, - const QString& sha256, + const EncryptedFile file, const QString& localFilename) : GetContentJob(serverName, mediaId) , d(localFilename.isEmpty() ? new Private : new Private(localFilename)) { setObjectName(QStringLiteral("DownloadFileJob")); - auto _key = key; - d->key = QByteArray::fromBase64(_key.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); - d->iv = QByteArray::fromBase64(iv.toLatin1()); - d->sha256 = QByteArray::fromBase64(sha256.toLatin1()); + d->encryptedFile = file; } #endif QString DownloadFileJob::targetFileName() const @@ -140,14 +121,12 @@ BaseJob::Status DownloadFileJob::prepareResult() { if (d->targetFile) { #ifdef Quotient_E2EE_ENABLED - if(d->key.size() != 0) { + if (d->encryptedFile.has_value()) { d->tempFile->seek(0); QByteArray encrypted = d->tempFile->readAll(); - if(d->sha256 != QCryptographicHash::hash(encrypted, QCryptographicHash::Sha256)) { - qCWarning(E2EE) << "Hash verification failed for file"; - return IncorrectResponse; - } - auto decrypted = decrypt(encrypted, d->key, d->iv); + + EncryptedFile file = *d->encryptedFile; + auto decrypted = EncryptionManager::decryptFile(encrypted, &file); d->targetFile->write(decrypted); d->tempFile->remove(); } else { @@ -167,15 +146,12 @@ BaseJob::Status DownloadFileJob::prepareResult() #endif } else { #ifdef Quotient_E2EE_ENABLED - if(d->key.size() != 0) { + if (d->encryptedFile.has_value()) { d->tempFile->seek(0); auto encrypted = d->tempFile->readAll(); - if(d->sha256 != QCryptographicHash::hash(encrypted, QCryptographicHash::Sha256)) { - qCWarning(E2EE) << "Hash verification failed for file"; - return IncorrectResponse; - } - auto decrypted = decrypt(encrypted, d->key, d->iv); + EncryptedFile file = *d->encryptedFile; + auto decrypted = EncryptionManager::decryptFile(encrypted, &file); d->tempFile->write(decrypted); } else { #endif diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index f000b991..67a3e95f 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -4,6 +4,7 @@ #pragma once #include "csapi/content-repo.h" +#include "events/encryptedfile.h" namespace Quotient { class DownloadFileJob : public GetContentJob { @@ -15,7 +16,7 @@ public: const QString& localFilename = {}); #ifdef Quotient_E2EE_ENABLED - DownloadFileJob(const QString& serverName, const QString& mediaId, const QString& key, const QString& iv, const QString& sha256, const QString& localFilename = {}); + DownloadFileJob(const QString& serverName, const QString& mediaId, const EncryptedFile file, const QString& localFilename = {}); #endif QString targetFileName() const; diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 0b6643fc..65078301 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -3,8 +3,17 @@ #include "mxcreply.h" +#include +#include +#include "accountregistry.h" +#include "connection.h" #include "room.h" +#ifdef Quotient_E2EE_ENABLED +#include "encryptionmanager.h" +#include "events/encryptedfile.h" +#endif + using namespace Quotient; class MxcReply::Private @@ -14,6 +23,8 @@ public: : m_reply(r) {} QNetworkReply* m_reply; + Omittable m_encryptedFile; + QIODevice* m_device = nullptr; }; MxcReply::MxcReply(QNetworkReply* reply) @@ -31,11 +42,32 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) : d(std::make_unique(reply)) { reply->setParent(this); - connect(d->m_reply, &QNetworkReply::finished, this, [this, room, eventId]() { + connect(d->m_reply, &QNetworkReply::finished, this, [this]() { setError(d->m_reply->error(), d->m_reply->errorString()); + +#ifdef Quotient_E2EE_ENABLED + if(!d->m_encryptedFile.has_value()) { + d->m_device = d->m_reply; + } else { + EncryptedFile file = *d->m_encryptedFile; + auto buffer = new QBuffer(this); + buffer->setData(EncryptionManager::decryptFile(d->m_reply->readAll(), &file)); + d->m_device = buffer; + } setOpenMode(ReadOnly); emit finished(); +#else + d->m_device = d->m_reply; +#endif }); + +#ifdef Quotient_E2EE_ENABLED + auto eventIt = room->findInTimeline(eventId); + if(eventIt != room->historyEdge()) { + auto event = eventIt->viewAs(); + d->m_encryptedFile = event->content()->fileInfo()->file; + } +#endif } #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) @@ -61,7 +93,7 @@ MxcReply::MxcReply() qint64 MxcReply::readData(char *data, qint64 maxSize) { - return d->m_reply->read(data, maxSize); + return d->m_device->read(data, maxSize); } void MxcReply::abort() diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index d0380cec..c660cff8 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -12,6 +12,12 @@ #include #include #include +#include "accountregistry.h" +#include "mxcreply.h" +#include "connection.h" +#include "events/eventcontent.h" + +#include "room.h" using namespace Quotient; diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h index 7643302f..efa41994 100644 --- a/lib/networkaccessmanager.h +++ b/lib/networkaccessmanager.h @@ -9,7 +9,7 @@ namespace Quotient { class Room; -class Connection; + class NetworkAccessManager : public QNetworkAccessManager { Q_OBJECT public: diff --git a/lib/room.cpp b/lib/room.cpp index d7ebe021..688ba5d4 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2409,7 +2409,7 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) #ifdef Quotient_E2EE_ENABLED if(fileInfo->file.has_value()) { auto file = *fileInfo->file; - job = connection()->downloadFile(fileUrl, file.key.k, file.iv, file.hashes["sha256"], filePath); + job = connection()->downloadFile(fileUrl, file, filePath); } else { #endif job = connection()->downloadFile(fileUrl, filePath); -- cgit v1.2.3 From 34db4fd1294e41765a5db58ee1a0c59712af62c6 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 15 Nov 2021 21:26:47 +0100 Subject: Various improvements and fixes --- lib/connection.cpp | 54 ++++++++++++++++++++++++---------------------- lib/crypto/qolmsession.cpp | 4 +--- lib/encryptionmanager.cpp | 2 +- lib/mxcreply.cpp | 1 + lib/room.cpp | 4 +++- lib/settings.cpp | 6 ------ 6 files changed, 34 insertions(+), 37 deletions(-) (limited to 'lib/mxcreply.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index d8e98bb0..f36166ff 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -830,33 +830,35 @@ void Connection::Private::consumePresenceData(Events&& presenceData) void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { #ifdef Quotient_E2EE_ENABLED - qWarning() << "Consuming to device events" << toDeviceEvents.size(); - if(toDeviceEvents.size() > 0) - visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { - if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { - qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); - return; - } - const auto decryptedEvent = sessionDecryptMessage(event); - if(!decryptedEvent) { - qCWarning(E2EE) << "Failed to decrypt event" << event.id(); - return; - } + if(toDeviceEvents.size() > 0) { + qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() << "to-device events"; + visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { + if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { + qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); + return; + } + qWarning() << event.fullJson(); + const auto decryptedEvent = sessionDecryptMessage(event); + if(!decryptedEvent) { + qCWarning(E2EE) << "Failed to decrypt event" << event.id(); + return; + } - visit(*decryptedEvent, - [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { - if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { - detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); - } else { - qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() - << "is not found at the connection" << q->objectName(); - } - }, - [](const Event& evt) { - qCDebug(E2EE) << "Skipping encrypted to_device event, type" - << evt.matrixType(); - }); - }); + visit(*decryptedEvent, + [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { + if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { + detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); + } else { + qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() + << "is not found at the connection" << q->objectName(); + } + }, + [](const Event& evt) { + qCDebug(E2EE) << "Skipping encrypted to_device event, type" + << evt.matrixType(); + }); + }); + } #endif } diff --git a/lib/crypto/qolmsession.cpp b/lib/crypto/qolmsession.cpp index 2068a7d9..a327a643 100644 --- a/lib/crypto/qolmsession.cpp +++ b/lib/crypto/qolmsession.cpp @@ -46,9 +46,7 @@ std::variant, QOlmError> QOlmSession::createInbound if (error == olm_error()) { const auto lastErr = lastError(olmSession); - if (lastErr == QOlmError::NotEnoughRandom) { - qCCritical(E2EE) << "Error when creating inbound session" << lastErr; - } + qCWarning(E2EE) << "Error when creating inbound session" << lastErr; return lastErr; } diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 5c1750c9..81c13e50 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -137,7 +137,7 @@ public: qCDebug(E2EE) << "Creating new inbound session"; auto newSessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), message); if(std::holds_alternative(newSessionResult)) { - qCWarning(E2EE) << "Failed to create inbound session for" << senderKey; + qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get(newSessionResult); return {}; } std::unique_ptr newSession = std::move(std::get>(newSessionResult)); diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 65078301..639c1324 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -52,6 +52,7 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) EncryptedFile file = *d->m_encryptedFile; auto buffer = new QBuffer(this); buffer->setData(EncryptionManager::decryptFile(d->m_reply->readAll(), &file)); + buffer->open(ReadOnly); d->m_device = buffer; } setOpenMode(ReadOnly); diff --git a/lib/room.cpp b/lib/room.cpp index 6c5a9d33..94f0c9eb 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1600,7 +1600,9 @@ void Room::handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, if (auto encryptedEvent = d->timeline[i].viewAs()) { auto decrypted = decryptMessage(*encryptedEvent); if(decrypted) { - d->timeline[i].replaceEvent(std::move(decrypted)); + qWarning() << "decrypted" << decrypted->fullJson(); + auto oldEvent = d->timeline[i].replaceEvent(std::move(decrypted)); + emit replacedEvent(d->timeline[i].event(), rawPtr(oldEvent)); } } } diff --git a/lib/settings.cpp b/lib/settings.cpp index ed9082b0..f9b4f471 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -136,18 +136,12 @@ void AccountSettings::clearAccessToken() QByteArray AccountSettings::encryptionAccountPickle() { - QString passphrase = ""; // FIXME: add QtKeychain return value("encryption_account_pickle", "").toByteArray(); } void AccountSettings::setEncryptionAccountPickle( const QByteArray& encryptionAccountPickle) { - qCWarning(MAIN) - << "Saving encryption_account_pickle to QSettings is insecure." - " Developers, do it manually or contribute to share QtKeychain " - "logic to libQuotient."; - QString passphrase = ""; // FIXME: add QtKeychain setValue("encryption_account_pickle", encryptionAccountPickle); } -- cgit v1.2.3 From e99802772ebab9802e2f35d83ce1de9f83691d90 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sat, 27 Nov 2021 00:11:36 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- lib/connection.cpp | 8 ++++---- lib/encryptionmanager.cpp | 2 +- lib/jobs/downloadfilejob.cpp | 2 +- lib/jobs/downloadfilejob.h | 2 +- lib/mxcreply.cpp | 3 +-- lib/room.cpp | 11 +++++------ 6 files changed, 13 insertions(+), 15 deletions(-) (limited to 'lib/mxcreply.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 95ed1eb6..df9ff445 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -57,7 +57,7 @@ #include -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#if QT_VERSION_MAJOR >= 6 # include #else # include @@ -830,7 +830,7 @@ void Connection::Private::consumePresenceData(Events&& presenceData) void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { #ifdef Quotient_E2EE_ENABLED - if(toDeviceEvents.size() > 0) { + if (!toDeviceEvents.empty()) { qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() << "to-device events"; visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { @@ -1020,7 +1020,7 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, #ifdef Quotient_E2EE_ENABLED DownloadFileJob* Connection::downloadFile(const QUrl& url, - const EncryptedFile file, + const EncryptedFile& file, const QString& localFilename) { auto mediaId = url.authority() + url.path(); @@ -1996,7 +1996,7 @@ void Connection::Private::saveDevicesList() void Connection::Private::loadDevicesList() { - QFile file { q->e2eeDataDir() + QStringLiteral("/deviceslist.json") }; + QFile file { q->e2eeDataDir() % "/deviceslist.json" }; if(!file.exists() || !file.open(QIODevice::ReadOnly)) { qCDebug(E2EE) << "No devicesList cache exists. Creating new"; return; diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index b09e5260..c816eda7 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -51,7 +51,7 @@ public: } } void loadSessions() { - QFile file { static_cast(q->parent())->e2eeDataDir() + QStringLiteral("/olmsessions.json") }; + QFile file { static_cast(q->parent())->e2eeDataDir() % "/olmsessions.json" }; if(!file.exists() || !file.open(QIODevice::ReadOnly)) { qCDebug(E2EE) << "No sessions cache exists."; return; diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 2fba1973..0b4cf6d2 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -49,7 +49,7 @@ DownloadFileJob::DownloadFileJob(const QString& serverName, #ifdef Quotient_E2EE_ENABLED DownloadFileJob::DownloadFileJob(const QString& serverName, const QString& mediaId, - const EncryptedFile file, + const EncryptedFile& file, const QString& localFilename) : GetContentJob(serverName, mediaId) , d(localFilename.isEmpty() ? new Private : new Private(localFilename)) diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index 67a3e95f..90d80478 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -16,7 +16,7 @@ public: const QString& localFilename = {}); #ifdef Quotient_E2EE_ENABLED - DownloadFileJob(const QString& serverName, const QString& mediaId, const EncryptedFile file, const QString& localFilename = {}); + DownloadFileJob(const QString& serverName, const QString& mediaId, const EncryptedFile& file, const QString& localFilename = {}); #endif QString targetFileName() const; diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 639c1324..2ad49c2c 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -3,8 +3,7 @@ #include "mxcreply.h" -#include -#include +#include #include "accountregistry.h" #include "connection.h" #include "room.h" diff --git a/lib/room.cpp b/lib/room.cpp index 65ce82ac..fca1912f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -456,7 +456,7 @@ public: return false; } qCWarning(E2EE) << "Adding inbound session"; - groupSessions[qMakePair(senderKey, sessionId)] = std::move(megolmSession); + groupSessions[{senderKey, sessionId}] = std::move(megolmSession); saveMegOlmSessions(); return true; } @@ -469,13 +469,14 @@ public: { QPair senderSessionPairKey = qMakePair(senderKey, sessionId); - if (groupSessions.find(senderSessionPairKey) == groupSessions.end()) { + auto groupSessionIt = groupSessions.find(senderSessionPairKey); + if (groupSessionIt == groupSessions.end()) { qCWarning(E2EE) << "Unable to decrypt event" << eventId << "The sender's device has not sent us the keys for " "this message"; return QString(); } - auto& senderSession = groupSessions[senderSessionPairKey]; + auto& senderSession = *groupSessionIt; auto decryptResult = senderSession->decrypt(cipher); if(std::holds_alternative(decryptResult)) { qCWarning(E2EE) << "Unable to decrypt event" << eventId @@ -520,8 +521,6 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) emit baseStateLoaded(); return this == r; // loadedRoomState fires only once per room }); - qCDebug(STATE) << "New" << initialJoinState << "Room:" << id; - #ifdef Quotient_E2EE_ENABLED connectSingleShot(this, &Room::encryption, this, [=](){ connection->encryptionUpdate(this); @@ -1555,7 +1554,7 @@ RoomEventPtr Room::decryptMessage(const EncryptedEvent& encryptedEvent) qCWarning(E2EE) << "Encrypted message is empty"; return {}; } - QJsonObject eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); + auto eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); eventObject["event_id"] = encryptedEvent.id(); eventObject["sender"] = encryptedEvent.senderId(); eventObject["origin_server_ts"] = encryptedEvent.originTimestamp().toMSecsSinceEpoch(); -- cgit v1.2.3 From 31bb962f36c31621b311f1aee654e36ea09e8d77 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 24 Dec 2021 01:47:52 +0100 Subject: Fix reading unencrypted images --- lib/mxcreply.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/mxcreply.cpp') diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 2ad49c2c..c7f27b0c 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -29,6 +29,7 @@ public: MxcReply::MxcReply(QNetworkReply* reply) : d(std::make_unique(reply)) { + d->m_device = d->m_reply; reply->setParent(this); connect(d->m_reply, &QNetworkReply::finished, this, [this]() { setError(d->m_reply->error(), d->m_reply->errorString()); -- cgit v1.2.3 From a5a261b2c0dc60f99c8caa4729683d2780677f88 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 30 Dec 2021 12:46:50 +0100 Subject: Define destructors out-of-line when unique/scoped ptr are involved Once visibility kicks in, MSVC changes its ways and tries to instantiate Private classes wrapped in smart pointers upon their occurence in the header file - which leads to build breakage because of a missing destructor. Usually making the outer class destructor out-of-line helps to fix this (see RoomEvent, for one example). --- lib/jobs/downloadfilejob.cpp | 2 ++ lib/jobs/downloadfilejob.h | 1 + lib/mxcreply.cpp | 2 ++ lib/mxcreply.h | 2 ++ 4 files changed, 7 insertions(+) (limited to 'lib/mxcreply.cpp') diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 0b0531ad..6bf221cb 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -37,6 +37,8 @@ DownloadFileJob::DownloadFileJob(const QString& serverName, setObjectName(QStringLiteral("DownloadFileJob")); } +DownloadFileJob::~DownloadFileJob() = default; + QString DownloadFileJob::targetFileName() const { return (d->targetFile ? d->targetFile : d->tempFile)->fileName(); diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index d9f3b686..9e807fe7 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -13,6 +13,7 @@ public: DownloadFileJob(const QString& serverName, const QString& mediaId, const QString& localFilename = {}); + ~DownloadFileJob() override; QString targetFileName() const; diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 0b6643fc..fb16c79f 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -38,6 +38,8 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) }); } +MxcReply::~MxcReply() = default; + #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) #define ERROR_SIGNAL errorOccurred #else diff --git a/lib/mxcreply.h b/lib/mxcreply.h index 23049b7d..1d31d608 100644 --- a/lib/mxcreply.h +++ b/lib/mxcreply.h @@ -13,10 +13,12 @@ class Room; class QUOTIENT_API MxcReply : public QNetworkReply { + Q_OBJECT public: explicit MxcReply(); explicit MxcReply(QNetworkReply *reply); MxcReply(QNetworkReply* reply, Room* room, const QString& eventId); + ~MxcReply() override; public Q_SLOTS: void abort() override; -- cgit v1.2.3 From 7d37d296f942ac993d041b4576ed52265170c4a8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 2 Jan 2022 06:03:26 +0100 Subject: Add ImplPtr and makeImpl The original (more complex and comprehensive) solution belongs to https://oliora.github.io/2015/12/29/pimpl-and-rule-of-zero.html - this commit only provides a small wrapper for non-copyable Private class implementations common throughout libQuotient. Unlike the original, default initialisation is made explicit - you have to pass ZeroImpl() instead (and I firmly believe it's a good thing: normally pointers to Private should not remain nullptr). The reason ZeroImpl<> is not a template variable is quite simple: unique_ptr is non-copyable and so cannot be initialised from; while a template function will initialise the value in-place thanks to copy elision. --- lib/avatar.cpp | 12 ++++-------- lib/avatar.h | 8 ++------ lib/connection.cpp | 3 ++- lib/connection.h | 2 +- lib/connectiondata.cpp | 2 +- lib/connectiondata.h | 5 +++-- lib/jobs/basejob.cpp | 4 ++-- lib/jobs/basejob.h | 2 +- lib/jobs/downloadfilejob.cpp | 5 ++--- lib/jobs/downloadfilejob.h | 3 +-- lib/mxcreply.cpp | 7 +++---- lib/mxcreply.h | 6 ++---- lib/networkaccessmanager.cpp | 4 +--- lib/networkaccessmanager.h | 8 +++----- lib/ssosession.cpp | 2 +- lib/ssosession.h | 6 ++---- lib/user.cpp | 4 +--- lib/user.h | 5 ++--- lib/util.h | 41 +++++++++++++++++++++++++++++++++++++++++ 19 files changed, 75 insertions(+), 54 deletions(-) (limited to 'lib/mxcreply.cpp') diff --git a/lib/avatar.cpp b/lib/avatar.cpp index 77648562..9304a3de 100644 --- a/lib/avatar.cpp +++ b/lib/avatar.cpp @@ -47,15 +47,11 @@ public: mutable std::vector callbacks; }; -Avatar::Avatar() : d(std::make_unique()) {} +Avatar::Avatar() + : d(makeImpl()) +{} -Avatar::Avatar(QUrl url) : d(std::make_unique(std::move(url))) {} - -Avatar::Avatar(Avatar&&) = default; - -Avatar::~Avatar() = default; - -Avatar& Avatar::operator=(Avatar&&) = default; +Avatar::Avatar(QUrl url) : d(makeImpl(std::move(url))) {} QImage Avatar::get(Connection* connection, int dimension, get_callback_t callback) const diff --git a/lib/avatar.h b/lib/avatar.h index 93f43948..c94dc369 100644 --- a/lib/avatar.h +++ b/lib/avatar.h @@ -3,13 +3,12 @@ #pragma once -#include "quotient_export.h" +#include "util.h" #include #include #include -#include namespace Quotient { class Connection; @@ -18,9 +17,6 @@ class QUOTIENT_API Avatar { public: explicit Avatar(); explicit Avatar(QUrl url); - Avatar(Avatar&&); - ~Avatar(); - Avatar& operator=(Avatar&&); using get_callback_t = std::function; using upload_callback_t = std::function; @@ -41,6 +37,6 @@ public: private: class Private; - std::unique_ptr d; + ImplPtr d; }; } // namespace Quotient diff --git a/lib/connection.cpp b/lib/connection.cpp index 8d1c80f1..1915c2a9 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -246,7 +246,8 @@ public: }; Connection::Connection(const QUrl& server, QObject* parent) - : QObject(parent), d(new Private(std::make_unique(server))) + : QObject(parent) + , d(makeImpl(std::make_unique(server))) { d->q = this; // All d initialization should occur before this line } diff --git a/lib/connection.h b/lib/connection.h index 23062664..b4476347 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -844,7 +844,7 @@ protected Q_SLOTS: private: class Private; - QScopedPointer d; + ImplPtr d; static room_factory_t _roomFactory; static user_factory_t _userFactory; diff --git a/lib/connectiondata.cpp b/lib/connectiondata.cpp index 87ad4577..aca218be 100644 --- a/lib/connectiondata.cpp +++ b/lib/connectiondata.cpp @@ -41,7 +41,7 @@ public: }; ConnectionData::ConnectionData(QUrl baseUrl) - : d(std::make_unique(std::move(baseUrl))) + : d(makeImpl(std::move(baseUrl))) { // Each lambda invocation below takes no more than one job from the // queues (first foreground, then background) and resumes it; then diff --git a/lib/connectiondata.h b/lib/connectiondata.h index e16a2dac..75fc332f 100644 --- a/lib/connectiondata.h +++ b/lib/connectiondata.h @@ -4,9 +4,10 @@ #pragma once +#include "util.h" + #include -#include #include class QNetworkAccessManager; @@ -42,6 +43,6 @@ public: private: class Private; - std::unique_ptr d; + ImplPtr d; }; } // namespace Quotient diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 971fea7b..f518a1b0 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -194,8 +194,8 @@ BaseJob::BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint, BaseJob::BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint, const QUrlQuery& query, RequestData&& data, bool needsToken) - : d(new Private(verb, std::move(endpoint), query, std::move(data), - needsToken)) + : d(makeImpl(verb, std::move(endpoint), query, std::move(data), + needsToken)) { setObjectName(name); connect(&d->timer, &QTimer::timeout, this, &BaseJob::timeout); diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index f41fc63c..c899170d 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -467,7 +467,7 @@ private: void finishJob(); class Private; - QScopedPointer d; + ImplPtr d; }; inline bool QUOTIENT_API isJobPending(BaseJob* job) diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 6bf221cb..4a507ebd 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -32,13 +32,12 @@ DownloadFileJob::DownloadFileJob(const QString& serverName, const QString& mediaId, const QString& localFilename) : GetContentJob(serverName, mediaId) - , d(localFilename.isEmpty() ? new Private : new Private(localFilename)) + , d(localFilename.isEmpty() ? makeImpl() + : makeImpl(localFilename)) { setObjectName(QStringLiteral("DownloadFileJob")); } -DownloadFileJob::~DownloadFileJob() = default; - QString DownloadFileJob::targetFileName() const { return (d->targetFile ? d->targetFile : d->tempFile)->fileName(); diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index 9e807fe7..f8c62e4b 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -13,13 +13,12 @@ public: DownloadFileJob(const QString& serverName, const QString& mediaId, const QString& localFilename = {}); - ~DownloadFileJob() override; QString targetFileName() const; private: class Private; - QScopedPointer d; + ImplPtr d; void doPrepare() override; void onSentRequest(QNetworkReply* reply) override; diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index fb16c79f..d3cc3c37 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -17,7 +17,7 @@ public: }; MxcReply::MxcReply(QNetworkReply* reply) - : d(std::make_unique(reply)) + : d(makeImpl(reply)) { reply->setParent(this); connect(d->m_reply, &QNetworkReply::finished, this, [this]() { @@ -28,7 +28,7 @@ MxcReply::MxcReply(QNetworkReply* reply) } MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) - : d(std::make_unique(reply)) + : d(makeImpl(reply)) { reply->setParent(this); connect(d->m_reply, &QNetworkReply::finished, this, [this, room, eventId]() { @@ -38,8 +38,6 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) }); } -MxcReply::~MxcReply() = default; - #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) #define ERROR_SIGNAL errorOccurred #else @@ -47,6 +45,7 @@ MxcReply::~MxcReply() = default; #endif MxcReply::MxcReply() + : d(ZeroImpl()) { static const auto BadRequestPhrase = tr("Bad Request"); QMetaObject::invokeMethod(this, [this]() { diff --git a/lib/mxcreply.h b/lib/mxcreply.h index 1d31d608..f6c4a34d 100644 --- a/lib/mxcreply.h +++ b/lib/mxcreply.h @@ -3,10 +3,9 @@ #pragma once -#include "quotient_export.h" +#include "util.h" #include -#include namespace Quotient { class Room; @@ -18,7 +17,6 @@ public: explicit MxcReply(); explicit MxcReply(QNetworkReply *reply); MxcReply(QNetworkReply* reply, Room* room, const QString& eventId); - ~MxcReply() override; public Q_SLOTS: void abort() override; @@ -28,6 +26,6 @@ protected: private: class Private; - std::unique_ptr d; + ImplPtr d; }; } diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index 57618329..2c0f716b 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -39,7 +39,7 @@ public: }; NetworkAccessManager::NetworkAccessManager(QObject* parent) - : QNetworkAccessManager(parent), d(std::make_unique(this)) + : QNetworkAccessManager(parent), d(makeImpl(this)) {} QList NetworkAccessManager::ignoredSslErrors() const @@ -79,8 +79,6 @@ NetworkAccessManager* NetworkAccessManager::instance() return storage.localData(); } -NetworkAccessManager::~NetworkAccessManager() = default; - QNetworkReply* NetworkAccessManager::createRequest( Operation op, const QNetworkRequest& request, QIODevice* outgoingData) { diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h index d06f9736..5a9c134c 100644 --- a/lib/networkaccessmanager.h +++ b/lib/networkaccessmanager.h @@ -3,20 +3,18 @@ #pragma once -#include "quotient_export.h" +#include "util.h" #include -#include - namespace Quotient { class Room; class Connection; + class QUOTIENT_API NetworkAccessManager : public QNetworkAccessManager { Q_OBJECT public: NetworkAccessManager(QObject* parent = nullptr); - ~NetworkAccessManager() override; QList ignoredSslErrors() const; void addIgnoredSslError(const QSslError& error); @@ -33,6 +31,6 @@ private: QIODevice* outgoingData = Q_NULLPTR) override; class Private; - std::unique_ptr d; + ImplPtr d; }; } // namespace Quotient diff --git a/lib/ssosession.cpp b/lib/ssosession.cpp index a1d27504..5f3479b8 100644 --- a/lib/ssosession.cpp +++ b/lib/ssosession.cpp @@ -61,7 +61,7 @@ public: SsoSession::SsoSession(Connection* connection, const QString& initialDeviceName, const QString& deviceId) : QObject(connection) - , d(std::make_unique(this, initialDeviceName, deviceId, connection)) + , d(makeImpl(this, initialDeviceName, deviceId, connection)) { qCDebug(MAIN) << "SSO session constructed"; } diff --git a/lib/ssosession.h b/lib/ssosession.h index a658c043..0f3fc3b8 100644 --- a/lib/ssosession.h +++ b/lib/ssosession.h @@ -3,13 +3,11 @@ #pragma once -#include "quotient_export.h" +#include "util.h" #include #include -#include - class QTcpServer; class QTcpSocket; @@ -44,6 +42,6 @@ public: private: class Private; - std::unique_ptr d; + ImplPtr d; }; } // namespace Quotient diff --git a/lib/user.cpp b/lib/user.cpp index 7da71dba..0dbc444a 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -46,7 +46,7 @@ public: decltype(User::Private::otherAvatars) User::Private::otherAvatars {}; User::User(QString userId, Connection* connection) - : QObject(connection), d(new Private(move(userId))) + : QObject(connection), d(makeImpl(move(userId))) { setObjectName(id()); if (connection->userId() == id()) { @@ -61,8 +61,6 @@ Connection* User::connection() const return static_cast(parent()); } -User::~User() = default; - void User::load() { auto* profileJob = diff --git a/lib/user.h b/lib/user.h index 435304ce..8412b7fd 100644 --- a/lib/user.h +++ b/lib/user.h @@ -5,7 +5,7 @@ #pragma once #include "avatar.h" -#include "quotient_export.h" +#include "util.h" #include @@ -27,7 +27,6 @@ class QUOTIENT_API User : public QObject { Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY defaultAvatarChanged) public: User(QString userId, Connection* connection); - ~User() override; Connection* connection() const; @@ -126,7 +125,7 @@ Q_SIGNALS: private: class Private; - QScopedPointer d; + ImplPtr d; template bool doSetAvatar(SourceT&& source); diff --git a/lib/util.h b/lib/util.h index 399f93c2..f5650ee2 100644 --- a/lib/util.h +++ b/lib/util.h @@ -245,6 +245,47 @@ inline std::pair findFirstOf(InputIt first, InputIt last, return std::make_pair(last, sLast); } +//! \brief An owning implementation pointer +//! +//! This is basically std::unique_ptr<> to hold your pimpl's but without having +//! to define default constructors/operator=() out of line. +//! Thanks to https://oliora.github.io/2015/12/29/pimpl-and-rule-of-zero.html +//! for inspiration +template +using ImplPtr = std::unique_ptr; + +// Why this works (see also the link above): because this defers the moment +// of requiring sizeof of ImplType to the place where makeImpl is invoked +// (which is located, necessarily, in the .cpp file after ImplType definition). +// The stock unique_ptr deleter (std::default_delete) normally needs sizeof +// at the same spot - as long as you defer definition of the owning type +// constructors and operator='s to the .cpp file as well. Which means you +// have to explicitly declare and define them (even if with = default), +// formally breaking the rule of zero; informally, just adding boilerplate code. +// The custom deleter itself is instantiated at makeImpl invocation - there's +// no way earlier to even know how ImplType will be deleted and whether that +// will need sizeof(ImplType) earlier. In theory it's a tad slower because +// the deleter is called by the pointer; however, the difference will not +// be noticeable (if exist at all) for any class with non-trivial contents. + +//! \brief make_unique for ImplPtr +//! +//! Since std::make_unique is not compatible with ImplPtr, this should be used +//! in constructors of frontend classes to create implementation instances. +template +inline ImplPtr makeImpl(ArgTs&&... args) +{ + return ImplPtr { new ImplType(std::forward(args)...), + [](ImplType* impl) { delete impl; } }; +} + +template +const inline ImplPtr ZeroImpl() +{ + return { nullptr, [](ImplType*) {} }; +} + /** Convert what looks like a URL or a Matrix ID to an HTML hyperlink */ QUOTIENT_API void linkifyUrls(QString& htmlEscapedText); -- cgit v1.2.3 From 7b5edb737522b03d4f697e0e09f1771ad5edef89 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 7 Feb 2022 21:48:07 +0100 Subject: Remove encryptionmanager and various fixes --- CMakeLists.txt | 2 +- autotests/testolmaccount.cpp | 2 +- lib/connection.cpp | 108 ++++++++++++++++++++++++----- lib/connection.h | 2 - lib/e2ee/qolminboundsession.cpp | 2 + lib/encryptionmanager.cpp | 149 ---------------------------------------- lib/encryptionmanager.h | 33 --------- lib/events/encryptedfile.cpp | 27 ++++++++ lib/events/encryptedfile.h | 6 ++ lib/jobs/downloadfilejob.cpp | 5 +- lib/mxcreply.cpp | 3 +- 11 files changed, 130 insertions(+), 209 deletions(-) delete mode 100644 lib/encryptionmanager.cpp delete mode 100644 lib/encryptionmanager.h create mode 100644 lib/events/encryptedfile.cpp (limited to 'lib/mxcreply.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ef3477e..69ac7e20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,6 +156,7 @@ list(APPEND lib_SRCS lib/events/roomkeyevent.cpp lib/events/stickerevent.cpp lib/events/keyverificationevent.cpp + lib/events/encryptedfile.cpp lib/jobs/requestdata.cpp lib/jobs/basejob.cpp lib/jobs/syncjob.cpp @@ -174,7 +175,6 @@ if (${PROJECT_NAME}_ENABLE_E2EE) lib/e2ee/qolmerrors.cpp lib/e2ee/qolmsession.cpp lib/e2ee/qolmmessage.cpp - lib/encryptionmanager.cpp ) endif() diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 45d158eb..62b786d0 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -537,7 +537,7 @@ void TestOlmAccount::enableEncryption() QString joinedRoom; auto job = alice->createRoom(Connection::PublishRoom, QString(), QString(), QString(), {"@bob:localhost"}); - connect(alice.get(), &Connection::newRoom, this, [alice, bob, joinedRoom, this] (Quotient::Room *room) { + connect(alice.get(), &Connection::newRoom, this, [alice, bob, &joinedRoom, this] (Quotient::Room *room) { room->activateEncryption(); QSignalSpy spy(room, &Room::encryption); diff --git a/lib/connection.cpp b/lib/connection.cpp index 58e3a9f8..1a1b284d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -37,7 +37,6 @@ #ifdef Quotient_E2EE_ENABLED # include "e2ee/qolmaccount.h" # include "e2ee/qolmutils.h" -# include "encryptionmanager.h" # include "database.h" #if QT_VERSION_MAJOR >= 6 @@ -117,6 +116,10 @@ public: bool encryptionUpdateRequired = false; PicklingMode picklingMode = Unencrypted {}; Database *database = nullptr; + + // A map from SenderKey to vector of InboundSession + UnorderedMap> olmSessions; + #endif GetCapabilitiesJob* capabilitiesJob = nullptr; @@ -127,7 +130,6 @@ public: #ifdef Quotient_E2EE_ENABLED std::unique_ptr olmAccount; bool isUploadingKeys = false; - EncryptionManager *encryptionManager; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -201,6 +203,85 @@ public: return q->stateCacheDir().filePath("state.json"); } +#ifdef Quotient_E2EE_ENABLED + void loadSessions() { + olmSessions = q->database()->loadOlmSessions(q->picklingMode()); + } + void saveSession(QOlmSessionPtr& session, const QString &senderKey) { + auto pickleResult = session->pickle(q->picklingMode()); + if (std::holds_alternative(pickleResult)) { + qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); + return; + } + q->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult)); + } + QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) + { + Q_ASSERT(message.type() == QOlmMessage::PreKey); + for(auto& session : olmSessions[senderKey]) { + const auto matches = session->matchesInboundSessionFrom(senderKey, message); + if(std::holds_alternative(matches) && std::get(matches)) { + qCDebug(E2EE) << "Found inbound session"; + const auto result = session->decrypt(message); + if(std::holds_alternative(result)) { + return std::get(result); + } else { + qCDebug(E2EE) << "Failed to decrypt prekey message"; + return {}; + } + } + } + qCDebug(E2EE) << "Creating new inbound session"; + auto newSessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), message); + if(std::holds_alternative(newSessionResult)) { + qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get(newSessionResult); + return {}; + } + auto newSession = std::move(std::get(newSessionResult)); + auto error = olmAccount->removeOneTimeKeys(newSession); + if (error) { + qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); + } + const auto result = newSession->decrypt(message); + saveSession(newSession, senderKey); + olmSessions[senderKey].push_back(std::move(newSession)); + if(std::holds_alternative(result)) { + return std::get(result); + } else { + qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; + return {}; + } + } + QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) + { + Q_ASSERT(message.type() == QOlmMessage::General); + for(auto& session : olmSessions[senderKey]) { + const auto result = session->decrypt(message); + if(std::holds_alternative(result)) { + return std::get(result); + } + } + qCWarning(E2EE) << "Failed to decrypt message"; + return {}; + } + + QString sessionDecryptMessage( + const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account) + { + QString decrypted; + int type = personalCipherObject.value(TypeKeyL).toInt(-1); + QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); + if (type == 0) { + QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); + decrypted = sessionDecryptPrekey(preKeyMessage, senderKey, account); + } else if (type == 1) { + QOlmMessage message(body, QOlmMessage::General); + decrypted = sessionDecryptGeneral(message, senderKey); + } + return decrypted; + } +#endif + EventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) { #ifndef Quotient_E2EE_ENABLED @@ -217,7 +298,7 @@ public: qCDebug(E2EE) << "Encrypted event is not for the current device"; return {}; } - const auto decrypted = encryptionManager->sessionDecryptMessage( + const auto decrypted = sessionDecryptMessage( personalCipherObject, encryptedEvent.senderKey().toLatin1(), olmAccount); if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" @@ -443,6 +524,7 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #endif // Quotient_E2EE_ENABLED + database->clear(); }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); @@ -493,13 +575,15 @@ void Connection::Private::completeSetup(const QString& mxId) picklingMode = Encrypted { job.binaryData() }; } + database = new Database(data->userId(), q); + // init olmAccount olmAccount = std::make_unique(data->userId(), data->deviceId(), q); connect(olmAccount.get(), &QOlmAccount::needsSave, q, &Connection::saveOlmAccount); - database = new Database(data->userId(), q); - - encryptionManager = new EncryptionManager(q); +#ifdef Quotient_E2EE_ENABLED + loadSessions(); +#endif if (database->accountPickle().isEmpty()) { // create new account and save unpickle data @@ -2019,18 +2103,6 @@ void Connection::saveOlmAccount() #endif } -QString Connection::e2eeDataDir() const -{ - auto safeUserId = userId(); - safeUserId.replace(':', '_'); - const QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) % '/' - % safeUserId % '/'; - QDir dir; - if (!dir.exists(path)) - dir.mkpath(path); - return path; -} - #ifdef Quotient_E2EE_ENABLED QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) { diff --git a/lib/connection.h b/lib/connection.h index 93ee496e..8dec2a0c 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -401,8 +401,6 @@ public: bool lazyLoading() const; void setLazyLoading(bool newValue); - QString e2eeDataDir() const; - /*! Start a pre-created job object on this connection */ Q_INVOKABLE BaseJob* run(BaseJob* job, RunningPolicy runningPolicy = ForegroundRequest); diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 9bc80eef..2e9cc716 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -4,6 +4,8 @@ #include "e2ee/qolminboundsession.h" #include +#include + using namespace Quotient; QOlmError lastError(OlmInboundGroupSession *session) { diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp deleted file mode 100644 index abdcdcee..00000000 --- a/lib/encryptionmanager.cpp +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-FileCopyrightText: 2019 Kitsune Ral -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "encryptionmanager.h" - -#include "connection.h" -#include "events/encryptedfile.h" -#include "database.h" - -#include "csapi/keys.h" - -#include -#include -#include - -#include "e2ee/e2ee.h" -#include "e2ee/qolmaccount.h" -#include "e2ee/qolmsession.h" -#include "e2ee/qolmmessage.h" -#include "e2ee/qolmerrors.h" -#include "e2ee/qolmutils.h" -#include -#include - -#include - -using namespace Quotient; -using std::move; - -class EncryptionManager::Private { -public: - EncryptionManager* q; - - Connection* connection; - - // A map from SenderKey to vector of InboundSession - UnorderedMap> sessions; - - void loadSessions() { - sessions = connection->database()->loadOlmSessions(connection->picklingMode()); - } - void saveSession(QOlmSessionPtr& session, const QString &senderKey) { - auto pickleResult = session->pickle(connection->picklingMode()); - if (std::holds_alternative(pickleResult)) { - qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); - return; - } - connection->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult)); - } - QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) - { - Q_ASSERT(message.type() == QOlmMessage::PreKey); - for(auto& session : sessions[senderKey]) { - const auto matches = session->matchesInboundSessionFrom(senderKey, message); - if(std::holds_alternative(matches) && std::get(matches)) { - qCDebug(E2EE) << "Found inbound session"; - const auto result = session->decrypt(message); - if(std::holds_alternative(result)) { - return std::get(result); - } else { - qCDebug(E2EE) << "Failed to decrypt prekey message"; - return {}; - } - } - } - qCDebug(E2EE) << "Creating new inbound session"; - auto newSessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), message); - if(std::holds_alternative(newSessionResult)) { - qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get(newSessionResult); - return {}; - } - auto newSession = std::move(std::get(newSessionResult)); - auto error = olmAccount->removeOneTimeKeys(newSession); - if (error) { - qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); - } - const auto result = newSession->decrypt(message); - saveSession(newSession, senderKey); - sessions[senderKey].push_back(std::move(newSession)); - if(std::holds_alternative(result)) { - return std::get(result); - } else { - qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; - return {}; - } - } - QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) - { - Q_ASSERT(message.type() == QOlmMessage::General); - for(auto& session : sessions[senderKey]) { - const auto result = session->decrypt(message); - if(std::holds_alternative(result)) { - return std::get(result); - } - } - qCWarning(E2EE) << "Failed to decrypt message"; - return {}; - } -}; - -EncryptionManager::EncryptionManager(QObject* parent) - : QObject(parent) - , d(std::make_unique()) -{ - d->q = this; - d->connection = static_cast(parent); - d->loadSessions(); -} - -EncryptionManager::~EncryptionManager() = default; - -QString EncryptionManager::sessionDecryptMessage( - const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account) -{ - QString decrypted; - int type = personalCipherObject.value(TypeKeyL).toInt(-1); - QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); - if (type == 0) { - QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); - decrypted = d->sessionDecryptPrekey(preKeyMessage, senderKey, account); - } else if (type == 1) { - QOlmMessage message(body, QOlmMessage::General); - decrypted = d->sessionDecryptGeneral(message, senderKey); - } - return decrypted; -} - -QByteArray EncryptionManager::decryptFile(const QByteArray &ciphertext, EncryptedFile* file) -{ - const auto key = QByteArray::fromBase64(file->key.k.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); - const auto iv = QByteArray::fromBase64(file->iv.toLatin1()); - const auto sha256 = QByteArray::fromBase64(file->hashes["sha256"].toLatin1()); - if(sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { - qCWarning(E2EE) << "Hash verification failed for file"; - return QByteArray(); - } - QByteArray plaintext(ciphertext.size(), 0); - EVP_CIPHER_CTX *ctx; - int length; - ctx = EVP_CIPHER_CTX_new(); - EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)key.data(), (const unsigned char *)iv.data()); - EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); - EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); - EVP_CIPHER_CTX_free(ctx); - return plaintext; -} -#endif // Quotient_E2EE_ENABLED diff --git a/lib/encryptionmanager.h b/lib/encryptionmanager.h deleted file mode 100644 index 96569980..00000000 --- a/lib/encryptionmanager.h +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#pragma once - -#include - -#include -#include - -namespace Quotient { -class Connection; -class QOlmAccount; -struct EncryptedFile; - -class EncryptionManager : public QObject { - Q_OBJECT - -public: - explicit EncryptionManager(QObject* parent = nullptr); - ~EncryptionManager(); - QString sessionDecryptMessage(const QJsonObject& personalCipherObject, - const QByteArray& senderKey, std::unique_ptr& account); - static QByteArray decryptFile(const QByteArray &ciphertext, EncryptedFile* encryptedFile); - -private: - class Private; - std::unique_ptr d; -}; - -} // namespace Quotient -#endif // Quotient_E2EE_ENABLED diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp new file mode 100644 index 00000000..5ec344bb --- /dev/null +++ b/lib/events/encryptedfile.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "encryptedfile.h" + +using namespace Quotient; + +QByteArray EncryptedFile::decryptFile(const QByteArray &ciphertext) const +{ + QString _key = key.k; + _key = QByteArray::fromBase64(_key.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); + const auto sha256 = QByteArray::fromBase64(hashes["sha256"].toLatin1()); + if(sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { + qCWarning(E2EE) << "Hash verification failed for file"; + return QByteArray(); + } + QByteArray plaintext(ciphertext.size(), 0); + EVP_CIPHER_CTX *ctx; + int length; + ctx = EVP_CIPHER_CTX_new(); + EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)_key.data(), (const unsigned char *)iv.toLatin1().data()); + EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); + EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); + EVP_CIPHER_CTX_free(ctx); + return plaintext; +} diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 24ac9de1..f271d345 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -5,6 +5,10 @@ #pragma once #include "converters.h" +#include "logging.h" + +#include +#include namespace Quotient { /** @@ -44,6 +48,8 @@ public: QString iv; QHash hashes; QString v; + + QByteArray decryptFile(const QByteArray &ciphertext) const; }; template <> diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 2eea9d59..c5280770 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -9,7 +9,6 @@ #ifdef Quotient_E2EE_ENABLED # include -# include "encryptionmanager.h" # include "events/encryptedfile.h" #endif @@ -126,7 +125,7 @@ BaseJob::Status DownloadFileJob::prepareResult() QByteArray encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = EncryptionManager::decryptFile(encrypted, &file); + auto decrypted = file.decryptFile(encrypted); d->targetFile->write(decrypted); d->tempFile->remove(); } else { @@ -151,7 +150,7 @@ BaseJob::Status DownloadFileJob::prepareResult() auto encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = EncryptionManager::decryptFile(encrypted, &file); + auto decrypted = file.decryptFile(encrypted); d->tempFile->write(decrypted); } else { #endif diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index c7f27b0c..c666cce3 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -9,7 +9,6 @@ #include "room.h" #ifdef Quotient_E2EE_ENABLED -#include "encryptionmanager.h" #include "events/encryptedfile.h" #endif @@ -51,7 +50,7 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) } else { EncryptedFile file = *d->m_encryptedFile; auto buffer = new QBuffer(this); - buffer->setData(EncryptionManager::decryptFile(d->m_reply->readAll(), &file)); + buffer->setData(file.decryptFile(d->m_reply->readAll())); buffer->open(ReadOnly); d->m_device = buffer; } -- cgit v1.2.3 From 494099a4b51b4d537a40a89c9d642fe3bd61e559 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 11 Mar 2022 15:05:39 +0100 Subject: Fix loading images when E2EE is disabled --- lib/mxcreply.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/mxcreply.cpp') diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 1d40c5e1..b757bb93 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -54,11 +54,11 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) buffer->open(ReadOnly); d->m_device = buffer; } - setOpenMode(ReadOnly); - emit finished(); #else d->m_device = d->m_reply; #endif + setOpenMode(ReadOnly); + emit finished(); }); #ifdef Quotient_E2EE_ENABLED -- cgit v1.2.3 From 408785f0680a531c493de225fad60b6369227cfb Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 23 Apr 2022 19:47:14 +0200 Subject: Cleanup --- lib/mxcreply.cpp | 1 - lib/util.h | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'lib/mxcreply.cpp') diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index b757bb93..319d514a 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -5,7 +5,6 @@ #include #include "accountregistry.h" -#include "connection.h" #include "room.h" #ifdef Quotient_E2EE_ENABLED diff --git a/lib/util.h b/lib/util.h index b14e1648..3910059b 100644 --- a/lib/util.h +++ b/lib/util.h @@ -109,8 +109,8 @@ private: */ template inline std::pair findFirstOf(InputIt first, InputIt last, - ForwardIt sFirst, - ForwardIt sLast, Pred pred) + ForwardIt sFirst, + ForwardIt sLast, Pred pred) { for (; first != last; ++first) for (auto it = sFirst; it != sLast; ++it) @@ -156,7 +156,7 @@ inline ImplPtr makeImpl(ArgTs&&... args) } template -const inline ImplPtr ZeroImpl() +constexpr ImplPtr ZeroImpl() { return { nullptr, [](ImplType*) { /* nullptr doesn't need deletion */ } }; } -- cgit v1.2.3 From 0b5e72a2c6502f22a752b72b4df5fa25746fdd25 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 26 May 2022 08:51:22 +0200 Subject: Refactor EncryptedFile and EC::FileInfo::file Besides having a misleading name (and it goes back to the spec), EncryptedFile under `file` key preempts the `url` (or `thumbnail_url`) string value so only one of the two should exist. This is a case for using std::variant<> - despite its clumsy syntax, it can actually simplify and streamline code when all the necessary bits are in place (such as conversion to JSON and getting the common piece - the URL - out of it). This commit replaces `FileInfo::url` and `FileInfo::file` with a common field `source` of type `FileSourceInfo` that is an alias for a variant type covering both underlying types; and `url()` is reintroduced as a function instead, to allow simplified access to whichever URL is available inside the variant. Oh, and EncryptedFile is EncryptedFileMetadata now, to clarify that it does not represent the file payload itself but rather the data necessary to obtain that payload. --- CMakeLists.txt | 2 +- autotests/testfilecrypto.cpp | 6 +- autotests/testolmaccount.cpp | 5 +- lib/connection.cpp | 11 ++- lib/connection.h | 5 +- lib/converters.h | 8 ++ lib/eventitem.cpp | 21 ++--- lib/eventitem.h | 9 +- lib/events/encryptedfile.cpp | 119 -------------------------- lib/events/encryptedfile.h | 63 -------------- lib/events/eventcontent.cpp | 68 +++++++-------- lib/events/eventcontent.h | 53 ++++++------ lib/events/filesourceinfo.cpp | 181 ++++++++++++++++++++++++++++++++++++++++ lib/events/filesourceinfo.h | 89 ++++++++++++++++++++ lib/events/roomavatarevent.h | 4 +- lib/events/roommessageevent.cpp | 8 +- lib/events/stickerevent.cpp | 2 +- lib/jobs/downloadfilejob.cpp | 13 +-- lib/jobs/downloadfilejob.h | 5 +- lib/mxcreply.cpp | 10 ++- lib/room.cpp | 85 +++++++++---------- lib/room.h | 3 +- quotest/quotest.cpp | 2 +- 23 files changed, 427 insertions(+), 345 deletions(-) delete mode 100644 lib/events/encryptedfile.cpp delete mode 100644 lib/events/encryptedfile.h create mode 100644 lib/events/filesourceinfo.cpp create mode 100644 lib/events/filesourceinfo.h (limited to 'lib/mxcreply.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index 404ba87c..635efd90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,7 +168,7 @@ list(APPEND lib_SRCS lib/events/roomkeyevent.h lib/events/roomkeyevent.cpp lib/events/stickerevent.h lib/events/stickerevent.cpp lib/events/keyverificationevent.h lib/events/keyverificationevent.cpp - lib/events/encryptedfile.h lib/events/encryptedfile.cpp + lib/events/filesourceinfo.h lib/events/filesourceinfo.cpp lib/jobs/requestdata.h lib/jobs/requestdata.cpp lib/jobs/basejob.h lib/jobs/basejob.cpp lib/jobs/syncjob.h lib/jobs/syncjob.cpp diff --git a/autotests/testfilecrypto.cpp b/autotests/testfilecrypto.cpp index f9212376..b86114a4 100644 --- a/autotests/testfilecrypto.cpp +++ b/autotests/testfilecrypto.cpp @@ -3,14 +3,16 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testfilecrypto.h" -#include "events/encryptedfile.h" + +#include "events/filesourceinfo.h" + #include using namespace Quotient; void TestFileCrypto::encryptDecryptData() { QByteArray data = "ABCDEF"; - auto [file, cipherText] = EncryptedFile::encryptFile(data); + auto [file, cipherText] = EncryptedFileMetadata::encryptFile(data); auto decrypted = file.decryptFile(cipherText); // AES CTR produces ciphertext of the same size as the original QCOMPARE(cipherText.size(), data.size()); diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index e31ff6d3..b509d12f 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include @@ -156,8 +156,7 @@ void TestOlmAccount::encryptedFile() "sha256": "fdSLu/YkRx3Wyh3KQabP3rd6+SFiKg5lsJZQHtkSAYA" }})"); - EncryptedFile file; - JsonObjectConverter::fillFrom(doc.object(), file); + const auto file = fromJson(doc); QCOMPARE(file.v, "v2"); QCOMPARE(file.iv, "w+sE15fzSc0AAAAAAAAAAA"); diff --git a/lib/connection.cpp b/lib/connection.cpp index dba18cb1..0994d85a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1137,15 +1137,14 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, } #ifdef Quotient_E2EE_ENABLED -DownloadFileJob* Connection::downloadFile(const QUrl& url, - const EncryptedFile& file, - const QString& localFilename) +DownloadFileJob* Connection::downloadFile( + const QUrl& url, const EncryptedFileMetadata& fileMetadata, + const QString& localFilename) { auto mediaId = url.authority() + url.path(); auto idParts = splitMediaId(mediaId); - auto* job = - callApi(idParts.front(), idParts.back(), file, localFilename); - return job; + return callApi(idParts.front(), idParts.back(), + fileMetadata, localFilename); } #endif diff --git a/lib/connection.h b/lib/connection.h index f8744752..656e597c 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -51,7 +51,7 @@ class SendToDeviceJob; class SendMessageJob; class LeaveRoomJob; class Database; -struct EncryptedFile; +struct EncryptedFileMetadata; class QOlmAccount; class QOlmInboundGroupSession; @@ -601,7 +601,8 @@ public Q_SLOTS: const QString& localFilename = {}); #ifdef Quotient_E2EE_ENABLED - DownloadFileJob* downloadFile(const QUrl& url, const EncryptedFile& file, + DownloadFileJob* downloadFile(const QUrl& url, + const EncryptedFileMetadata& file, const QString& localFilename = {}); #endif /** diff --git a/lib/converters.h b/lib/converters.h index 5e3becb8..49cb1ed9 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -16,6 +16,7 @@ #include #include +#include class QVariant; @@ -224,6 +225,13 @@ struct QUOTIENT_API JsonConverter { static QVariant load(const QJsonValue& jv); }; +template +inline QJsonValue toJson(const std::variant& v) +{ + return std::visit( + [](const auto& value) { return QJsonValue { toJson(value) }; }, v); +} + template struct JsonConverter> { static QJsonValue dump(const Omittable& from) diff --git a/lib/eventitem.cpp b/lib/eventitem.cpp index 302ae053..a2e2a156 100644 --- a/lib/eventitem.cpp +++ b/lib/eventitem.cpp @@ -8,32 +8,23 @@ using namespace Quotient; -void PendingEventItem::setFileUploaded(const QUrl& remoteUrl) +void PendingEventItem::setFileUploaded(const FileSourceInfo& uploadedFileData) { // TODO: eventually we might introduce hasFileContent to RoomEvent, // and unify the code below. if (auto* rme = getAs()) { Q_ASSERT(rme->hasFileContent()); - rme->editContent([remoteUrl](EventContent::TypedBase& ec) { - ec.fileInfo()->url = remoteUrl; + rme->editContent([&uploadedFileData](EventContent::TypedBase& ec) { + ec.fileInfo()->source = uploadedFileData; }); } if (auto* rae = getAs()) { Q_ASSERT(rae->content().fileInfo()); - rae->editContent( - [remoteUrl](EventContent::FileInfo& fi) { fi.url = remoteUrl; }); - } - setStatus(EventStatus::FileUploaded); -} - -void PendingEventItem::setEncryptedFile(const EncryptedFile& encryptedFile) -{ - if (auto* rme = getAs()) { - Q_ASSERT(rme->hasFileContent()); - rme->editContent([encryptedFile](EventContent::TypedBase& ec) { - ec.fileInfo()->file = encryptedFile; + rae->editContent([&uploadedFileData](EventContent::FileInfo& fi) { + fi.source = uploadedFileData; }); } + setStatus(EventStatus::FileUploaded); } // Not exactly sure why but this helps with the linker not finding diff --git a/lib/eventitem.h b/lib/eventitem.h index d8313736..5e001d88 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -3,14 +3,14 @@ #pragma once -#include "events/stateevent.h" #include "quotient_common.h" +#include "events/filesourceinfo.h" +#include "events/stateevent.h" + #include #include -#include "events/encryptedfile.h" - namespace Quotient { namespace EventStatus { @@ -115,8 +115,7 @@ public: QString annotation() const { return _annotation; } void setDeparted() { setStatus(EventStatus::Departed); } - void setFileUploaded(const QUrl& remoteUrl); - void setEncryptedFile(const EncryptedFile& encryptedFile); + void setFileUploaded(const FileSourceInfo &uploadedFileData); void setReachedServer(const QString& eventId) { setStatus(EventStatus::ReachedServer); diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp deleted file mode 100644 index 33ebb514..00000000 --- a/lib/events/encryptedfile.cpp +++ /dev/null @@ -1,119 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "encryptedfile.h" -#include "logging.h" - -#ifdef Quotient_E2EE_ENABLED -#include -#include -#include "e2ee/qolmutils.h" -#endif - -using namespace Quotient; - -QByteArray EncryptedFile::decryptFile(const QByteArray& ciphertext) const -{ -#ifdef Quotient_E2EE_ENABLED - auto _key = key.k; - const auto keyBytes = QByteArray::fromBase64( - _key.replace(u'_', u'/').replace(u'-', u'+').toLatin1()); - const auto sha256 = QByteArray::fromBase64(hashes["sha256"].toLatin1()); - if (sha256 - != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { - qCWarning(E2EE) << "Hash verification failed for file"; - return {}; - } - { - int length; - auto* ctx = EVP_CIPHER_CTX_new(); - QByteArray plaintext(ciphertext.size() + EVP_MAX_BLOCK_LENGTH - - 1, - '\0'); - EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, - reinterpret_cast( - keyBytes.data()), - reinterpret_cast( - QByteArray::fromBase64(iv.toLatin1()).data())); - EVP_DecryptUpdate( - ctx, reinterpret_cast(plaintext.data()), &length, - reinterpret_cast(ciphertext.data()), - ciphertext.size()); - EVP_DecryptFinal_ex(ctx, - reinterpret_cast(plaintext.data()) - + length, - &length); - EVP_CIPHER_CTX_free(ctx); - return plaintext.left(ciphertext.size()); - } -#else - qWarning(MAIN) << "This build of libQuotient doesn't support E2EE, " - "cannot decrypt the file"; - return ciphertext; -#endif -} - -std::pair EncryptedFile::encryptFile(const QByteArray &plainText) -{ -#ifdef Quotient_E2EE_ENABLED - QByteArray k = getRandom(32); - auto kBase64 = k.toBase64(); - QByteArray iv = getRandom(16); - JWK key = {"oct"_ls, {"encrypt"_ls, "decrypt"_ls}, "A256CTR"_ls, QString(k.toBase64()).replace(u'/', u'_').replace(u'+', u'-').left(kBase64.indexOf('=')), true}; - - int length; - auto* ctx = EVP_CIPHER_CTX_new(); - EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, reinterpret_cast(k.data()),reinterpret_cast(iv.data())); - const auto blockSize = EVP_CIPHER_CTX_block_size(ctx); - QByteArray cipherText(plainText.size() + blockSize - 1, '\0'); - EVP_EncryptUpdate(ctx, reinterpret_cast(cipherText.data()), &length, reinterpret_cast(plainText.data()), plainText.size()); - EVP_EncryptFinal_ex(ctx, reinterpret_cast(cipherText.data()) + length, &length); - EVP_CIPHER_CTX_free(ctx); - - auto hash = QCryptographicHash::hash(cipherText, QCryptographicHash::Sha256).toBase64(); - auto ivBase64 = iv.toBase64(); - EncryptedFile file = {{}, key, ivBase64.left(ivBase64.indexOf('=')), {{QStringLiteral("sha256"), hash.left(hash.indexOf('='))}}, "v2"_ls}; - return {file, cipherText}; -#else - return {}; -#endif -} - -void JsonObjectConverter::dumpTo(QJsonObject& jo, - const EncryptedFile& pod) -{ - addParam<>(jo, QStringLiteral("url"), pod.url); - addParam<>(jo, QStringLiteral("key"), pod.key); - addParam<>(jo, QStringLiteral("iv"), pod.iv); - addParam<>(jo, QStringLiteral("hashes"), pod.hashes); - addParam<>(jo, QStringLiteral("v"), pod.v); -} - -void JsonObjectConverter::fillFrom(const QJsonObject& jo, - EncryptedFile& pod) -{ - fromJson(jo.value("url"_ls), pod.url); - fromJson(jo.value("key"_ls), pod.key); - fromJson(jo.value("iv"_ls), pod.iv); - fromJson(jo.value("hashes"_ls), pod.hashes); - fromJson(jo.value("v"_ls), pod.v); -} - -void JsonObjectConverter::dumpTo(QJsonObject &jo, const JWK &pod) -{ - addParam<>(jo, QStringLiteral("kty"), pod.kty); - addParam<>(jo, QStringLiteral("key_ops"), pod.keyOps); - addParam<>(jo, QStringLiteral("alg"), pod.alg); - addParam<>(jo, QStringLiteral("k"), pod.k); - addParam<>(jo, QStringLiteral("ext"), pod.ext); -} - -void JsonObjectConverter::fillFrom(const QJsonObject &jo, JWK &pod) -{ - fromJson(jo.value("kty"_ls), pod.kty); - fromJson(jo.value("key_ops"_ls), pod.keyOps); - fromJson(jo.value("alg"_ls), pod.alg); - fromJson(jo.value("k"_ls), pod.k); - fromJson(jo.value("ext"_ls), pod.ext); -} diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h deleted file mode 100644 index 022ac91e..00000000 --- a/lib/events/encryptedfile.h +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include "converters.h" - -namespace Quotient { -/** - * JSON Web Key object as specified in - * https://spec.matrix.org/unstable/client-server-api/#extensions-to-mroommessage-msgtypes - * The only currently relevant member is `k`, the rest needs to be set to the defaults specified in the spec. - */ -struct JWK -{ - Q_GADGET - Q_PROPERTY(QString kty MEMBER kty CONSTANT) - Q_PROPERTY(QStringList keyOps MEMBER keyOps CONSTANT) - Q_PROPERTY(QString alg MEMBER alg CONSTANT) - Q_PROPERTY(QString k MEMBER k CONSTANT) - Q_PROPERTY(bool ext MEMBER ext CONSTANT) - -public: - QString kty; - QStringList keyOps; - QString alg; - QString k; - bool ext; -}; - -struct QUOTIENT_API EncryptedFile -{ - Q_GADGET - Q_PROPERTY(QUrl url MEMBER url CONSTANT) - Q_PROPERTY(JWK key MEMBER key CONSTANT) - Q_PROPERTY(QString iv MEMBER iv CONSTANT) - Q_PROPERTY(QHash hashes MEMBER hashes CONSTANT) - Q_PROPERTY(QString v MEMBER v CONSTANT) - -public: - QUrl url; - JWK key; - QString iv; - QHash hashes; - QString v; - - QByteArray decryptFile(const QByteArray &ciphertext) const; - static std::pair encryptFile(const QByteArray& plainText); -}; - -template <> -struct QUOTIENT_API JsonObjectConverter { - static void dumpTo(QJsonObject& jo, const EncryptedFile& pod); - static void fillFrom(const QJsonObject& jo, EncryptedFile& pod); -}; - -template <> -struct QUOTIENT_API JsonObjectConverter { - static void dumpTo(QJsonObject& jo, const JWK& pod); - static void fillFrom(const QJsonObject& jo, JWK& pod); -}; -} // namespace Quotient diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 6218e3b8..36b647cb 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -19,23 +19,21 @@ QJsonObject Base::toJson() const return o; } -FileInfo::FileInfo(const QFileInfo &fi) - : mimeType(QMimeDatabase().mimeTypeForFile(fi)) - , url(QUrl::fromLocalFile(fi.filePath())) - , payloadSize(fi.size()) - , originalName(fi.fileName()) +FileInfo::FileInfo(const QFileInfo& fi) + : source(QUrl::fromLocalFile(fi.filePath())), + mimeType(QMimeDatabase().mimeTypeForFile(fi)), + payloadSize(fi.size()), + originalName(fi.fileName()) { Q_ASSERT(fi.isFile()); } -FileInfo::FileInfo(QUrl u, qint64 payloadSize, const QMimeType& mimeType, - Omittable encryptedFile, - QString originalFilename) - : mimeType(mimeType) - , url(move(u)) +FileInfo::FileInfo(FileSourceInfo sourceInfo, qint64 payloadSize, + const QMimeType& mimeType, QString originalFilename) + : source(move(sourceInfo)) + , mimeType(mimeType) , payloadSize(payloadSize) , originalName(move(originalFilename)) - , file(move(encryptedFile)) { if (!isValid()) qCWarning(MESSAGES) @@ -44,28 +42,28 @@ FileInfo::FileInfo(QUrl u, qint64 payloadSize, const QMimeType& mimeType, "0.7; for local resources, use FileInfo(QFileInfo) instead"; } -FileInfo::FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, - Omittable encryptedFile, +FileInfo::FileInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson, QString originalFilename) - : originalInfoJson(infoJson) + : source(move(sourceInfo)) + , originalInfoJson(infoJson) , mimeType( QMimeDatabase().mimeTypeForName(infoJson["mimetype"_ls].toString())) - , url(move(mxcUrl)) , payloadSize(fromJson(infoJson["size"_ls])) , originalName(move(originalFilename)) - , file(move(encryptedFile)) { - if(url.isEmpty() && file.has_value()) { - url = file->url; - } if (!mimeType.isValid()) mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); } bool FileInfo::isValid() const { - return url.scheme() == "mxc" - && (url.authority() + url.path()).count('/') == 1; + const auto& u = url(); + return u.scheme() == "mxc" && (u.authority() + u.path()).count('/') == 1; +} + +QUrl FileInfo::url() const +{ + return getUrlFromSourceInfo(source); } QJsonObject Quotient::EventContent::toInfoJson(const FileInfo& info) @@ -75,7 +73,6 @@ QJsonObject Quotient::EventContent::toInfoJson(const FileInfo& info) infoJson.insert(QStringLiteral("size"), info.payloadSize); if (info.mimeType.isValid()) infoJson.insert(QStringLiteral("mimetype"), info.mimeType.name()); - //TODO add encryptedfile return infoJson; } @@ -83,17 +80,16 @@ ImageInfo::ImageInfo(const QFileInfo& fi, QSize imageSize) : FileInfo(fi), imageSize(imageSize) {} -ImageInfo::ImageInfo(const QUrl& mxcUrl, qint64 fileSize, const QMimeType& type, - QSize imageSize, Omittable encryptedFile, +ImageInfo::ImageInfo(FileSourceInfo sourceInfo, qint64 fileSize, + const QMimeType& type, QSize imageSize, const QString& originalFilename) - : FileInfo(mxcUrl, fileSize, type, move(encryptedFile), originalFilename) + : FileInfo(move(sourceInfo), fileSize, type, originalFilename) , imageSize(imageSize) {} -ImageInfo::ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, - Omittable encryptedFile, +ImageInfo::ImageInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson, const QString& originalFilename) - : FileInfo(mxcUrl, infoJson, move(encryptedFile), originalFilename) + : FileInfo(move(sourceInfo), infoJson, originalFilename) , imageSize(infoJson["w"_ls].toInt(), infoJson["h"_ls].toInt()) {} @@ -107,16 +103,20 @@ QJsonObject Quotient::EventContent::toInfoJson(const ImageInfo& info) return infoJson; } -Thumbnail::Thumbnail(const QJsonObject& infoJson, - Omittable encryptedFile) +Thumbnail::Thumbnail( + const QJsonObject& infoJson, + const Omittable& encryptedFileMetadata) : ImageInfo(QUrl(infoJson["thumbnail_url"_ls].toString()), - infoJson["thumbnail_info"_ls].toObject(), move(encryptedFile)) -{} + infoJson["thumbnail_info"_ls].toObject()) +{ + if (encryptedFileMetadata) + source = *encryptedFileMetadata; +} void Thumbnail::dumpTo(QJsonObject& infoJson) const { - if (url.isValid()) - infoJson.insert(QStringLiteral("thumbnail_url"), url.toString()); + if (url().isValid()) + fillJson(infoJson, { "thumbnail_url"_ls, "thumbnail_file"_ls }, source); if (!imageSize.isEmpty()) infoJson.insert(QStringLiteral("thumbnail_info"), toInfoJson(*this)); diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index bbd35618..23281876 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -6,14 +6,14 @@ // This file contains generic event content definitions, applicable to room // message events as well as other events (e.g., avatars). -#include "encryptedfile.h" +#include "filesourceinfo.h" #include "quotient_export.h" #include +#include #include #include #include -#include class QFileInfo; @@ -50,7 +50,7 @@ namespace EventContent { // A quick classes inheritance structure follows (the definitions are // spread across eventcontent.h and roommessageevent.h): - // UrlBasedContent : InfoT + url and thumbnail data + // UrlBasedContent : InfoT + thumbnail data // PlayableContent : + duration attribute // FileInfo // FileContent = UrlBasedContent @@ -89,34 +89,32 @@ namespace EventContent { //! //! \param fi a QFileInfo object referring to an existing file explicit FileInfo(const QFileInfo& fi); - explicit FileInfo(QUrl mxcUrl, qint64 payloadSize = -1, + explicit FileInfo(FileSourceInfo sourceInfo, qint64 payloadSize = -1, const QMimeType& mimeType = {}, - Omittable encryptedFile = none, QString originalFilename = {}); //! \brief Construct from a JSON `info` payload //! //! Make sure to pass the `info` subobject of content JSON, not the //! whole JSON content. - FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, - Omittable encryptedFile, + FileInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson, QString originalFilename = {}); bool isValid() const; + QUrl url() const; //! \brief Extract media id from the URL //! //! This can be used, e.g., to construct a QML-facing image:// //! URI as follows: //! \code "image://provider/" + info.mediaId() \endcode - QString mediaId() const { return url.authority() + url.path(); } + QString mediaId() const { return url().authority() + url().path(); } public: + FileSourceInfo source; QJsonObject originalInfoJson; QMimeType mimeType; - QUrl url; qint64 payloadSize = 0; QString originalName; - Omittable file = none; }; QUOTIENT_API QJsonObject toInfoJson(const FileInfo& info); @@ -126,12 +124,10 @@ namespace EventContent { public: ImageInfo() = default; explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {}); - explicit ImageInfo(const QUrl& mxcUrl, qint64 fileSize = -1, + explicit ImageInfo(FileSourceInfo sourceInfo, qint64 fileSize = -1, const QMimeType& type = {}, QSize imageSize = {}, - Omittable encryptedFile = none, const QString& originalFilename = {}); - ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, - Omittable encryptedFile, + ImageInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson, const QString& originalFilename = {}); public: @@ -144,12 +140,13 @@ namespace EventContent { //! //! This class saves/loads a thumbnail to/from `info` subobject of //! the JSON representation of event content; namely, `info/thumbnail_url` - //! and `info/thumbnail_info` fields are used. + //! (or, in case of an encrypted thumbnail, `info/thumbnail_file`) and + //! `info/thumbnail_info` fields are used. class QUOTIENT_API Thumbnail : public ImageInfo { public: using ImageInfo::ImageInfo; Thumbnail(const QJsonObject& infoJson, - Omittable encryptedFile = none); + const Omittable& encryptedFile = none); //! \brief Add thumbnail information to the passed `info` JSON object void dumpTo(QJsonObject& infoJson) const; @@ -169,10 +166,10 @@ namespace EventContent { //! \brief A template class for content types with a URL and additional info //! - //! Types that derive from this class template take `url` and, - //! optionally, `filename` values from the top-level JSON object and - //! the rest of information from the `info` subobject, as defined by - //! the parameter type. + //! Types that derive from this class template take `url` (or, if the file + //! is encrypted, `file`) and, optionally, `filename` values from + //! the top-level JSON object and the rest of information from the `info` + //! subobject, as defined by the parameter type. //! \tparam InfoT base info class - FileInfo or ImageInfo template class UrlBasedContent : public TypedBase, public InfoT { @@ -181,10 +178,12 @@ namespace EventContent { explicit UrlBasedContent(const QJsonObject& json) : TypedBase(json) , InfoT(QUrl(json["url"].toString()), json["info"].toObject(), - fromJson>(json["file"]), json["filename"].toString()) , thumbnail(FileInfo::originalInfoJson) { + const auto efmJson = json.value("file"_ls).toObject(); + if (!efmJson.isEmpty()) + InfoT::source = fromJson(efmJson); // Two small hacks on originalJson to expose mediaIds to QML originalJson.insert("mediaId", InfoT::mediaId()); originalJson.insert("thumbnailMediaId", thumbnail.mediaId()); @@ -204,11 +203,7 @@ namespace EventContent { void fillJson(QJsonObject& json) const override { - if (!InfoT::file.has_value()) { - json.insert("url", InfoT::url.toString()); - } else { - json.insert("file", Quotient::toJson(*InfoT::file)); - } + Quotient::fillJson(json, { "url"_ls, "file"_ls }, InfoT::source); if (!InfoT::originalName.isEmpty()) json.insert("filename", InfoT::originalName); auto infoJson = toInfoJson(*this); @@ -223,7 +218,7 @@ namespace EventContent { //! //! Available fields: //! - corresponding to the top-level JSON: - //! - url + //! - source (corresponding to `url` or `file` in JSON) //! - filename (extension to the spec) //! - corresponding to the `info` subobject: //! - payloadSize (`size` in JSON) @@ -241,12 +236,12 @@ namespace EventContent { //! //! Available fields: //! - corresponding to the top-level JSON: - //! - url + //! - source (corresponding to `url` or `file` in JSON) //! - filename //! - corresponding to the `info` subobject: //! - payloadSize (`size` in JSON) //! - mimeType (`mimetype` in JSON) - //! - thumbnail.url (`thumbnail_url` in JSON) + //! - thumbnail.source (`thumbnail_url` or `thumbnail_file` in JSON) //! - corresponding to the `info/thumbnail_info` subobject: //! - thumbnail.payloadSize //! - thumbnail.mimeType diff --git a/lib/events/filesourceinfo.cpp b/lib/events/filesourceinfo.cpp new file mode 100644 index 00000000..a64c7da8 --- /dev/null +++ b/lib/events/filesourceinfo.cpp @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "filesourceinfo.h" + +#include "logging.h" + +#ifdef Quotient_E2EE_ENABLED +# include "e2ee/qolmutils.h" + +# include + +# include +#endif + +using namespace Quotient; + +QByteArray EncryptedFileMetadata::decryptFile(const QByteArray& ciphertext) const +{ +#ifdef Quotient_E2EE_ENABLED + auto _key = key.k; + const auto keyBytes = QByteArray::fromBase64( + _key.replace(u'_', u'/').replace(u'-', u'+').toLatin1()); + const auto sha256 = + QByteArray::fromBase64(hashes["sha256"_ls].toLatin1()); + if (sha256 + != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { + qCWarning(E2EE) << "Hash verification failed for file"; + return {}; + } + { + int length; + auto* ctx = EVP_CIPHER_CTX_new(); + QByteArray plaintext(ciphertext.size() + EVP_MAX_BLOCK_LENGTH - 1, '\0'); + EVP_DecryptInit_ex( + ctx, EVP_aes_256_ctr(), nullptr, + reinterpret_cast(keyBytes.data()), + reinterpret_cast( + QByteArray::fromBase64(iv.toLatin1()).data())); + EVP_DecryptUpdate( + ctx, reinterpret_cast(plaintext.data()), &length, + reinterpret_cast(ciphertext.data()), + ciphertext.size()); + EVP_DecryptFinal_ex(ctx, + reinterpret_cast(plaintext.data()) + + length, + &length); + EVP_CIPHER_CTX_free(ctx); + return plaintext.left(ciphertext.size()); + } +#else + qWarning(MAIN) << "This build of libQuotient doesn't support E2EE, " + "cannot decrypt the file"; + return ciphertext; +#endif +} + +std::pair EncryptedFileMetadata::encryptFile( + const QByteArray& plainText) +{ +#ifdef Quotient_E2EE_ENABLED + QByteArray k = getRandom(32); + auto kBase64 = k.toBase64(); + QByteArray iv = getRandom(16); + JWK key = { "oct"_ls, + { "encrypt"_ls, "decrypt"_ls }, + "A256CTR"_ls, + QString(k.toBase64()) + .replace(u'/', u'_') + .replace(u'+', u'-') + .left(kBase64.indexOf('=')), + true }; + + int length; + auto* ctx = EVP_CIPHER_CTX_new(); + EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, + reinterpret_cast(k.data()), + reinterpret_cast(iv.data())); + const auto blockSize = EVP_CIPHER_CTX_block_size(ctx); + QByteArray cipherText(plainText.size() + blockSize - 1, '\0'); + EVP_EncryptUpdate(ctx, reinterpret_cast(cipherText.data()), + &length, + reinterpret_cast(plainText.data()), + plainText.size()); + EVP_EncryptFinal_ex(ctx, + reinterpret_cast(cipherText.data()) + + length, + &length); + EVP_CIPHER_CTX_free(ctx); + + auto hash = QCryptographicHash::hash(cipherText, QCryptographicHash::Sha256) + .toBase64(); + auto ivBase64 = iv.toBase64(); + EncryptedFileMetadata efm = { {}, + key, + ivBase64.left(ivBase64.indexOf('=')), + { { QStringLiteral("sha256"), + hash.left(hash.indexOf('=')) } }, + "v2"_ls }; + return { efm, cipherText }; +#else + return {}; +#endif +} + +void JsonObjectConverter::dumpTo(QJsonObject& jo, + const EncryptedFileMetadata& pod) +{ + addParam<>(jo, QStringLiteral("url"), pod.url); + addParam<>(jo, QStringLiteral("key"), pod.key); + addParam<>(jo, QStringLiteral("iv"), pod.iv); + addParam<>(jo, QStringLiteral("hashes"), pod.hashes); + addParam<>(jo, QStringLiteral("v"), pod.v); +} + +void JsonObjectConverter::fillFrom(const QJsonObject& jo, + EncryptedFileMetadata& pod) +{ + fromJson(jo.value("url"_ls), pod.url); + fromJson(jo.value("key"_ls), pod.key); + fromJson(jo.value("iv"_ls), pod.iv); + fromJson(jo.value("hashes"_ls), pod.hashes); + fromJson(jo.value("v"_ls), pod.v); +} + +void JsonObjectConverter::dumpTo(QJsonObject& jo, const JWK& pod) +{ + addParam<>(jo, QStringLiteral("kty"), pod.kty); + addParam<>(jo, QStringLiteral("key_ops"), pod.keyOps); + addParam<>(jo, QStringLiteral("alg"), pod.alg); + addParam<>(jo, QStringLiteral("k"), pod.k); + addParam<>(jo, QStringLiteral("ext"), pod.ext); +} + +void JsonObjectConverter::fillFrom(const QJsonObject& jo, JWK& pod) +{ + fromJson(jo.value("kty"_ls), pod.kty); + fromJson(jo.value("key_ops"_ls), pod.keyOps); + fromJson(jo.value("alg"_ls), pod.alg); + fromJson(jo.value("k"_ls), pod.k); + fromJson(jo.value("ext"_ls), pod.ext); +} + +template +struct Overloads : FunctorTs... { + using FunctorTs::operator()...; +}; + +template +Overloads(FunctorTs&&...) -> Overloads; + +QUrl Quotient::getUrlFromSourceInfo(const FileSourceInfo& fsi) +{ + return std::visit(Overloads { [](const QUrl& url) { return url; }, + [](const EncryptedFileMetadata& efm) { + return efm.url; + } }, + fsi); +} + +void Quotient::setUrlInSourceInfo(FileSourceInfo& fsi, const QUrl& newUrl) +{ + std::visit(Overloads { [&newUrl](QUrl& url) { url = newUrl; }, + [&newUrl](EncryptedFileMetadata& efm) { + efm.url = newUrl; + } }, + fsi); +} + +void Quotient::fillJson(QJsonObject& jo, + const std::array& jsonKeys, + const FileSourceInfo& fsi) +{ + // NB: Keeping variant_size_v out of the function signature for readability. + // NB2: Can't use jsonKeys directly inside static_assert as its value is + // unknown so the compiler cannot ensure size() is constexpr (go figure...) + static_assert( + std::variant_size_v == decltype(jsonKeys) {}.size()); + jo.insert(jsonKeys[fsi.index()], toJson(fsi)); +} diff --git a/lib/events/filesourceinfo.h b/lib/events/filesourceinfo.h new file mode 100644 index 00000000..885601be --- /dev/null +++ b/lib/events/filesourceinfo.h @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "converters.h" + +#include + +namespace Quotient { +/** + * JSON Web Key object as specified in + * https://spec.matrix.org/unstable/client-server-api/#extensions-to-mroommessage-msgtypes + * The only currently relevant member is `k`, the rest needs to be set to the defaults specified in the spec. + */ +struct JWK +{ + Q_GADGET + Q_PROPERTY(QString kty MEMBER kty CONSTANT) + Q_PROPERTY(QStringList keyOps MEMBER keyOps CONSTANT) + Q_PROPERTY(QString alg MEMBER alg CONSTANT) + Q_PROPERTY(QString k MEMBER k CONSTANT) + Q_PROPERTY(bool ext MEMBER ext CONSTANT) + +public: + QString kty; + QStringList keyOps; + QString alg; + QString k; + bool ext; +}; + +struct QUOTIENT_API EncryptedFileMetadata { + Q_GADGET + Q_PROPERTY(QUrl url MEMBER url CONSTANT) + Q_PROPERTY(JWK key MEMBER key CONSTANT) + Q_PROPERTY(QString iv MEMBER iv CONSTANT) + Q_PROPERTY(QHash hashes MEMBER hashes CONSTANT) + Q_PROPERTY(QString v MEMBER v CONSTANT) + +public: + QUrl url; + JWK key; + QString iv; + QHash hashes; + QString v; + + static std::pair encryptFile( + const QByteArray& plainText); + QByteArray decryptFile(const QByteArray& ciphertext) const; +}; + +template <> +struct QUOTIENT_API JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const EncryptedFileMetadata& pod); + static void fillFrom(const QJsonObject& jo, EncryptedFileMetadata& pod); +}; + +template <> +struct QUOTIENT_API JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const JWK& pod); + static void fillFrom(const QJsonObject& jo, JWK& pod); +}; + +using FileSourceInfo = std::variant; + +QUOTIENT_API QUrl getUrlFromSourceInfo(const FileSourceInfo& fsi); + +QUOTIENT_API void setUrlInSourceInfo(FileSourceInfo& fsi, const QUrl& newUrl); + +// The way FileSourceInfo is stored in JSON requires an extra parameter so +// the original template is not applicable +template <> +void fillJson(QJsonObject&, const FileSourceInfo&) = delete; + +//! \brief Export FileSourceInfo to a JSON object +//! +//! Depending on what is stored inside FileSourceInfo, this function will insert +//! - a key-to-string pair where key is taken from jsonKeys[0] and the string +//! is the URL, if FileSourceInfo stores a QUrl; +//! - a key-to-object mapping where key is taken from jsonKeys[1] and the object +//! is the result of converting EncryptedFileMetadata to JSON, +//! if FileSourceInfo stores EncryptedFileMetadata +QUOTIENT_API void fillJson(QJsonObject& jo, + const std::array& jsonKeys, + const FileSourceInfo& fsi); + +} // namespace Quotient diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index c54b5801..af291696 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -26,10 +26,10 @@ public: const QSize& imageSize = {}, const QString& originalFilename = {}) : RoomAvatarEvent(EventContent::ImageContent { - mxcUrl, fileSize, mimeType, imageSize, none, originalFilename }) + mxcUrl, fileSize, mimeType, imageSize, originalFilename }) {} - QUrl url() const { return content().url; } + QUrl url() const { return content().url(); } }; REGISTER_EVENT_TYPE(RoomAvatarEvent) } // namespace Quotient diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index d9d3fbe0..2a6ae93c 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -148,21 +148,21 @@ TypedBase* contentFromFile(const QFileInfo& file, bool asGenericFile) auto mimeTypeName = mimeType.name(); if (mimeTypeName.startsWith("image/")) return new ImageContent(localUrl, file.size(), mimeType, - QImageReader(filePath).size(), none, + QImageReader(filePath).size(), file.fileName()); // duration can only be obtained asynchronously and can only be reliably // done by starting to play the file. Left for a future implementation. if (mimeTypeName.startsWith("video/")) return new VideoContent(localUrl, file.size(), mimeType, - QMediaResource(localUrl).resolution(), none, + QMediaResource(localUrl).resolution(), file.fileName()); if (mimeTypeName.startsWith("audio/")) - return new AudioContent(localUrl, file.size(), mimeType, none, + return new AudioContent(localUrl, file.size(), mimeType, file.fileName()); } - return new FileContent(localUrl, file.size(), mimeType, none, file.fileName()); + return new FileContent(localUrl, file.size(), mimeType, file.fileName()); } RoomMessageEvent::RoomMessageEvent(const QString& plainBody, diff --git a/lib/events/stickerevent.cpp b/lib/events/stickerevent.cpp index 628fd154..6d318f0e 100644 --- a/lib/events/stickerevent.cpp +++ b/lib/events/stickerevent.cpp @@ -22,5 +22,5 @@ const EventContent::ImageContent &StickerEvent::image() const QUrl StickerEvent::url() const { - return m_imageContent.url; + return m_imageContent.url(); } diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index d00fc5f4..85c235c7 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -8,8 +8,9 @@ #include #ifdef Quotient_E2EE_ENABLED -# include -# include "events/encryptedfile.h" +# include "events/filesourceinfo.h" + +# include #endif using namespace Quotient; @@ -26,7 +27,7 @@ public: QScopedPointer tempFile; #ifdef Quotient_E2EE_ENABLED - Omittable encryptedFile; + Omittable encryptedFile; #endif }; @@ -49,7 +50,7 @@ DownloadFileJob::DownloadFileJob(const QString& serverName, #ifdef Quotient_E2EE_ENABLED DownloadFileJob::DownloadFileJob(const QString& serverName, const QString& mediaId, - const EncryptedFile& file, + const EncryptedFileMetadata& file, const QString& localFilename) : GetContentJob(serverName, mediaId) , d(localFilename.isEmpty() ? makeImpl() @@ -126,7 +127,7 @@ BaseJob::Status DownloadFileJob::prepareResult() d->tempFile->seek(0); QByteArray encrypted = d->tempFile->readAll(); - EncryptedFile file = *d->encryptedFile; + EncryptedFileMetadata file = *d->encryptedFile; const auto decrypted = file.decryptFile(encrypted); d->targetFile->write(decrypted); d->tempFile->remove(); @@ -151,7 +152,7 @@ BaseJob::Status DownloadFileJob::prepareResult() d->tempFile->seek(0); const auto encrypted = d->tempFile->readAll(); - EncryptedFile file = *d->encryptedFile; + EncryptedFileMetadata file = *d->encryptedFile; const auto decrypted = file.decryptFile(encrypted); d->tempFile->write(decrypted); } else { diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index ffa3d055..cbbfd244 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -4,7 +4,8 @@ #pragma once #include "csapi/content-repo.h" -#include "events/encryptedfile.h" + +#include "events/filesourceinfo.h" namespace Quotient { class QUOTIENT_API DownloadFileJob : public GetContentJob { @@ -16,7 +17,7 @@ public: const QString& localFilename = {}); #ifdef Quotient_E2EE_ENABLED - DownloadFileJob(const QString& serverName, const QString& mediaId, const EncryptedFile& file, const QString& localFilename = {}); + DownloadFileJob(const QString& serverName, const QString& mediaId, const EncryptedFileMetadata& file, const QString& localFilename = {}); #endif QString targetFileName() const; diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 319d514a..b7993ad5 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -8,7 +8,7 @@ #include "room.h" #ifdef Quotient_E2EE_ENABLED -#include "events/encryptedfile.h" +#include "events/filesourceinfo.h" #endif using namespace Quotient; @@ -20,7 +20,7 @@ public: : m_reply(r) {} QNetworkReply* m_reply; - Omittable m_encryptedFile; + Omittable m_encryptedFile; QIODevice* m_device = nullptr; }; @@ -47,7 +47,7 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) if(!d->m_encryptedFile.has_value()) { d->m_device = d->m_reply; } else { - EncryptedFile file = *d->m_encryptedFile; + EncryptedFileMetadata file = *d->m_encryptedFile; auto buffer = new QBuffer(this); buffer->setData(file.decryptFile(d->m_reply->readAll())); buffer->open(ReadOnly); @@ -64,7 +64,9 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) auto eventIt = room->findInTimeline(eventId); if(eventIt != room->historyEdge()) { auto event = eventIt->viewAs(); - d->m_encryptedFile = event->content()->fileInfo()->file; + if (auto* efm = std::get_if( + &event->content()->fileInfo()->source)) + d->m_encryptedFile = *efm; } #endif } diff --git a/lib/room.cpp b/lib/room.cpp index 7022a49d..20ea1159 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1525,7 +1525,7 @@ QUrl Room::urlToThumbnail(const QString& eventId) const auto* thumbnail = event->content()->thumbnailInfo(); Q_ASSERT(thumbnail != nullptr); return connection()->getUrlForApi( - thumbnail->url, thumbnail->imageSize); + thumbnail->url(), thumbnail->imageSize); } qCDebug(MAIN) << "Event" << eventId << "has no thumbnail"; return {}; @@ -1536,7 +1536,7 @@ QUrl Room::urlToDownload(const QString& eventId) const if (auto* event = d->getEventWithFile(eventId)) { auto* fileInfo = event->content()->fileInfo(); Q_ASSERT(fileInfo != nullptr); - return connection()->getUrlForApi(fileInfo->url); + return connection()->getUrlForApi(fileInfo->url()); } return {}; } @@ -2275,28 +2275,26 @@ QString Room::Private::doPostFile(RoomEventPtr&& msgEvent, const QUrl& localUrl) // Below, the upload job is used as a context object to clean up connections const auto& transferJob = fileTransfers.value(txnId).job; connect(q, &Room::fileTransferCompleted, transferJob, - [this, txnId](const QString& tId, const QUrl&, const QUrl& mxcUri, Omittable encryptedFile) { - if (tId != txnId) - return; + [this, txnId](const QString& tId, const QUrl&, + const FileSourceInfo fileMetadata) { + if (tId != txnId) + return; - const auto it = q->findPendingEvent(txnId); - if (it != unsyncedEvents.end()) { - it->setFileUploaded(mxcUri); - if (encryptedFile) { - it->setEncryptedFile(*encryptedFile); - } - emit q->pendingEventChanged( - int(it - unsyncedEvents.begin())); - doSendEvent(it->get()); - } else { - // Normally in this situation we should instruct - // the media server to delete the file; alas, there's no - // API specced for that. - qCWarning(MAIN) << "File uploaded to" << mxcUri - << "but the event referring to it was " - "cancelled"; - } - }); + const auto it = q->findPendingEvent(txnId); + if (it != unsyncedEvents.end()) { + it->setFileUploaded(fileMetadata); + emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); + doSendEvent(it->get()); + } else { + // Normally in this situation we should instruct + // the media server to delete the file; alas, there's no + // API specced for that. + qCWarning(MAIN) + << "File uploaded to" << getUrlFromSourceInfo(fileMetadata) + << "but the event referring to it was " + "cancelled"; + } + }); connect(q, &Room::fileTransferFailed, transferJob, [this, txnId](const QString& tId) { if (tId != txnId) @@ -2322,13 +2320,13 @@ QString Room::postFile(const QString& plainText, Q_ASSERT(content != nullptr && content->fileInfo() != nullptr); const auto* const fileInfo = content->fileInfo(); Q_ASSERT(fileInfo != nullptr); - QFileInfo localFile { fileInfo->url.toLocalFile() }; + QFileInfo localFile { fileInfo->url().toLocalFile() }; Q_ASSERT(localFile.isFile()); return d->doPostFile( makeEvent( plainText, RoomMessageEvent::rawMsgTypeForFile(localFile), content), - fileInfo->url); + fileInfo->url()); } #if QT_VERSION_MAJOR < 6 @@ -2520,18 +2518,19 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, Q_ASSERT_X(localFilename.isLocalFile(), __FUNCTION__, "localFilename should point at a local file"); auto fileName = localFilename.toLocalFile(); - Omittable encryptedFile { none }; + FileSourceInfo fileMetadata; #ifdef Quotient_E2EE_ENABLED QTemporaryFile tempFile; if (usesEncryption()) { tempFile.open(); QFile file(localFilename.toLocalFile()); file.open(QFile::ReadOnly); - auto [e, data] = EncryptedFile::encryptFile(file.readAll()); + QByteArray data; + std::tie(fileMetadata, data) = + EncryptedFileMetadata::encryptFile(file.readAll()); tempFile.write(data); tempFile.close(); fileName = QFileInfo(tempFile).absoluteFilePath(); - encryptedFile = e; } #endif auto job = connection()->uploadFile(fileName, overrideContentType); @@ -2542,17 +2541,13 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, d->fileTransfers[id].update(sent, total); emit fileTransferProgress(id, sent, total); }); - connect(job, &BaseJob::success, this, [this, id, localFilename, job, encryptedFile] { - d->fileTransfers[id].status = FileTransferInfo::Completed; - if (encryptedFile) { - auto file = *encryptedFile; - file.url = QUrl(job->contentUri()); - emit fileTransferCompleted(id, localFilename, QUrl(job->contentUri()), file); - } else { - emit fileTransferCompleted(id, localFilename, QUrl(job->contentUri()), none); - } - - }); + connect(job, &BaseJob::success, this, + [this, id, localFilename, job, fileMetadata]() mutable { + // The lambda is mutable to change encryptedFileMetadata + d->fileTransfers[id].status = FileTransferInfo::Completed; + setUrlInSourceInfo(fileMetadata, QUrl(job->contentUri())); + emit fileTransferCompleted(id, localFilename, fileMetadata); + }); connect(job, &BaseJob::failure, this, std::bind(&Private::failedTransfer, d, id, job->errorString())); emit newFileTransfer(id, localFilename); @@ -2585,11 +2580,11 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) << "has an empty or malformed mxc URL; won't download"; return; } - const auto fileUrl = fileInfo->url; + const auto fileUrl = fileInfo->url(); auto filePath = localFilename.toLocalFile(); if (filePath.isEmpty()) { // Setup default file path filePath = - fileInfo->url.path().mid(1) % '_' % d->fileNameToDownload(event); + fileInfo->url().path().mid(1) % '_' % d->fileNameToDownload(event); if (filePath.size() > 200) // If too long, elide in the middle filePath.replace(128, filePath.size() - 192, "---"); @@ -2599,9 +2594,9 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) } DownloadFileJob *job = nullptr; #ifdef Quotient_E2EE_ENABLED - if(fileInfo->file.has_value()) { - auto file = *fileInfo->file; - job = connection()->downloadFile(fileUrl, file, filePath); + if (auto* fileMetadata = + std::get_if(&fileInfo->source)) { + job = connection()->downloadFile(fileUrl, *fileMetadata, filePath); } else { #endif job = connection()->downloadFile(fileUrl, filePath); @@ -2619,7 +2614,7 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) connect(job, &BaseJob::success, this, [this, eventId, fileUrl, job] { d->fileTransfers[eventId].status = FileTransferInfo::Completed; emit fileTransferCompleted( - eventId, fileUrl, QUrl::fromLocalFile(job->targetFileName()), none); + eventId, fileUrl, QUrl::fromLocalFile(job->targetFileName())); }); connect(job, &BaseJob::failure, this, std::bind(&Private::failedTransfer, d, eventId, diff --git a/lib/room.h b/lib/room.h index c3bdc4a0..0636c4bb 100644 --- a/lib/room.h +++ b/lib/room.h @@ -999,7 +999,8 @@ Q_SIGNALS: void newFileTransfer(QString id, QUrl localFile); void fileTransferProgress(QString id, qint64 progress, qint64 total); - void fileTransferCompleted(QString id, QUrl localFile, QUrl mxcUrl, Omittable encryptedFile); + void fileTransferCompleted(QString id, QUrl localFile, + FileSourceInfo fileMetadata); void fileTransferFailed(QString id, QString errorMessage = {}); // fileTransferCancelled() is no more here; use fileTransferFailed() and // check the transfer status instead diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 1eed865f..6bcd71cd 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -516,7 +516,7 @@ bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest, && e.hasFileContent() && e.content()->fileInfo()->originalName == fileName && testDownload(targetRoom->connection()->makeMediaUrl( - e.content()->fileInfo()->url))); + e.content()->fileInfo()->url()))); }, [this, thisTest](const RoomEvent&) { FAIL_TEST(); }); }); -- cgit v1.2.3 From c2d87291dbf8bd240e3e96138ec52aa5da22416b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 26 May 2022 12:50:30 +0200 Subject: Move encryptFile/decryptFile out of EncryptedFileMetadata These are not operations on EncryptedFileMetadata but rather on a combination of EncryptedFileMetadata and ciphertext. If C++ had multimethods these could be bound to such a combination. --- autotests/testfilecrypto.cpp | 4 ++-- lib/events/filesourceinfo.cpp | 11 ++++++----- lib/events/filesourceinfo.h | 9 +++++---- lib/jobs/downloadfilejob.cpp | 4 ++-- lib/mxcreply.cpp | 4 ++-- lib/room.cpp | 3 +-- 6 files changed, 18 insertions(+), 17 deletions(-) (limited to 'lib/mxcreply.cpp') diff --git a/autotests/testfilecrypto.cpp b/autotests/testfilecrypto.cpp index b86114a4..29521060 100644 --- a/autotests/testfilecrypto.cpp +++ b/autotests/testfilecrypto.cpp @@ -12,8 +12,8 @@ using namespace Quotient; void TestFileCrypto::encryptDecryptData() { QByteArray data = "ABCDEF"; - auto [file, cipherText] = EncryptedFileMetadata::encryptFile(data); - auto decrypted = file.decryptFile(cipherText); + auto [file, cipherText] = encryptFile(data); + auto decrypted = decryptFile(cipherText, file); // AES CTR produces ciphertext of the same size as the original QCOMPARE(cipherText.size(), data.size()); QCOMPARE(decrypted.size(), data.size()); diff --git a/lib/events/filesourceinfo.cpp b/lib/events/filesourceinfo.cpp index a64c7da8..43e8e44c 100644 --- a/lib/events/filesourceinfo.cpp +++ b/lib/events/filesourceinfo.cpp @@ -16,14 +16,15 @@ using namespace Quotient; -QByteArray EncryptedFileMetadata::decryptFile(const QByteArray& ciphertext) const +QByteArray Quotient::decryptFile(const QByteArray& ciphertext, + const EncryptedFileMetadata& metadata) { #ifdef Quotient_E2EE_ENABLED - auto _key = key.k; + auto _key = metadata.key.k; const auto keyBytes = QByteArray::fromBase64( _key.replace(u'_', u'/').replace(u'-', u'+').toLatin1()); const auto sha256 = - QByteArray::fromBase64(hashes["sha256"_ls].toLatin1()); + QByteArray::fromBase64(metadata.hashes["sha256"_ls].toLatin1()); if (sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { qCWarning(E2EE) << "Hash verification failed for file"; @@ -37,7 +38,7 @@ QByteArray EncryptedFileMetadata::decryptFile(const QByteArray& ciphertext) cons ctx, EVP_aes_256_ctr(), nullptr, reinterpret_cast(keyBytes.data()), reinterpret_cast( - QByteArray::fromBase64(iv.toLatin1()).data())); + QByteArray::fromBase64(metadata.iv.toLatin1()).data())); EVP_DecryptUpdate( ctx, reinterpret_cast(plaintext.data()), &length, reinterpret_cast(ciphertext.data()), @@ -56,7 +57,7 @@ QByteArray EncryptedFileMetadata::decryptFile(const QByteArray& ciphertext) cons #endif } -std::pair EncryptedFileMetadata::encryptFile( +std::pair Quotient::encryptFile( const QByteArray& plainText) { #ifdef Quotient_E2EE_ENABLED diff --git a/lib/events/filesourceinfo.h b/lib/events/filesourceinfo.h index 885601be..8f7e3cbe 100644 --- a/lib/events/filesourceinfo.h +++ b/lib/events/filesourceinfo.h @@ -45,12 +45,13 @@ public: QString iv; QHash hashes; QString v; - - static std::pair encryptFile( - const QByteArray& plainText); - QByteArray decryptFile(const QByteArray& ciphertext) const; }; +QUOTIENT_API std::pair encryptFile( + const QByteArray& plainText); +QUOTIENT_API QByteArray decryptFile(const QByteArray& ciphertext, + const EncryptedFileMetadata& metadata); + template <> struct QUOTIENT_API JsonObjectConverter { static void dumpTo(QJsonObject& jo, const EncryptedFileMetadata& pod); diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 85c235c7..032b24f2 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -128,7 +128,7 @@ BaseJob::Status DownloadFileJob::prepareResult() QByteArray encrypted = d->tempFile->readAll(); EncryptedFileMetadata file = *d->encryptedFile; - const auto decrypted = file.decryptFile(encrypted); + const auto decrypted = decryptFile(encrypted, file); d->targetFile->write(decrypted); d->tempFile->remove(); } else { @@ -153,7 +153,7 @@ BaseJob::Status DownloadFileJob::prepareResult() const auto encrypted = d->tempFile->readAll(); EncryptedFileMetadata file = *d->encryptedFile; - const auto decrypted = file.decryptFile(encrypted); + const auto decrypted = decryptFile(encrypted, file); d->tempFile->write(decrypted); } else { #endif diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index b7993ad5..4174cfd8 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -47,9 +47,9 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) if(!d->m_encryptedFile.has_value()) { d->m_device = d->m_reply; } else { - EncryptedFileMetadata file = *d->m_encryptedFile; auto buffer = new QBuffer(this); - buffer->setData(file.decryptFile(d->m_reply->readAll())); + buffer->setData( + decryptFile(d->m_reply->readAll(), *d->m_encryptedFile)); buffer->open(ReadOnly); d->m_device = buffer; } diff --git a/lib/room.cpp b/lib/room.cpp index 0cef1025..4cb01a39 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2524,8 +2524,7 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, QFile file(localFilename.toLocalFile()); file.open(QFile::ReadOnly); QByteArray data; - std::tie(fileMetadata, data) = - EncryptedFileMetadata::encryptFile(file.readAll()); + std::tie(fileMetadata, data) = encryptFile(file.readAll()); tempFile.write(data); tempFile.close(); fileName = QFileInfo(tempFile).absoluteFilePath(); -- cgit v1.2.3 From 6b355d1aa87072143e09ea5269e8cf465318a64f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 18 Jun 2022 21:26:43 +0200 Subject: Drop pre-Qt 5.15 code --- lib/connection.cpp | 16 ---------------- lib/converters.h | 10 +--------- lib/eventitem.h | 2 +- lib/jobs/basejob.cpp | 8 +------- lib/logging.h | 7 +------ lib/mxcreply.cpp | 8 +------- lib/networkaccessmanager.cpp | 15 +-------------- lib/quotient_common.h | 15 +-------------- lib/room.cpp | 7 ++----- lib/syncdata.cpp | 7 +------ 10 files changed, 10 insertions(+), 85 deletions(-) (limited to 'lib/mxcreply.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index c390cc05..2319a38a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -49,10 +49,6 @@ # include #endif -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) -# include -#endif - #include #include #include @@ -1826,16 +1822,10 @@ void Connection::saveRoomState(Room* r) const QFile outRoomFile { stateCacheDir().filePath( SyncData::fileNameForRoom(r->id())) }; if (outRoomFile.open(QFile::WriteOnly)) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) const auto data = d->cacheToBinary ? QCborValue::fromJsonValue(r->toJson()).toCbor() : QJsonDocument(r->toJson()).toJson(QJsonDocument::Compact); -#else - QJsonDocument json { r->toJson() }; - const auto data = d->cacheToBinary ? json.toBinaryData() - : json.toJson(QJsonDocument::Compact); -#endif outRoomFile.write(data.data(), data.size()); qCDebug(MAIN) << "Room state cache saved to" << outRoomFile.fileName(); } else { @@ -1905,15 +1895,9 @@ void Connection::saveState() const } #endif -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) const auto data = d->cacheToBinary ? QCborValue::fromJsonValue(rootObj).toCbor() : QJsonDocument(rootObj).toJson(QJsonDocument::Compact); -#else - QJsonDocument json { rootObj }; - const auto data = d->cacheToBinary ? json.toBinaryData() - : json.toJson(QJsonDocument::Compact); -#endif qCDebug(PROFILER) << "Cache for" << userId() << "generated in" << et; outFile.write(data.data(), data.size()); diff --git a/lib/converters.h b/lib/converters.h index da30cae6..c985fd60 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -188,15 +188,7 @@ inline QDateTime fromJson(const QJsonValue& jv) return QDateTime::fromMSecsSinceEpoch(fromJson(jv), Qt::UTC); } -inline QJsonValue toJson(const QDate& val) { - return toJson( -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - QDateTime(val) -#else - val.startOfDay() -#endif - ); -} +inline QJsonValue toJson(const QDate& val) { return toJson(val.startOfDay()); } template <> inline QDate fromJson(const QJsonValue& jv) { diff --git a/lib/eventitem.h b/lib/eventitem.h index 5e001d88..e476c66c 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -14,7 +14,7 @@ namespace Quotient { namespace EventStatus { - QUO_NAMESPACE + Q_NAMESPACE_EXPORT(QUOTIENT_API) /** Special marks an event can assume * diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index b6858b5a..fe70911e 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -301,16 +301,10 @@ void BaseJob::Private::sendRequest() QNetworkRequest::NoLessSafeRedirectPolicy); req.setMaximumRedirectsAllowed(10); req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); - req.setAttribute( -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) - QNetworkRequest::Http2AllowedAttribute -#else - QNetworkRequest::HTTP2AllowedAttribute -#endif // Qt doesn't combine HTTP2 with SSL quite right, occasionally crashing at // what seems like an attempt to write to a closed channel. If/when that // changes, false should be turned to true below. - , false); + req.setAttribute(QNetworkRequest::Http2AllowedAttribute, false); Q_ASSERT(req.url().isValid()); for (auto it = requestHeaders.cbegin(); it != requestHeaders.cend(); ++it) req.setRawHeader(it.key(), it.value()); diff --git a/lib/logging.h b/lib/logging.h index fc0a4c99..c8d17210 100644 --- a/lib/logging.h +++ b/lib/logging.h @@ -44,12 +44,7 @@ inline QDebug formatJson(QDebug debug_object) //! Suppress full qualification of enums/QFlags when logging inline QDebug terse(QDebug dbg) { - return -#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) - dbg.setVerbosity(0), dbg; -#else - dbg.verbosity(QDebug::MinimumVerbosity); -#endif + return dbg.verbosity(QDebug::MinimumVerbosity); } inline qint64 profilerMinNsecs() diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 4174cfd8..c7547be8 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -71,12 +71,6 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) #endif } -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) -#define ERROR_SIGNAL errorOccurred -#else -#define ERROR_SIGNAL error -#endif - MxcReply::MxcReply() : d(ZeroImpl()) { @@ -88,7 +82,7 @@ MxcReply::MxcReply() setError(QNetworkReply::ProtocolInvalidOperationError, BadRequestPhrase); setFinished(true); - emit ERROR_SIGNAL(QNetworkReply::ProtocolInvalidOperationError); + emit errorOccurred(QNetworkReply::ProtocolInvalidOperationError); emit finished(); }, Qt::QueuedConnection); } diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index f4e7b1af..38ab07cc 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -68,24 +68,11 @@ void NetworkAccessManager::clearIgnoredSslErrors() d->ignoredSslErrors.clear(); } -static NetworkAccessManager* createNam() -{ - auto nam = new NetworkAccessManager(); -#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) - // See #109; in newer Qt, bearer management is deprecated altogether - NetworkAccessManager::connect(nam, - &QNetworkAccessManager::networkAccessibleChanged, [nam] { - nam->setNetworkAccessible(QNetworkAccessManager::Accessible); - }); -#endif - return nam; -} - NetworkAccessManager* NetworkAccessManager::instance() { static QThreadStorage storage; if(!storage.hasLocalData()) { - storage.setLocalData(createNam()); + storage.setLocalData(new NetworkAccessManager()); } return storage.localData(); } diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 2b785a39..8bcd5ca6 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -51,21 +51,8 @@ #define DECL_DEPRECATED_ENUMERATOR(Deprecated, Recommended) \ Deprecated Q_DECL_ENUMERATOR_DEPRECATED_X("Use " #Recommended) = Recommended -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) -// The first line forward-declares the namespace static metaobject with -// QUOTIENT_API so that dynamically linked clients could serialise flag/enum -// values from the namespace; Qt before 5.14 doesn't help with that. The second -// line is needed for moc to do its job on the namespace. -#define QUO_NAMESPACE \ - extern QUOTIENT_API const QMetaObject staticMetaObject; \ - Q_NAMESPACE -#else -// Since Qt 5.14.0, it's all packed in a single macro -#define QUO_NAMESPACE Q_NAMESPACE_EXPORT(QUOTIENT_API) -#endif - namespace Quotient { -QUO_NAMESPACE +Q_NAMESPACE_EXPORT(QUOTIENT_API) // std::array {} needs explicit template parameters on macOS because // Apple stdlib doesn't have deduction guides for std::array. C++20 has diff --git a/lib/room.cpp b/lib/room.cpp index f692c354..e07de123 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1974,11 +1974,8 @@ void Room::Private::postprocessChanges(Changes changes, bool saveState) if (changes & Change::Highlights) emit q->highlightCountChanged(); - qCDebug(MAIN) << terse << changes << "= hex" << -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - Qt:: -#endif - hex << uint(changes) << "in" << q->objectName(); + qCDebug(MAIN) << terse << changes << "= hex" << Qt::hex << uint(changes) + << "in" << q->objectName(); emit q->changed(changes); if (saveState) connection->saveRoomState(q); diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index 95d3c7e4..ff3dc0c2 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -179,12 +179,7 @@ QJsonObject SyncData::loadJson(const QString& fileName) const auto json = data.startsWith('{') ? QJsonDocument::fromJson(data).object() -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - : QCborValue::fromCbor(data).toJsonValue().toObject() -#else - : QJsonDocument::fromBinaryData(data).object() -#endif - ; + : QCborValue::fromCbor(data).toJsonValue().toObject(); if (json.isEmpty()) { qCWarning(MAIN) << "State cache in" << fileName << "is broken or empty, discarding"; -- cgit v1.2.3 From 94d1bb2c624ca9a689a5d7cdca8af6ef5e489150 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 22 Sep 2022 20:41:26 +0200 Subject: Don't crash in MxcReply if the event is not a RoomMessageEvent --- lib/mxcreply.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'lib/mxcreply.cpp') diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index c7547be8..ce833b98 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -63,10 +63,11 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) #ifdef Quotient_E2EE_ENABLED auto eventIt = room->findInTimeline(eventId); if(eventIt != room->historyEdge()) { - auto event = eventIt->viewAs(); - if (auto* efm = std::get_if( - &event->content()->fileInfo()->source)) - d->m_encryptedFile = *efm; + if (auto event = eventIt->viewAs()) { + if (auto* efm = std::get_if( + &event->content()->fileInfo()->source)) + d->m_encryptedFile = *efm; + } } #endif } -- cgit v1.2.3