aboutsummaryrefslogtreecommitdiff
path: root/lib/events
diff options
context:
space:
mode:
authorAlexey Rusakov <Kitsune-Ral@users.sf.net>2022-08-24 09:41:51 +0200
committerAlexey Rusakov <Kitsune-Ral@users.sf.net>2022-08-24 09:41:51 +0200
commit82f4efb0227e7e22e831733fae3952818b063ac2 (patch)
tree3b154a16f9d355996a59c611230d0e010edab57f /lib/events
parent16d4f4e48304543a0ab59b235edba07f5f2c2204 (diff)
parent6308bff3336ca7680eee54d9bd125f780fa9f033 (diff)
downloadlibquotient-82f4efb0227e7e22e831733fae3952818b063ac2.tar.gz
libquotient-82f4efb0227e7e22e831733fae3952818b063ac2.zip
Merge branch 'dev' into device-verification
# Conflicts: # autotests/testfilecrypto.cpp # lib/connection.cpp # lib/connection.h # lib/database.cpp # lib/database.h # lib/e2ee/qolmoutboundsession.cpp # lib/e2ee/qolmoutboundsession.h # lib/eventitem.h # lib/events/encryptedevent.cpp # lib/events/encryptedevent.h # lib/events/encryptedfile.cpp # lib/events/encryptedfile.h # lib/events/keyverificationevent.cpp # lib/events/keyverificationevent.h # lib/events/roomkeyevent.h # lib/room.cpp # lib/room.h
Diffstat (limited to 'lib/events')
-rw-r--r--lib/events/accountdataevents.h33
-rw-r--r--lib/events/callanswerevent.cpp11
-rw-r--r--lib/events/callanswerevent.h6
-rw-r--r--lib/events/callcandidatesevent.h17
-rw-r--r--lib/events/callinviteevent.cpp2
-rw-r--r--lib/events/callinviteevent.h7
-rw-r--r--lib/events/directchatevent.cpp2
-rw-r--r--lib/events/encryptedevent.cpp12
-rw-r--r--lib/events/encryptedevent.h2
-rw-r--r--lib/events/encryptedfile.cpp118
-rw-r--r--lib/events/encryptedfile.h63
-rw-r--r--lib/events/encryptionevent.cpp65
-rw-r--r--lib/events/encryptionevent.h44
-rw-r--r--lib/events/event.cpp2
-rw-r--r--lib/events/event.h152
-rw-r--r--lib/events/eventcontent.cpp101
-rw-r--r--lib/events/eventcontent.h496
-rw-r--r--lib/events/eventloader.h48
-rw-r--r--lib/events/eventrelation.h6
-rw-r--r--lib/events/filesourceinfo.cpp172
-rw-r--r--lib/events/filesourceinfo.h90
-rw-r--r--lib/events/keyverificationevent.cpp194
-rw-r--r--lib/events/keyverificationevent.h122
-rw-r--r--lib/events/redactionevent.h4
-rw-r--r--lib/events/roomavatarevent.h4
-rw-r--r--lib/events/roomcanonicalaliasevent.h45
-rw-r--r--lib/events/roomcreateevent.cpp19
-rw-r--r--lib/events/roomevent.cpp22
-rw-r--r--lib/events/roomevent.h9
-rw-r--r--lib/events/roomkeyevent.h15
-rw-r--r--lib/events/roommemberevent.cpp34
-rw-r--r--lib/events/roommemberevent.h7
-rw-r--r--lib/events/roommessageevent.cpp26
-rw-r--r--lib/events/roommessageevent.h29
-rw-r--r--lib/events/roompowerlevelsevent.cpp66
-rw-r--r--lib/events/roompowerlevelsevent.h8
-rw-r--r--lib/events/simplestateevents.h80
-rw-r--r--lib/events/single_key_value.h27
-rw-r--r--lib/events/stateevent.cpp8
-rw-r--r--lib/events/stateevent.h41
-rw-r--r--lib/events/stickerevent.cpp26
-rw-r--r--lib/events/stickerevent.h19
42 files changed, 1051 insertions, 1203 deletions
diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h
index 12f1f00b..24c3353c 100644
--- a/lib/events/accountdataevents.h
+++ b/lib/events/accountdataevents.h
@@ -32,7 +32,7 @@ struct JsonObjectConverter<TagRecord> {
if (orderJv.isDouble())
rec.order = fromJson<float>(orderJv);
if (orderJv.isString()) {
- bool ok;
+ bool ok = false;
rec.order = orderJv.toString().toFloat(&ok);
if (!ok)
rec.order = none;
@@ -46,27 +46,14 @@ struct JsonObjectConverter<TagRecord> {
using TagsMap = QHash<QString, TagRecord>;
-#define DEFINE_SIMPLE_EVENT(_Name, _TypeId, _ContentType, _ContentKey) \
- class QUOTIENT_API _Name : public Event { \
- public: \
- using content_type = _ContentType; \
- DEFINE_EVENT_TYPEID(_TypeId, _Name) \
- explicit _Name(const QJsonObject& obj) : Event(typeId(), obj) {} \
- explicit _Name(const content_type& content) \
- : Event(typeId(), matrixTypeId(), \
- QJsonObject { \
- { QStringLiteral(#_ContentKey), toJson(content) } }) \
- {} \
- auto _ContentKey() const \
- { \
- return contentPart<content_type>(#_ContentKey##_ls); \
- } \
- }; \
- REGISTER_EVENT_TYPE(_Name) \
- // End of macro
-
-DEFINE_SIMPLE_EVENT(TagEvent, "m.tag", TagsMap, tags)
-DEFINE_SIMPLE_EVENT(ReadMarkerEvent, "m.fully_read", QString, event_id)
-DEFINE_SIMPLE_EVENT(IgnoredUsersEvent, "m.ignored_user_list", QSet<QString>,
+DEFINE_SIMPLE_EVENT(TagEvent, Event, "m.tag", TagsMap, tags)
+DEFINE_SIMPLE_EVENT(ReadMarkerEventImpl, Event, "m.fully_read", QString, eventId)
+class ReadMarkerEvent : public ReadMarkerEventImpl {
+public:
+ using ReadMarkerEventImpl::ReadMarkerEventImpl;
+ [[deprecated("Use ReadMarkerEvent::eventId() instead")]]
+ QString event_id() const { return eventId(); }
+};
+DEFINE_SIMPLE_EVENT(IgnoredUsersEvent, Event, "m.ignored_user_list", QSet<QString>,
ignored_users)
} // namespace Quotient
diff --git a/lib/events/callanswerevent.cpp b/lib/events/callanswerevent.cpp
index be83d9d0..f75f8ad3 100644
--- a/lib/events/callanswerevent.cpp
+++ b/lib/events/callanswerevent.cpp
@@ -14,7 +14,6 @@ m.call.answer
"type": "answer"
},
"call_id": "12345",
- "lifetime": 60000,
"version": 0
},
"event_id": "$WLGTSEFSEF:localhost",
@@ -33,16 +32,6 @@ CallAnswerEvent::CallAnswerEvent(const QJsonObject& obj)
qCDebug(EVENTS) << "Call Answer event";
}
-CallAnswerEvent::CallAnswerEvent(const QString& callId, const int lifetime,
- const QString& sdp)
- : CallEventBase(
- typeId(), matrixTypeId(), callId, 0,
- { { QStringLiteral("lifetime"), lifetime },
- { QStringLiteral("answer"),
- QJsonObject { { QStringLiteral("type"), QStringLiteral("answer") },
- { QStringLiteral("sdp"), sdp } } } })
-{}
-
CallAnswerEvent::CallAnswerEvent(const QString& callId, const QString& sdp)
: CallEventBase(
typeId(), matrixTypeId(), callId, 0,
diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h
index 8ffe60f2..4d539b85 100644
--- a/lib/events/callanswerevent.h
+++ b/lib/events/callanswerevent.h
@@ -13,14 +13,8 @@ public:
explicit CallAnswerEvent(const QJsonObject& obj);
- explicit CallAnswerEvent(const QString& callId, const int lifetime,
- const QString& sdp);
explicit CallAnswerEvent(const QString& callId, const QString& sdp);
- int lifetime() const
- {
- return contentPart<int>("lifetime"_ls);
- } // FIXME: Omittable<>?
QString sdp() const
{
return contentPart<QJsonObject>("answer"_ls).value("sdp"_ls).toString();
diff --git a/lib/events/callcandidatesevent.h b/lib/events/callcandidatesevent.h
index 74c38f2c..e949f722 100644
--- a/lib/events/callcandidatesevent.h
+++ b/lib/events/callcandidatesevent.h
@@ -23,20 +23,9 @@ public:
{ { QStringLiteral("candidates"), candidates } })
{}
- QJsonArray candidates() const
- {
- return contentPart<QJsonArray>("candidates"_ls);
- }
-
- QString callId() const
- {
- return contentPart<QString>("call_id");
- }
-
- int version() const
- {
- return contentPart<int>("version");
- }
+ QUO_CONTENT_GETTER(QJsonArray, candidates)
+ QUO_CONTENT_GETTER(QString, callId)
+ QUO_CONTENT_GETTER(int, version)
};
REGISTER_EVENT_TYPE(CallCandidatesEvent)
diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp
index 11d50768..2f26a1cb 100644
--- a/lib/events/callinviteevent.cpp
+++ b/lib/events/callinviteevent.cpp
@@ -33,7 +33,7 @@ CallInviteEvent::CallInviteEvent(const QJsonObject& obj)
qCDebug(EVENTS) << "Call Invite event";
}
-CallInviteEvent::CallInviteEvent(const QString& callId, const int lifetime,
+CallInviteEvent::CallInviteEvent(const QString& callId, int lifetime,
const QString& sdp)
: CallEventBase(
typeId(), matrixTypeId(), callId, 0,
diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h
index 47362b5c..5b4ca0df 100644
--- a/lib/events/callinviteevent.h
+++ b/lib/events/callinviteevent.h
@@ -13,13 +13,10 @@ public:
explicit CallInviteEvent(const QJsonObject& obj);
- explicit CallInviteEvent(const QString& callId, const int lifetime,
+ explicit CallInviteEvent(const QString& callId, int lifetime,
const QString& sdp);
- int lifetime() const
- {
- return contentPart<int>("lifetime"_ls);
- } // FIXME: Omittable<>?
+ QUO_CONTENT_GETTER(int, lifetime)
QString sdp() const
{
return contentPart<QJsonObject>("offer"_ls).value("sdp"_ls).toString();
diff --git a/lib/events/directchatevent.cpp b/lib/events/directchatevent.cpp
index 0ee1f7b0..83bb1e32 100644
--- a/lib/events/directchatevent.cpp
+++ b/lib/events/directchatevent.cpp
@@ -3,8 +3,6 @@
#include "directchatevent.h"
-#include <QtCore/QJsonArray>
-
using namespace Quotient;
QMultiHash<QString, QString> DirectChatEvent::usersToDirectChats() const
diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp
index 3af3d6ff..ec00ad4c 100644
--- a/lib/events/encryptedevent.cpp
+++ b/lib/events/encryptedevent.cpp
@@ -49,14 +49,16 @@ RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const
eventObject["event_id"] = id();
eventObject["sender"] = senderId();
eventObject["origin_server_ts"] = originTimestamp().toMSecsSinceEpoch();
- if (const auto relatesToJson = contentPart("m.relates_to"_ls); !relatesToJson.isUndefined()) {
+ if (const auto relatesToJson = contentPart<QJsonObject>("m.relates_to"_ls);
+ !relatesToJson.isEmpty()) {
auto content = eventObject["content"].toObject();
- content["m.relates_to"] = relatesToJson.toObject();
+ content["m.relates_to"] = relatesToJson;
eventObject["content"] = content;
}
- if (const auto redactsJson = unsignedPart("redacts"_ls); !redactsJson.isUndefined()) {
+ if (const auto redactsJson = unsignedPart<QString>("redacts"_ls);
+ !redactsJson.isEmpty()) {
auto unsign = eventObject["unsigned"].toObject();
- unsign["redacts"] = redactsJson.toString();
+ unsign["redacts"] = redactsJson;
eventObject["unsigned"] = unsign;
}
return loadEvent<RoomEvent>(eventObject);
@@ -64,7 +66,7 @@ RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const
void EncryptedEvent::setRelation(const QJsonObject& relation)
{
- auto content = editJson()["content"_ls].toObject();
+ auto content = contentJson();
content["m.relates_to"] = relation;
editJson()["content"] = content;
}
diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h
index bfacdec9..ddd5e415 100644
--- a/lib/events/encryptedevent.h
+++ b/lib/events/encryptedevent.h
@@ -58,8 +58,6 @@ public:
RoomEventPtr createDecrypted(const QString &decrypted) const;
void setRelation(const QJsonObject& relation);
-
- bool isVerified();
};
REGISTER_EVENT_TYPE(EncryptedEvent)
diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp
deleted file mode 100644
index bb4e26c7..00000000
--- a/lib/events/encryptedfile.cpp
+++ /dev/null
@@ -1,118 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
-//
-// SPDX-License-Identifier: LGPL-2.1-or-later
-
-#include "encryptedfile.h"
-#include "logging.h"
-
-#ifdef Quotient_E2EE_ENABLED
-#include <openssl/evp.h>
-#include <QtCore/QCryptographicHash>
-#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<const unsigned char*>(
- keyBytes.data()),
- reinterpret_cast<const unsigned char*>(
- QByteArray::fromBase64(iv.toLatin1()).data()));
- EVP_DecryptUpdate(
- ctx, reinterpret_cast<unsigned char*>(plaintext.data()), &length,
- reinterpret_cast<const unsigned char*>(ciphertext.data()),
- ciphertext.size());
- EVP_DecryptFinal_ex(ctx,
- reinterpret_cast<unsigned char*>(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, QByteArray> 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();
- QByteArray cipherText(plainText.size(), plainText.size() + EVP_MAX_BLOCK_LENGTH - 1);
- EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, reinterpret_cast<const unsigned char*>(k.data()),reinterpret_cast<const unsigned char*>(iv.data()));
- EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char*>(cipherText.data()), &length, reinterpret_cast<const unsigned char*>(plainText.data()), plainText.size());
- EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char*>(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<EncryptedFile>::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<EncryptedFile>::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<JWK>::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<JWK>::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 b2808395..00000000
--- a/lib/events/encryptedfile.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
-//
-// 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<QString, QString> hashes MEMBER hashes CONSTANT)
- Q_PROPERTY(QString v MEMBER v CONSTANT)
-
-public:
- QUrl url;
- JWK key;
- QString iv;
- QHash<QString, QString> hashes;
- QString v;
-
- QByteArray decryptFile(const QByteArray &ciphertext) const;
- static std::pair<EncryptedFile, QByteArray> encryptFile(const QByteArray& plainText);
-};
-
-template <>
-struct QUOTIENT_API JsonObjectConverter<EncryptedFile> {
- static void dumpTo(QJsonObject& jo, const EncryptedFile& pod);
- static void fillFrom(const QJsonObject& jo, EncryptedFile& pod);
-};
-
-template <>
-struct QUOTIENT_API JsonObjectConverter<JWK> {
- static void dumpTo(QJsonObject& jo, const JWK& pod);
- static void fillFrom(const QJsonObject& jo, JWK& pod);
-};
-} // namespace Quotient
diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp
index 6272c668..8872447b 100644
--- a/lib/events/encryptionevent.cpp
+++ b/lib/events/encryptionevent.cpp
@@ -6,52 +6,47 @@
#include "e2ee/e2ee.h"
-#include <array>
+using namespace Quotient;
-namespace Quotient {
-static const std::array<QString, 1> encryptionStrings = {
- { MegolmV1AesSha2AlgoKey }
-};
+static constexpr std::array encryptionStrings { MegolmV1AesSha2AlgoKey };
template <>
-struct JsonConverter<EncryptionType> {
- static EncryptionType load(const QJsonValue& jv)
- {
- const auto& encryptionString = jv.toString();
- for (auto it = encryptionStrings.begin(); it != encryptionStrings.end();
- ++it)
- if (encryptionString == *it)
- return EncryptionType(it - encryptionStrings.begin());
-
- if (!encryptionString.isEmpty())
- qCWarning(EVENTS) << "Unknown EncryptionType: " << encryptionString;
- return EncryptionType::Undefined;
- }
-};
-} // namespace Quotient
-
-using namespace Quotient;
+EncryptionType Quotient::fromJson(const QJsonValue& jv)
+{
+ const auto& encryptionString = jv.toString();
+ for (auto it = encryptionStrings.begin(); it != encryptionStrings.end();
+ ++it)
+ if (encryptionString == *it)
+ return EncryptionType(it - encryptionStrings.begin());
+
+ if (!encryptionString.isEmpty())
+ qCWarning(EVENTS) << "Unknown EncryptionType: " << encryptionString;
+ return EncryptionType::Undefined;
+}
EncryptionEventContent::EncryptionEventContent(const QJsonObject& json)
- : encryption(fromJson<EncryptionType>(json[AlgorithmKeyL]))
+ : encryption(fromJson<Quotient::EncryptionType>(json[AlgorithmKeyL]))
, algorithm(sanitized(json[AlgorithmKeyL].toString()))
- , rotationPeriodMs(json[RotationPeriodMsKeyL].toInt(604800000))
- , rotationPeriodMsgs(json[RotationPeriodMsgsKeyL].toInt(100))
-{}
+{
+ // NB: fillFromJson only fills the variable if the JSON key exists
+ fillFromJson<int>(json[RotationPeriodMsKeyL], rotationPeriodMs);
+ fillFromJson<int>(json[RotationPeriodMsgsKeyL], rotationPeriodMsgs);
+}
-EncryptionEventContent::EncryptionEventContent(EncryptionType et)
+EncryptionEventContent::EncryptionEventContent(Quotient::EncryptionType et)
: encryption(et)
{
- if(encryption != Undefined) {
- algorithm = encryptionStrings[encryption];
+ if(encryption != Quotient::EncryptionType::Undefined) {
+ algorithm = encryptionStrings[static_cast<size_t>(encryption)];
}
}
-void EncryptionEventContent::fillJson(QJsonObject* o) const
+QJsonObject EncryptionEventContent::toJson() const
{
- Q_ASSERT(o);
- if (encryption != EncryptionType::Undefined)
- o->insert(AlgorithmKey, algorithm);
- o->insert(RotationPeriodMsKey, rotationPeriodMs);
- o->insert(RotationPeriodMsgsKey, rotationPeriodMsgs);
+ QJsonObject o;
+ if (encryption != Quotient::EncryptionType::Undefined)
+ o.insert(AlgorithmKey, algorithm);
+ o.insert(RotationPeriodMsKey, rotationPeriodMs);
+ o.insert(RotationPeriodMsgsKey, rotationPeriodMsgs);
+ return o;
}
diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h
index 124ced33..91452c3f 100644
--- a/lib/events/encryptionevent.h
+++ b/lib/events/encryptionevent.h
@@ -4,57 +4,49 @@
#pragma once
-#include "eventcontent.h"
-#include "stateevent.h"
#include "quotient_common.h"
+#include "stateevent.h"
namespace Quotient {
-class QUOTIENT_API EncryptionEventContent : public EventContent::Base {
+class QUOTIENT_API EncryptionEventContent {
public:
- enum EncryptionType : size_t { MegolmV1AesSha2 = 0, Undefined };
+ using EncryptionType
+ [[deprecated("Use Quotient::EncryptionType instead")]] =
+ Quotient::EncryptionType;
- QUO_IMPLICIT EncryptionEventContent(EncryptionType et);
- [[deprecated("This constructor will require explicit EncryptionType soon")]] //
- explicit EncryptionEventContent()
- : EncryptionEventContent(Undefined)
- {}
+ // NOLINTNEXTLINE(google-explicit-constructor)
+ QUO_IMPLICIT EncryptionEventContent(Quotient::EncryptionType et);
explicit EncryptionEventContent(const QJsonObject& json);
- EncryptionType encryption;
- QString algorithm;
- int rotationPeriodMs;
- int rotationPeriodMsgs;
+ QJsonObject toJson() const;
-protected:
- void fillJson(QJsonObject* o) const override;
+ Quotient::EncryptionType encryption;
+ QString algorithm {};
+ int rotationPeriodMs = 604'800'000;
+ int rotationPeriodMsgs = 100;
};
-using EncryptionType = EncryptionEventContent::EncryptionType;
-
class QUOTIENT_API EncryptionEvent : public StateEvent<EncryptionEventContent> {
- Q_GADGET
public:
DEFINE_EVENT_TYPEID("m.room.encryption", EncryptionEvent)
- using EncryptionType = EncryptionEventContent::EncryptionType;
- Q_ENUM(EncryptionType)
+ using EncryptionType
+ [[deprecated("Use Quotient::EncryptionType instead")]] =
+ Quotient::EncryptionType;
explicit EncryptionEvent(const QJsonObject& obj)
: StateEvent(typeId(), obj)
{}
- [[deprecated("This constructor will require an explicit parameter soon")]] //
-// explicit EncryptionEvent()
-// : EncryptionEvent(QJsonObject())
-// {}
explicit EncryptionEvent(EncryptionEventContent&& content)
: StateEvent(typeId(), matrixTypeId(), QString(), std::move(content))
{}
- EncryptionType encryption() const { return content().encryption; }
-
+ Quotient::EncryptionType encryption() const { return content().encryption; }
QString algorithm() const { return content().algorithm; }
int rotationPeriodMs() const { return content().rotationPeriodMs; }
int rotationPeriodMsgs() const { return content().rotationPeriodMsgs; }
+
+ bool useEncryption() const { return !algorithm().isEmpty(); }
};
REGISTER_EVENT_TYPE(EncryptionEvent)
} // namespace Quotient
diff --git a/lib/events/event.cpp b/lib/events/event.cpp
index 4c304a3c..1f1eebaa 100644
--- a/lib/events/event.cpp
+++ b/lib/events/event.cpp
@@ -29,7 +29,7 @@ Event::Event(Type type, const QJsonObject& json) : _type(type), _json(json)
}
Event::Event(Type type, event_mtype_t matrixType, const QJsonObject& contentJson)
- : Event(type, basicEventJson(matrixType, contentJson))
+ : Event(type, basicJson(matrixType, contentJson))
{}
Event::~Event() = default;
diff --git a/lib/events/event.h b/lib/events/event.h
index 113fa3fa..b7454337 100644
--- a/lib/events/event.h
+++ b/lib/events/event.h
@@ -48,13 +48,6 @@ const QString RoomIdKey { RoomIdKeyL };
const QString UnsignedKey { UnsignedKeyL };
const QString StateKeyKey { StateKeyKeyL };
-/// Make a minimal correct Matrix event JSON
-inline QJsonObject basicEventJson(const QString& matrixType,
- const QJsonObject& content)
-{
- return { { TypeKey, matrixType }, { ContentKey, content } };
-}
-
// === Event types ===
using event_type_t = QLatin1String;
@@ -193,6 +186,13 @@ public:
Event& operator=(Event&&) = delete;
virtual ~Event();
+ /// Make a minimal correct Matrix event JSON
+ static QJsonObject basicJson(const QString& matrixType,
+ const QJsonObject& content)
+ {
+ return { { TypeKey, matrixType }, { ContentKey, content } };
+ }
+
Type type() const { return _type; }
QString matrixType() const;
[[deprecated("Use fullJson() and stringify it with QJsonDocument::toJson() "
@@ -212,7 +212,7 @@ public:
const QJsonObject contentJson() const;
- template <typename T = QJsonValue, typename KeyT>
+ template <typename T, typename KeyT>
const T contentPart(KeyT&& key) const
{
return fromJson<T>(contentJson()[std::forward<KeyT>(key)]);
@@ -227,7 +227,7 @@ public:
const QJsonObject unsignedJson() const;
- template <typename T = QJsonValue, typename KeyT>
+ template <typename T, typename KeyT>
const T unsignedPart(KeyT&& key) const
{
return fromJson<T>(unsignedJson()[std::forward<KeyT>(key)]);
@@ -258,6 +258,21 @@ template <typename EventT>
using EventsArray = std::vector<event_ptr_tt<EventT>>;
using Events = EventsArray<Event>;
+//! \brief Define an inline method obtaining a content part
+//!
+//! This macro adds a const method that extracts a JSON value at the key
+//! <tt>toSnakeCase(PartName_)</tt> (sic) and converts it to the type
+//! \p PartType_. Effectively, the generated method is an equivalent of
+//! \code
+//! contentPart<PartType_>(Quotient::toSnakeCase(#PartName_##_ls));
+//! \endcode
+#define QUO_CONTENT_GETTER(PartType_, PartName_) \
+ PartType_ PartName_() const \
+ { \
+ static const auto JsonKey = toSnakeCase(#PartName_##_ls); \
+ return contentPart<PartType_>(JsonKey); \
+ }
+
// === Facilities for event class definitions ===
// This macro should be used in a public section of an event class to
@@ -278,6 +293,32 @@ using Events = EventsArray<Event>;
Type_::factory.addMethod<Type_>(); \
// End of macro
+/// \brief Define a new event class with a single key-value pair in the content
+///
+/// This macro defines a new event class \p Name_ derived from \p Base_,
+/// with Matrix event type \p TypeId_, providing a getter named \p GetterName_
+/// for a single value of type \p ValueType_ inside the event content.
+/// To retrieve the value the getter uses a JSON key name that corresponds to
+/// its own (getter's) name but written in snake_case. \p GetterName_ must be
+/// in camelCase, no quotes (an identifier, not a literal).
+#define DEFINE_SIMPLE_EVENT(Name_, Base_, TypeId_, ValueType_, GetterName_) \
+ class QUOTIENT_API Name_ : public Base_ { \
+ public: \
+ using content_type = ValueType_; \
+ DEFINE_EVENT_TYPEID(TypeId_, Name_) \
+ explicit Name_(const QJsonObject& obj) : Base_(TypeId, obj) {} \
+ explicit Name_(const content_type& content) \
+ : Name_(Base_::basicJson(TypeId, { { JsonKey, toJson(content) } })) \
+ {} \
+ auto GetterName_() const \
+ { \
+ return contentPart<content_type>(JsonKey); \
+ } \
+ static inline const auto JsonKey = toSnakeCase(#GetterName_##_ls); \
+ }; \
+ REGISTER_EVENT_TYPE(Name_) \
+ // End of macro
+
// === is<>(), eventCast<>() and switchOnType<>() ===
template <class EventT>
@@ -291,65 +332,72 @@ inline bool isUnknown(const Event& e)
return e.type() == UnknownEventTypeId;
}
+//! \brief Cast the event pointer down in a type-safe way
+//!
+//! Checks that the event \p eptr points to actually is of the requested type
+//! and returns a (plain) pointer to the event downcast to that type. \p eptr
+//! can be either "dumb" (BaseEventT*) or "smart" (`event_ptr_tt<>`). This
+//! overload doesn't affect the event ownership - if the original pointer owns
+//! the event it must outlive the downcast pointer to keep it from dangling.
template <class EventT, typename BasePtrT>
inline auto eventCast(const BasePtrT& eptr)
-> decltype(static_cast<EventT*>(&*eptr))
{
- Q_ASSERT(eptr);
- return is<std::decay_t<EventT>>(*eptr) ? static_cast<EventT*>(&*eptr)
- : nullptr;
+ return eptr && is<std::decay_t<EventT>>(*eptr)
+ ? static_cast<EventT*>(&*eptr)
+ : nullptr;
}
-// A trivial generic catch-all "switch"
-template <class BaseEventT, typename FnT>
-inline auto switchOnType(const BaseEventT& event, FnT&& fn)
- -> decltype(fn(event))
+//! \brief Cast the event pointer down in a type-safe way, with moving
+//!
+//! Checks that the event \p eptr points to actually is of the requested type;
+//! if (and only if) it is, releases the pointer, downcasts it to the requested
+//! event type and returns a new smart pointer wrapping the downcast one.
+//! Unlike the non-moving eventCast() overload, this one only accepts a smart
+//! pointer, and that smart pointer should be an rvalue (either a temporary,
+//! or as a result of std::move()). The ownership, respectively, is transferred
+//! to the new pointer; the original smart pointer is reset to nullptr, as is
+//! normal for `unique_ptr<>::release()`.
+//! \note If \p eptr's event type does not match \p EventT it retains ownership
+//! after calling this overload; if it is a temporary, this normally
+//! leads to the event getting deleted along with the end of
+//! the temporary's lifetime.
+template <class EventT, typename BaseEventT>
+inline auto eventCast(event_ptr_tt<BaseEventT>&& eptr)
{
- return fn(event);
+ return eptr && is<std::decay_t<EventT>>(*eptr)
+ ? event_ptr_tt<EventT>(static_cast<EventT*>(eptr.release()))
+ : nullptr;
}
namespace _impl {
- // Using bool instead of auto below because auto apparently upsets MSVC
- template <class BaseT, typename FnT>
- constexpr bool needs_downcast =
- std::is_base_of_v<BaseT, std::decay_t<fn_arg_t<FnT>>>
- && !std::is_same_v<BaseT, std::decay_t<fn_arg_t<FnT>>>;
+ template <typename FnT, class BaseT>
+ concept Invocable_With_Downcast =
+ std::is_base_of_v<BaseT, std::remove_cvref_t<fn_arg_t<FnT>>>;
}
-// A trivial type-specific "switch" for a void function
-template <class BaseT, typename FnT>
-inline auto switchOnType(const BaseT& event, FnT&& fn)
- -> std::enable_if_t<_impl::needs_downcast<BaseT, FnT>
- && std::is_void_v<fn_return_t<FnT>>>
+template <class BaseT, typename TailT>
+inline auto switchOnType(const BaseT& event, TailT&& tail)
{
- using event_type = fn_arg_t<FnT>;
- if (is<std::decay_t<event_type>>(event))
- fn(static_cast<event_type>(event));
-}
-
-// A trivial type-specific "switch" for non-void functions with an optional
-// default value; non-voidness is guarded by defaultValue type
-template <class BaseT, typename FnT>
-inline auto switchOnType(const BaseT& event, FnT&& fn,
- fn_return_t<FnT>&& defaultValue = {})
- -> std::enable_if_t<_impl::needs_downcast<BaseT, FnT>, fn_return_t<FnT>>
-{
- using event_type = fn_arg_t<FnT>;
- if (is<std::decay_t<event_type>>(event))
- return fn(static_cast<event_type>(event));
- return std::move(defaultValue);
+ if constexpr (std::is_invocable_v<TailT, BaseT>) {
+ return tail(event);
+ } else if constexpr (_impl::Invocable_With_Downcast<TailT, BaseT>) {
+ using event_type = fn_arg_t<TailT>;
+ if (is<std::decay_t<event_type>>(event))
+ return tail(static_cast<event_type>(event));
+ return std::invoke_result_t<TailT, event_type>(); // Default-constructed
+ } else { // Treat it as a value to return
+ return std::forward<TailT>(tail);
+ }
}
-// A switch for a chain of 2 or more functions
-template <class BaseT, typename FnT1, typename FnT2, typename... FnTs>
-inline std::common_type_t<fn_return_t<FnT1>, fn_return_t<FnT2>>
-switchOnType(const BaseT& event, FnT1&& fn1, FnT2&& fn2, FnTs&&... fns)
+template <class BaseT, typename FnT1, typename... FnTs>
+inline auto switchOnType(const BaseT& event, FnT1&& fn1, FnTs&&... fns)
{
using event_type1 = fn_arg_t<FnT1>;
if (is<std::decay_t<event_type1>>(event))
- return fn1(static_cast<event_type1&>(event));
- return switchOnType(event, std::forward<FnT2>(fn2),
- std::forward<FnTs>(fns)...);
+ return fn1(static_cast<event_type1>(event));
+ return switchOnType(event, std::forward<FnTs>(fns)...);
}
template <class BaseT, typename... FnTs>
@@ -364,8 +412,8 @@ inline auto visit(const BaseT& event, FnTs&&... fns)
// TODO: replace with ranges::for_each once all standard libraries have it
template <typename RangeT, typename... FnTs>
inline auto visitEach(RangeT&& events, FnTs&&... fns)
- -> std::enable_if_t<std::is_void_v<
- decltype(switchOnType(**begin(events), std::forward<FnTs>(fns)...))>>
+ requires std::is_void_v<
+ decltype(switchOnType(**begin(events), std::forward<FnTs>(fns)...))>
{
for (auto&& evtPtr: events)
switchOnType(*evtPtr, std::forward<FnTs>(fns)...);
diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp
index 9d7edf20..8db3b7e3 100644
--- a/lib/events/eventcontent.cpp
+++ b/lib/events/eventcontent.cpp
@@ -15,26 +15,25 @@ using std::move;
QJsonObject Base::toJson() const
{
QJsonObject o;
- fillJson(&o);
+ fillJson(o);
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> file, 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(file)
{
if (!isValid())
qCWarning(MESSAGES)
@@ -43,77 +42,81 @@ 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,
- const Omittable<EncryptedFile> &file,
+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<qint64>(infoJson["size"_ls]))
, originalName(move(originalFilename))
- , file(file)
{
- 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;
}
-void FileInfo::fillInfoJson(QJsonObject* infoJson) const
+QUrl FileInfo::url() const
{
- Q_ASSERT(infoJson);
- if (payloadSize != -1)
- infoJson->insert(QStringLiteral("size"), payloadSize);
- if (mimeType.isValid())
- infoJson->insert(QStringLiteral("mimetype"), mimeType.name());
- //TODO add encryptedfile
+ return getUrlFromSourceInfo(source);
+}
+
+QJsonObject Quotient::EventContent::toInfoJson(const FileInfo& info)
+{
+ QJsonObject infoJson;
+ if (info.payloadSize != -1)
+ infoJson.insert(QStringLiteral("size"), info.payloadSize);
+ if (info.mimeType.isValid())
+ infoJson.insert(QStringLiteral("mimetype"), info.mimeType.name());
+ return infoJson;
}
ImageInfo::ImageInfo(const QFileInfo& fi, QSize imageSize)
: FileInfo(fi), imageSize(imageSize)
{}
-ImageInfo::ImageInfo(const QUrl& mxcUrl, qint64 fileSize, const QMimeType& type,
- QSize imageSize, const Omittable<EncryptedFile> &file, const QString& originalFilename)
- : FileInfo(mxcUrl, fileSize, type, file, originalFilename)
+ImageInfo::ImageInfo(FileSourceInfo sourceInfo, qint64 fileSize,
+ const QMimeType& type, QSize imageSize,
+ const QString& originalFilename)
+ : FileInfo(move(sourceInfo), fileSize, type, originalFilename)
, imageSize(imageSize)
{}
-ImageInfo::ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson,
- const Omittable<EncryptedFile> &file,
+ImageInfo::ImageInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson,
const QString& originalFilename)
- : FileInfo(mxcUrl, infoJson, file, originalFilename)
+ : FileInfo(move(sourceInfo), infoJson, originalFilename)
, imageSize(infoJson["w"_ls].toInt(), infoJson["h"_ls].toInt())
{}
-void ImageInfo::fillInfoJson(QJsonObject* infoJson) const
+QJsonObject Quotient::EventContent::toInfoJson(const ImageInfo& info)
{
- FileInfo::fillInfoJson(infoJson);
- if (imageSize.width() != -1)
- infoJson->insert(QStringLiteral("w"), imageSize.width());
- if (imageSize.height() != -1)
- infoJson->insert(QStringLiteral("h"), imageSize.height());
+ auto infoJson = toInfoJson(static_cast<const FileInfo&>(info));
+ if (info.imageSize.width() != -1)
+ infoJson.insert(QStringLiteral("w"), info.imageSize.width());
+ if (info.imageSize.height() != -1)
+ infoJson.insert(QStringLiteral("h"), info.imageSize.height());
+ return infoJson;
}
-Thumbnail::Thumbnail(const QJsonObject& infoJson, const Omittable<EncryptedFile> &file)
+Thumbnail::Thumbnail(const QJsonObject& infoJson,
+ const Omittable<EncryptedFileMetadata>& efm)
: ImageInfo(QUrl(infoJson["thumbnail_url"_ls].toString()),
- infoJson["thumbnail_info"_ls].toObject(),
- file)
-{}
+ infoJson["thumbnail_info"_ls].toObject())
+{
+ if (efm)
+ source = *efm;
+}
-void Thumbnail::fillInfoJson(QJsonObject* infoJson) const
+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<ImageInfo>(*this));
+ infoJson.insert(QStringLiteral("thumbnail_info"),
+ toInfoJson(*this));
}
diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h
index de9a792b..af26c0a4 100644
--- a/lib/events/eventcontent.h
+++ b/lib/events/eventcontent.h
@@ -6,279 +6,249 @@
// 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 <QtCore/QJsonObject>
+#include <QtCore/QMetaType>
#include <QtCore/QMimeType>
#include <QtCore/QSize>
#include <QtCore/QUrl>
-#include <QtCore/QMetaType>
class QFileInfo;
-namespace Quotient {
-namespace EventContent {
- /**
- * A base class for all content types that can be stored
- * in a RoomMessageEvent
- *
- * Each content type class should have a constructor taking
- * a QJsonObject and override fillJson() with an implementation
- * that will fill the target QJsonObject with stored values. It is
- * assumed but not required that a content object can also be created
- * from plain data.
- */
- class QUOTIENT_API Base {
- public:
- explicit Base(QJsonObject o = {}) : originalJson(std::move(o)) {}
- virtual ~Base() = default;
-
- // FIXME: make toJson() from converters.* work on base classes
- QJsonObject toJson() const;
-
- public:
- QJsonObject originalJson;
-
- protected:
- Base(const Base&) = default;
- Base(Base&&) = default;
-
- virtual void fillJson(QJsonObject* o) const = 0;
- };
-
- // The below structures fairly follow CS spec 11.2.1.6. The overall
- // set of attributes for each content types is a superset of the spec
- // but specific aggregation structure is altered. See doc comments to
- // each type for the list of available attributes.
-
- // A quick classes inheritance structure follows (the definitions are
- // spread across eventcontent.h and roommessageevent.h):
- // FileInfo
- // FileContent : UrlWithThumbnailContent<FileInfo>
- // AudioContent : PlayableContent<UrlBasedContent<FileInfo>>
- // ImageInfo : FileInfo + imageSize attribute
- // ImageContent : UrlWithThumbnailContent<ImageInfo>
- // VideoContent : PlayableContent<UrlWithThumbnailContent<ImageInfo>>
-
- /**
- * A base/mixin class for structures representing an "info" object for
- * some content types. These include most attachment types currently in
- * the CS API spec.
- *
- * In order to use it in a content class, derive both from TypedBase
- * (or Base) and from FileInfo (or its derivative, such as \p ImageInfo)
- * and call fillInfoJson() to fill the "info" subobject. Make sure
- * to pass an "info" part of JSON to FileInfo constructor, not the whole
- * JSON content, as well as contents of "url" (or a similar key) and
- * optionally "filename" node from the main JSON content. Assuming you
- * don't do unusual things, you should use \p UrlBasedContent<> instead
- * of doing multiple inheritance and overriding Base::fillJson() by hand.
- *
- * This class is not polymorphic.
- */
- class QUOTIENT_API FileInfo {
- public:
- FileInfo() = default;
- explicit FileInfo(const QFileInfo& fi);
- explicit FileInfo(QUrl mxcUrl, qint64 payloadSize = -1,
- const QMimeType& mimeType = {},
- Omittable<EncryptedFile> file = none,
- QString originalFilename = {});
- FileInfo(QUrl mxcUrl, const QJsonObject& infoJson,
- const Omittable<EncryptedFile> &file,
- QString originalFilename = {});
-
- bool isValid() const;
-
- void fillInfoJson(QJsonObject* infoJson) 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(); }
-
- public:
- QJsonObject originalInfoJson;
- QMimeType mimeType;
- QUrl url;
- qint64 payloadSize = 0;
- QString originalName;
- Omittable<EncryptedFile> file = none;
- };
-
- template <typename InfoT>
- QJsonObject toInfoJson(const InfoT& info)
+namespace Quotient::EventContent {
+//! \brief Base for all content types that can be stored in RoomMessageEvent
+//!
+//! Each content type class should have a constructor taking
+//! a QJsonObject and override fillJson() with an implementation
+//! that will fill the target QJsonObject with stored values. It is
+//! assumed but not required that a content object can also be created
+//! from plain data.
+class QUOTIENT_API Base {
+public:
+ explicit Base(QJsonObject o = {}) : originalJson(std::move(o)) {}
+ virtual ~Base() = default;
+
+ QJsonObject toJson() const;
+
+public:
+ QJsonObject originalJson;
+
+ // You can't assign those classes
+ Base& operator=(const Base&) = delete;
+ Base& operator=(Base&&) = delete;
+
+protected:
+ Base(const Base&) = default;
+ Base(Base&&) noexcept = default;
+
+ virtual void fillJson(QJsonObject&) const = 0;
+};
+
+// The below structures fairly follow CS spec 11.2.1.6. The overall
+// set of attributes for each content types is a superset of the spec
+// but specific aggregation structure is altered. See doc comments to
+// each type for the list of available attributes.
+
+// A quick classes inheritance structure follows (the definitions are
+// spread across eventcontent.h and roommessageevent.h):
+// UrlBasedContent<InfoT> : InfoT + thumbnail data
+// PlayableContent<InfoT> : + duration attribute
+// FileInfo
+// FileContent = UrlBasedContent<FileInfo>
+// AudioContent = PlayableContent<FileInfo>
+// ImageInfo : FileInfo + imageSize attribute
+// ImageContent = UrlBasedContent<ImageInfo>
+// VideoContent = PlayableContent<ImageInfo>
+
+//! \brief Mix-in class representing `info` subobject in content JSON
+//!
+//! This is one of base classes for content types that deal with files or
+//! URLs. It stores the file metadata attributes, such as size, MIME type
+//! etc. found in the `content/info` subobject of event JSON payloads.
+//! Actual content classes derive from this class _and_ TypedBase that
+//! provides a polymorphic interface to access data in the mix-in. FileInfo
+//! (as well as ImageInfo, that adds image size to the metadata) is NOT
+//! polymorphic and is used in a non-polymorphic way to store thumbnail
+//! metadata (in a separate instance), next to the metadata on the file
+//! itself.
+//!
+//! If you need to make a new _content_ (not info) class based on files/URLs
+//! take UrlBasedContent as the example, i.e.:
+//! 1. Double-inherit from this class (or ImageInfo) and TypedBase.
+//! 2. Provide a constructor from QJsonObject that will pass the `info`
+//! subobject (not the whole content JSON) down to FileInfo/ImageInfo.
+//! 3. Override fillJson() to customise the JSON export logic. Make sure
+//! to call toInfoJson() from it to produce the payload for the `info`
+//! subobject in the JSON payload.
+//!
+//! \sa ImageInfo, FileContent, ImageContent, AudioContent, VideoContent,
+//! UrlBasedContent
+class QUOTIENT_API FileInfo {
+public:
+ FileInfo() = default;
+ //! \brief Construct from a QFileInfo object
+ //!
+ //! \param fi a QFileInfo object referring to an existing file
+ explicit FileInfo(const QFileInfo& fi);
+ explicit FileInfo(FileSourceInfo sourceInfo, qint64 payloadSize = -1,
+ const QMimeType& mimeType = {},
+ 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(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(); }
+
+public:
+ FileSourceInfo source;
+ QJsonObject originalInfoJson;
+ QMimeType mimeType;
+ qint64 payloadSize = 0;
+ QString originalName;
+};
+
+QUOTIENT_API QJsonObject toInfoJson(const FileInfo& info);
+
+//! \brief A content info class for image/video content types and thumbnails
+class QUOTIENT_API ImageInfo : public FileInfo {
+public:
+ ImageInfo() = default;
+ explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {});
+ explicit ImageInfo(FileSourceInfo sourceInfo, qint64 fileSize = -1,
+ const QMimeType& type = {}, QSize imageSize = {},
+ const QString& originalFilename = {});
+ ImageInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson,
+ const QString& originalFilename = {});
+
+public:
+ QSize imageSize;
+};
+
+QUOTIENT_API QJsonObject toInfoJson(const ImageInfo& info);
+
+//! \brief An auxiliary class for an info type that carries a thumbnail
+//!
+//! This class saves/loads a thumbnail to/from `info` subobject of
+//! the JSON representation of event content; namely, `info/thumbnail_url`
+//! (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;
+ explicit Thumbnail(const QJsonObject& infoJson,
+ const Omittable<EncryptedFileMetadata>& efm = none);
+
+ //! \brief Add thumbnail information to the passed `info` JSON object
+ void dumpTo(QJsonObject& infoJson) const;
+};
+
+class QUOTIENT_API TypedBase : public Base {
+public:
+ virtual QMimeType type() const = 0;
+ virtual const FileInfo* fileInfo() const { return nullptr; }
+ virtual FileInfo* fileInfo() { return nullptr; }
+ virtual const Thumbnail* thumbnailInfo() const { return nullptr; }
+
+protected:
+ explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {}
+ using Base::Base;
+};
+
+//! \brief A template class for content types with a URL and additional info
+//!
+//! 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 InfoT>
+class UrlBasedContent : public TypedBase, public InfoT {
+public:
+ using InfoT::InfoT;
+ explicit UrlBasedContent(const QJsonObject& json)
+ : TypedBase(json)
+ , InfoT(QUrl(json["url"].toString()), json["info"].toObject(),
+ json["filename"].toString())
+ , thumbnail(FileInfo::originalInfoJson)
{
- QJsonObject infoJson;
- info.fillInfoJson(&infoJson);
- return infoJson;
+ if (const auto efmJson = json.value("file"_ls).toObject();
+ !efmJson.isEmpty())
+ InfoT::source = fromJson<EncryptedFileMetadata>(efmJson);
+ // Two small hacks on originalJson to expose mediaIds to QML
+ originalJson.insert("mediaId", InfoT::mediaId());
+ originalJson.insert("thumbnailMediaId", thumbnail.mediaId());
}
- /**
- * A content info class for image content types: image, thumbnail, video
- */
- class QUOTIENT_API ImageInfo : public FileInfo {
- public:
- ImageInfo() = default;
- explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {});
- explicit ImageInfo(const QUrl& mxcUrl, qint64 fileSize = -1,
- const QMimeType& type = {}, QSize imageSize = {},
- const Omittable<EncryptedFile> &file = none,
- const QString& originalFilename = {});
- ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson,
- const Omittable<EncryptedFile> &encryptedFile,
- const QString& originalFilename = {});
-
- void fillInfoJson(QJsonObject* infoJson) const;
-
- public:
- QSize imageSize;
- };
-
- /**
- * An auxiliary class for an info type that carries a thumbnail
- *
- * 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.
- */
- class QUOTIENT_API Thumbnail : public ImageInfo {
- public:
- using ImageInfo::ImageInfo;
- Thumbnail(const QJsonObject& infoJson, const Omittable<EncryptedFile> &file = none);
-
- /**
- * Writes thumbnail information to "thumbnail_info" subobject
- * and thumbnail URL to "thumbnail_url" node inside "info".
- */
- void fillInfoJson(QJsonObject* infoJson) const;
- };
-
- class QUOTIENT_API TypedBase : public Base {
- public:
- virtual QMimeType type() const = 0;
- virtual const FileInfo* fileInfo() const { return nullptr; }
- virtual FileInfo* fileInfo() { return nullptr; }
- virtual const Thumbnail* thumbnailInfo() const { return nullptr; }
-
- protected:
- explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {}
- using Base::Base;
- };
-
- /**
- * A base class for content types that have 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.
- *
- * \tparam InfoT base info class
- */
- template <class InfoT>
- class QUOTIENT_API UrlBasedContent : public TypedBase, public InfoT {
- public:
- using InfoT::InfoT;
- explicit UrlBasedContent(const QJsonObject& json)
- : TypedBase(json)
- , InfoT(QUrl(json["url"].toString()), json["info"].toObject(),
- fromJson<Omittable<EncryptedFile>>(json["file"]), json["filename"].toString())
- {
- // A small hack to facilitate links creation in QML.
- originalJson.insert("mediaId", InfoT::mediaId());
- }
-
- QMimeType type() const override { return InfoT::mimeType; }
- const FileInfo* fileInfo() const override { return this; }
- FileInfo* fileInfo() override { return this; }
-
- protected:
- void fillJson(QJsonObject* json) const override
- {
- Q_ASSERT(json);
- if (!InfoT::file.has_value()) {
- json->insert("url", InfoT::url.toString());
- } else {
- json->insert("file", Quotient::toJson(*InfoT::file));
- }
- if (!InfoT::originalName.isEmpty())
- json->insert("filename", InfoT::originalName);
- json->insert("info", toInfoJson<InfoT>(*this));
- }
- };
-
- template <typename InfoT>
- class QUOTIENT_API UrlWithThumbnailContent : public UrlBasedContent<InfoT> {
- public:
- // NB: when using inherited constructors, thumbnail has to be
- // initialised separately
- using UrlBasedContent<InfoT>::UrlBasedContent;
- explicit UrlWithThumbnailContent(const QJsonObject& json)
- : UrlBasedContent<InfoT>(json), thumbnail(InfoT::originalInfoJson)
- {
- // Another small hack, to simplify making a thumbnail link
- UrlBasedContent<InfoT>::originalJson.insert("thumbnailMediaId",
- thumbnail.mediaId());
- }
-
- const Thumbnail* thumbnailInfo() const override { return &thumbnail; }
-
- public:
- Thumbnail thumbnail;
-
- protected:
- void fillJson(QJsonObject* json) const override
- {
- UrlBasedContent<InfoT>::fillJson(json);
- auto infoJson = json->take("info").toObject();
- thumbnail.fillInfoJson(&infoJson);
- json->insert("info", infoJson);
- }
- };
-
- /**
- * Content class for m.image
- *
- * Available fields:
- * - corresponding to the top-level JSON:
- * - url
- * - filename (extension to the spec)
- * - corresponding to the "info" subobject:
- * - payloadSize ("size" in JSON)
- * - mimeType ("mimetype" in JSON)
- * - imageSize (QSize for a combination of "h" and "w" in JSON)
- * - thumbnail.url ("thumbnail_url" in JSON)
- * - corresponding to the "info/thumbnail_info" subobject: contents of
- * thumbnail field, in the same vein as for the main image:
- * - payloadSize
- * - mimeType
- * - imageSize
- */
- using ImageContent = UrlWithThumbnailContent<ImageInfo>;
-
- /**
- * Content class for m.file
- *
- * Available fields:
- * - corresponding to the top-level JSON:
- * - url
- * - filename
- * - corresponding to the "info" subobject:
- * - payloadSize ("size" in JSON)
- * - mimeType ("mimetype" in JSON)
- * - thumbnail.url ("thumbnail_url" in JSON)
- * - corresponding to the "info/thumbnail_info" subobject:
- * - thumbnail.payloadSize
- * - thumbnail.mimeType
- * - thumbnail.imageSize (QSize for "h" and "w" in JSON)
- */
- using FileContent = UrlWithThumbnailContent<FileInfo>;
-} // namespace EventContent
-} // namespace Quotient
+ QMimeType type() const override { return InfoT::mimeType; }
+ const FileInfo* fileInfo() const override { return this; }
+ FileInfo* fileInfo() override { return this; }
+ const Thumbnail* thumbnailInfo() const override { return &thumbnail; }
+
+public:
+ Thumbnail thumbnail;
+
+protected:
+ virtual void fillInfoJson(QJsonObject& infoJson [[maybe_unused]]) const
+ {}
+
+ void fillJson(QJsonObject& json) const override
+ {
+ Quotient::fillJson(json, { "url"_ls, "file"_ls }, InfoT::source);
+ if (!InfoT::originalName.isEmpty())
+ json.insert("filename", InfoT::originalName);
+ auto infoJson = toInfoJson(*this);
+ if (thumbnail.isValid())
+ thumbnail.dumpTo(infoJson);
+ fillInfoJson(infoJson);
+ json.insert("info", infoJson);
+ }
+};
+
+//! \brief Content class for m.image
+//!
+//! Available fields:
+//! - corresponding to the top-level JSON:
+//! - source (corresponding to `url` or `file` in JSON)
+//! - filename (extension to the spec)
+//! - corresponding to the `info` subobject:
+//! - payloadSize (`size` in JSON)
+//! - mimeType (`mimetype` in JSON)
+//! - imageSize (QSize for a combination of `h` and `w` in JSON)
+//! - thumbnail.url (`thumbnail_url` in JSON)
+//! - corresponding to the `info/thumbnail_info` subobject: contents of
+//! thumbnail field, in the same vein as for the main image:
+//! - payloadSize
+//! - mimeType
+//! - imageSize
+using ImageContent = UrlBasedContent<ImageInfo>;
+
+//! \brief Content class for m.file
+//!
+//! Available fields:
+//! - corresponding to the top-level JSON:
+//! - source (corresponding to `url` or `file` in JSON)
+//! - filename
+//! - corresponding to the `info` subobject:
+//! - payloadSize (`size` in JSON)
+//! - mimeType (`mimetype` in JSON)
+//! - thumbnail.source (`thumbnail_url` or `thumbnail_file` in JSON)
+//! - corresponding to the `info/thumbnail_info` subobject:
+//! - thumbnail.payloadSize
+//! - thumbnail.mimeType
+//! - thumbnail.imageSize (QSize for `h` and `w` in JSON)
+using FileContent = UrlBasedContent<FileInfo>;
+} // namespace Quotient::EventContent
Q_DECLARE_METATYPE(const Quotient::EventContent::TypedBase*)
diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h
index fe624d70..4c639efa 100644
--- a/lib/events/eventloader.h
+++ b/lib/events/eventloader.h
@@ -19,43 +19,27 @@ inline event_ptr_tt<BaseEventT> loadEvent(const QJsonObject& fullJson)
return doLoadEvent<BaseEventT>(fullJson, fullJson[TypeKeyL].toString());
}
-/*! Create an event from a type string and content JSON
- *
- * Use this factory template to resolve the C++ type from the Matrix
- * type string in \p matrixType and create an event of that type that has
- * its content part set to \p content.
- */
-template <typename BaseEventT>
-inline event_ptr_tt<BaseEventT> loadEvent(const QString& matrixType,
- const QJsonObject& content)
+//! \brief Create an event from a type string and content JSON
+//!
+//! Use this template to resolve the C++ type from the Matrix type string in
+//! \p matrixType and create an event of that type by passing all parameters
+//! to BaseEventT::basicJson().
+template <typename BaseEventT, typename... BasicJsonParamTs>
+inline event_ptr_tt<BaseEventT> loadEvent(
+ const QString& matrixType, const BasicJsonParamTs&... basicJsonParams)
{
- return doLoadEvent<BaseEventT>(basicEventJson(matrixType, content),
- matrixType);
-}
-
-/*! Create a state event from a type string, content JSON and state key
- *
- * Use this factory to resolve the C++ type from the Matrix type string
- * in \p matrixType and create a state event of that type with content part
- * set to \p content and state key set to \p stateKey (empty by default).
- */
-inline StateEventPtr loadStateEvent(const QString& matrixType,
- const QJsonObject& content,
- const QString& stateKey = {})
-{
- return doLoadEvent<StateEventBase>(
- basicStateEventJson(matrixType, content, stateKey), matrixType);
+ return doLoadEvent<BaseEventT>(
+ BaseEventT::basicJson(matrixType, basicJsonParams...), matrixType);
}
template <typename EventT>
-struct JsonConverter<event_ptr_tt<EventT>> {
- static auto load(const QJsonValue& jv)
+struct JsonConverter<event_ptr_tt<EventT>>
+ : JsonObjectUnpacker<event_ptr_tt<EventT>> {
+ using JsonObjectUnpacker<event_ptr_tt<EventT>>::load;
+ static auto load(const QJsonObject& jo)
{
- return loadEvent<EventT>(jv.toObject());
- }
- static auto load(const QJsonDocument& jd)
- {
- return loadEvent<EventT>(jd.object());
+ return loadEvent<EventT>(jo);
}
};
+
} // namespace Quotient
diff --git a/lib/events/eventrelation.h b/lib/events/eventrelation.h
index e445ee42..2a841cf1 100644
--- a/lib/events/eventrelation.h
+++ b/lib/events/eventrelation.h
@@ -34,11 +34,11 @@ struct QUOTIENT_API EventRelation {
return { ReplacementType, std::move(eventId) };
}
- [[deprecated("Use ReplyRelation variable instead")]]
+ [[deprecated("Use ReplyType variable instead")]]
static constexpr auto Reply() { return ReplyType; }
- [[deprecated("Use AnnotationRelation variable instead")]] //
+ [[deprecated("Use AnnotationType variable instead")]] //
static constexpr auto Annotation() { return AnnotationType; }
- [[deprecated("Use ReplacementRelation variable instead")]] //
+ [[deprecated("Use ReplacementType variable instead")]] //
static constexpr auto Replacement() { return ReplacementType; }
};
diff --git a/lib/events/filesourceinfo.cpp b/lib/events/filesourceinfo.cpp
new file mode 100644
index 00000000..e8b6794b
--- /dev/null
+++ b/lib/events/filesourceinfo.cpp
@@ -0,0 +1,172 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "filesourceinfo.h"
+
+#include "logging.h"
+#include "util.h"
+
+#ifdef Quotient_E2EE_ENABLED
+# include "e2ee/qolmutils.h"
+
+# include <QtCore/QCryptographicHash>
+
+# include <openssl/evp.h>
+#endif
+
+using namespace Quotient;
+
+QByteArray Quotient::decryptFile(const QByteArray& ciphertext,
+ const EncryptedFileMetadata& metadata)
+{
+#ifdef Quotient_E2EE_ENABLED
+ if (QByteArray::fromBase64(metadata.hashes["sha256"_ls].toLatin1())
+ != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) {
+ qCWarning(E2EE) << "Hash verification failed for file";
+ return {};
+ }
+
+ auto _key = metadata.key.k;
+ const auto keyBytes = QByteArray::fromBase64(
+ _key.replace(u'_', u'/').replace(u'-', u'+').toLatin1());
+ 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<const unsigned char*>(keyBytes.data()),
+ reinterpret_cast<const unsigned char*>(
+ QByteArray::fromBase64(metadata.iv.toLatin1()).data()));
+ EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char*>(plaintext.data()),
+ &length,
+ reinterpret_cast<const unsigned char*>(ciphertext.data()),
+ ciphertext.size());
+ EVP_DecryptFinal_ex(ctx,
+ reinterpret_cast<unsigned char*>(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, QByteArray> Quotient::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<const unsigned char*>(k.data()),
+ reinterpret_cast<const unsigned char*>(iv.data()));
+ const auto blockSize = EVP_CIPHER_CTX_block_size(ctx);
+ QByteArray cipherText(plainText.size() + blockSize - 1, '\0');
+ EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char*>(cipherText.data()),
+ &length,
+ reinterpret_cast<const unsigned char*>(plainText.data()),
+ plainText.size());
+ EVP_EncryptFinal_ex(ctx,
+ reinterpret_cast<unsigned char*>(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<EncryptedFileMetadata>::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<EncryptedFileMetadata>::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<JWK>::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<JWK>::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);
+}
+
+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<QLatin1String, 2>& 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<FileSourceInfo> == 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..8f7e3cbe
--- /dev/null
+++ b/lib/events/filesourceinfo.h
@@ -0,0 +1,90 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include "converters.h"
+
+#include <array>
+
+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<QString, QString> hashes MEMBER hashes CONSTANT)
+ Q_PROPERTY(QString v MEMBER v CONSTANT)
+
+public:
+ QUrl url;
+ JWK key;
+ QString iv;
+ QHash<QString, QString> hashes;
+ QString v;
+};
+
+QUOTIENT_API std::pair<EncryptedFileMetadata, QByteArray> encryptFile(
+ const QByteArray& plainText);
+QUOTIENT_API QByteArray decryptFile(const QByteArray& ciphertext,
+ const EncryptedFileMetadata& metadata);
+
+template <>
+struct QUOTIENT_API JsonObjectConverter<EncryptedFileMetadata> {
+ static void dumpTo(QJsonObject& jo, const EncryptedFileMetadata& pod);
+ static void fillFrom(const QJsonObject& jo, EncryptedFileMetadata& pod);
+};
+
+template <>
+struct QUOTIENT_API JsonObjectConverter<JWK> {
+ static void dumpTo(QJsonObject& jo, const JWK& pod);
+ static void fillFrom(const QJsonObject& jo, JWK& pod);
+};
+
+using FileSourceInfo = std::variant<QUrl, EncryptedFileMetadata>;
+
+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<QLatin1String, 2>& jsonKeys,
+ const FileSourceInfo& fsi);
+
+} // namespace Quotient
diff --git a/lib/events/keyverificationevent.cpp b/lib/events/keyverificationevent.cpp
deleted file mode 100644
index e7f5b019..00000000
--- a/lib/events/keyverificationevent.cpp
+++ /dev/null
@@ -1,194 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
-// SPDX-License-Identifier: LGPL-2.1-or-later
-
-#include "keyverificationevent.h"
-
-using namespace Quotient;
-
-KeyVerificationRequestEvent::KeyVerificationRequestEvent(const QJsonObject &obj)
- : Event(typeId(), obj)
-{}
-
-QString KeyVerificationRequestEvent::fromDevice() const
-{
- return contentPart<QString>("from_device"_ls);
-}
-
-QString KeyVerificationRequestEvent::transactionId() const
-{
- return contentPart<QString>("transaction_id"_ls);
-}
-
-QStringList KeyVerificationRequestEvent::methods() const
-{
- return contentPart<QStringList>("methods"_ls);
-}
-
-uint64_t KeyVerificationRequestEvent::timestamp() const
-{
- return contentPart<double>("timestamp"_ls);
-}
-
-KeyVerificationStartEvent::KeyVerificationStartEvent(const QJsonObject &obj)
- : Event(typeId(), obj)
-{}
-
-QString KeyVerificationStartEvent::fromDevice() const
-{
- return contentPart<QString>("from_device"_ls);
-}
-
-QString KeyVerificationStartEvent::transactionId() const
-{
- return contentPart<QString>("transaction_id"_ls);
-}
-
-QString KeyVerificationStartEvent::method() const
-{
- return contentPart<QString>("method"_ls);
-}
-
-Omittable<QString> KeyVerificationStartEvent::nextMethod() const
-{
- return contentPart<Omittable<QString>>("method_ls");
-}
-
-QStringList KeyVerificationStartEvent::keyAgreementProtocols() const
-{
- Q_ASSERT(method() == QStringLiteral("m.sas.v1"));
- return contentPart<QStringList>("key_agreement_protocols"_ls);
-}
-
-QStringList KeyVerificationStartEvent::hashes() const
-{
- Q_ASSERT(method() == QStringLiteral("m.sas.v1"));
- return contentPart<QStringList>("hashes"_ls);
-
-}
-
-QStringList KeyVerificationStartEvent::messageAuthenticationCodes() const
-{
- Q_ASSERT(method() == QStringLiteral("m.sas.v1"));
- return contentPart<QStringList>("message_authentication_codes"_ls);
-}
-
-QString KeyVerificationStartEvent::shortAuthenticationString() const
-{
- return contentPart<QString>("short_authentification_string"_ls);
-}
-
-KeyVerificationAcceptEvent::KeyVerificationAcceptEvent(const QJsonObject &obj)
- : Event(typeId(), obj)
-{}
-
-QString KeyVerificationAcceptEvent::transactionId() const
-{
- return contentPart<QString>("transaction_id"_ls);
-}
-
-QString KeyVerificationAcceptEvent::method() const
-{
- return contentPart<QString>("method"_ls);
-}
-
-QString KeyVerificationAcceptEvent::keyAgreementProtocol() const
-{
- return contentPart<QString>("key_agreement_protocol"_ls);
-}
-
-QString KeyVerificationAcceptEvent::hashData() const
-{
- return contentPart<QString>("hash"_ls);
-}
-
-QStringList KeyVerificationAcceptEvent::shortAuthenticationString() const
-{
- return contentPart<QStringList>("short_authentification_string"_ls);
-}
-
-QString KeyVerificationAcceptEvent::commitment() const
-{
- return contentPart<QString>("commitment"_ls);
-}
-
-KeyVerificationCancelEvent::KeyVerificationCancelEvent(const QJsonObject &obj)
- : Event(typeId(), obj)
-{}
-
-QString KeyVerificationCancelEvent::transactionId() const
-{
- return contentPart<QString>("transaction_id"_ls);
-}
-
-QString KeyVerificationCancelEvent::reason() const
-{
- return contentPart<QString>("reason"_ls);
-}
-
-QString KeyVerificationCancelEvent::code() const
-{
- return contentPart<QString>("code"_ls);
-}
-
-KeyVerificationKeyEvent::KeyVerificationKeyEvent(const QJsonObject &obj)
- : Event(typeId(), obj)
-{}
-
-QString KeyVerificationKeyEvent::transactionId() const
-{
- return contentPart<QString>("transaction_id"_ls);
-}
-
-QString KeyVerificationKeyEvent::key() const
-{
- return contentPart<QString>("key"_ls);
-}
-
-KeyVerificationMacEvent::KeyVerificationMacEvent(const QJsonObject &obj)
- : Event(typeId(), obj)
-{}
-
-QString KeyVerificationMacEvent::transactionId() const
-{
- return contentPart<QString>("transaction_id"_ls);
-}
-
-QString KeyVerificationMacEvent::keys() const
-{
- return contentPart<QString>("keys"_ls);
-}
-
-QHash<QString, QString> KeyVerificationMacEvent::mac() const
-{
- return contentPart<QHash<QString, QString>>("mac"_ls);
-}
-
-KeyVerificationDoneEvent::KeyVerificationDoneEvent(const QJsonObject &obj)
- : Event(typeId(), obj)
-{
-}
-
-QString KeyVerificationDoneEvent::transactionId() const
-{
- return contentPart<QString>("transaction_id"_ls);
-}
-
-
-KeyVerificationReadyEvent::KeyVerificationReadyEvent(const QJsonObject &obj)
- : Event(typeId(), obj)
-{}
-
-QString KeyVerificationReadyEvent::fromDevice() const
-{
- return contentPart<QString>("from_device"_ls);
-}
-
-QString KeyVerificationReadyEvent::transactionId() const
-{
- return contentPart<QString>("transaction_id"_ls);
-}
-
-QStringList KeyVerificationReadyEvent::methods() const
-{
- return contentPart<QStringList>("methods"_ls);
-}
diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h
index a9f63968..cdbd5d74 100644
--- a/lib/events/keyverificationevent.h
+++ b/lib/events/keyverificationevent.h
@@ -7,29 +7,33 @@
namespace Quotient {
+static constexpr auto SasV1Method = "m.sas.v1"_ls;
+
/// Requests a key verification with another user's devices.
/// Typically sent as a to-device event.
class QUOTIENT_API KeyVerificationRequestEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.key.verification.request", KeyVerificationRequestEvent)
- explicit KeyVerificationRequestEvent(const QJsonObject& obj);
+ explicit KeyVerificationRequestEvent(const QJsonObject& obj)
+ : Event(TypeId, obj)
+ {}
/// The device ID which is initiating the request.
- QString fromDevice() const;
+ QUO_CONTENT_GETTER(QString, fromDevice)
/// An opaque identifier for the verification request. Must
/// be unique with respect to the devices involved.
- QString transactionId() const;
+ QUO_CONTENT_GETTER(QString, transactionId)
/// The verification methods supported by the sender.
- QStringList methods() const;
+ QUO_CONTENT_GETTER(QStringList, methods)
/// The POSIX timestamp in milliseconds for when the request was
/// made. If the request is in the future by more than 5 minutes or
/// more than 10 minutes in the past, the message should be ignored
/// by the receiver.
- uint64_t timestamp() const;
+ QUO_CONTENT_GETTER(QDateTime, timestamp)
};
REGISTER_EVENT_TYPE(KeyVerificationRequestEvent)
@@ -37,16 +41,18 @@ class QUOTIENT_API KeyVerificationReadyEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.key.verification.ready", KeyVerificationReadyEvent)
- explicit KeyVerificationReadyEvent(const QJsonObject& obj);
+ explicit KeyVerificationReadyEvent(const QJsonObject& obj)
+ : Event(TypeId, obj)
+ {}
/// The device ID which is accepting the request.
- QString fromDevice() const;
+ QUO_CONTENT_GETTER(QString, fromDevice)
/// The transaction id of the verification request
- QString transactionId() const;
+ QUO_CONTENT_GETTER(QString, transactionId)
/// The verification methods supported by the sender.
- QStringList methods() const;
+ QUO_CONTENT_GETTER(QStringList, methods)
};
REGISTER_EVENT_TYPE(KeyVerificationReadyEvent)
@@ -56,39 +62,57 @@ class QUOTIENT_API KeyVerificationStartEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.key.verification.start", KeyVerificationStartEvent)
- explicit KeyVerificationStartEvent(const QJsonObject &obj);
+ explicit KeyVerificationStartEvent(const QJsonObject &obj)
+ : Event(TypeId, obj)
+ {}
/// The device ID which is initiating the process.
- QString fromDevice() const;
+ QUO_CONTENT_GETTER(QString, fromDevice)
/// An opaque identifier for the verification request. Must
/// be unique with respect to the devices involved.
- QString transactionId() const;
+ QUO_CONTENT_GETTER(QString, transactionId)
/// The verification method to use.
- QString method() const;
+ QUO_CONTENT_GETTER(QString, method)
/// Optional method to use to verify the other user's key with.
- Omittable<QString> nextMethod() const;
+ QUO_CONTENT_GETTER(Omittable<QString>, nextMethod)
// SAS.V1 methods
/// The key agreement protocols the sending device understands.
/// \note Only exist if method is m.sas.v1
- QStringList keyAgreementProtocols() const;
+ QStringList keyAgreementProtocols() const
+ {
+ Q_ASSERT(method() == SasV1Method);
+ return contentPart<QStringList>("key_agreement_protocols"_ls);
+ }
/// The hash methods the sending device understands.
/// \note Only exist if method is m.sas.v1
- QStringList hashes() const;
+ QStringList hashes() const
+ {
+ Q_ASSERT(method() == SasV1Method);
+ return contentPart<QStringList>("hashes"_ls);
+ }
/// The message authentication codes that the sending device understands.
/// \note Only exist if method is m.sas.v1
- QStringList messageAuthenticationCodes() const;
+ QStringList messageAuthenticationCodes() const
+ {
+ Q_ASSERT(method() == SasV1Method);
+ return contentPart<QStringList>("message_authentication_codes"_ls);
+ }
/// The SAS methods the sending device (and the sending device's
/// user) understands.
/// \note Only exist if method is m.sas.v1
- QString shortAuthenticationString() const;
+ QString shortAuthenticationString() const
+ {
+ Q_ASSERT(method() == SasV1Method);
+ return contentPart<QString>("short_authentification_string"_ls);
+ }
};
REGISTER_EVENT_TYPE(KeyVerificationStartEvent)
@@ -98,33 +122,38 @@ class QUOTIENT_API KeyVerificationAcceptEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.key.verification.accept", KeyVerificationAcceptEvent)
- explicit KeyVerificationAcceptEvent(const QJsonObject& obj);
+ explicit KeyVerificationAcceptEvent(const QJsonObject& obj)
+ : Event(TypeId, obj)
+ {}
/// An opaque identifier for the verification process.
- QString transactionId() const;
+ QUO_CONTENT_GETTER(QString, transactionId)
/// The verification method to use. Must be 'm.sas.v1'.
- QString method() const;
+ QUO_CONTENT_GETTER(QString, method)
/// The key agreement protocol the device is choosing to use, out of
/// the options in the m.key.verification.start message.
- QString keyAgreementProtocol() const;
+ QUO_CONTENT_GETTER(QString, keyAgreementProtocol)
/// The hash method the device is choosing to use, out of the
/// options in the m.key.verification.start message.
- QString hashData() const;
+ QString hashData() const
+ {
+ return contentPart<QString>("hash"_ls);
+ }
/// The message authentication code the device is choosing to use, out
/// of the options in the m.key.verification.start message.
- QString messageAuthenticationCode() const;
+ QUO_CONTENT_GETTER(QString, messageAuthenticationCode)
/// The SAS methods both devices involved in the verification process understand.
- QStringList shortAuthenticationString() const;
+ QUO_CONTENT_GETTER(QStringList, shortAuthenticationString)
/// The hash (encoded as unpadded base64) of the concatenation of the
/// device's ephemeral public key (encoded as unpadded base64) and the
/// canonical JSON representation of the m.key.verification.start message.
- QString commitment() const;
+ QUO_CONTENT_GETTER(QString, commitment)
};
REGISTER_EVENT_TYPE(KeyVerificationAcceptEvent)
@@ -132,17 +161,19 @@ class QUOTIENT_API KeyVerificationCancelEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.key.verification.cancel", KeyVerificationCancelEvent)
- explicit KeyVerificationCancelEvent(const QJsonObject &obj);
+ explicit KeyVerificationCancelEvent(const QJsonObject &obj)
+ : Event(TypeId, obj)
+ {}
/// An opaque identifier for the verification process.
- QString transactionId() const;
+ QUO_CONTENT_GETTER(QString, transactionId)
/// A human readable description of the code. The client should only
/// rely on this string if it does not understand the code.
- QString reason() const;
+ QUO_CONTENT_GETTER(QString, reason)
/// The error code for why the process/request was cancelled by the user.
- QString code() const;
+ QUO_CONTENT_GETTER(QString, code)
};
REGISTER_EVENT_TYPE(KeyVerificationCancelEvent)
@@ -152,13 +183,15 @@ class QUOTIENT_API KeyVerificationKeyEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.key.verification.key", KeyVerificationKeyEvent)
- explicit KeyVerificationKeyEvent(const QJsonObject &obj);
+ explicit KeyVerificationKeyEvent(const QJsonObject &obj)
+ : Event(TypeId, obj)
+ {}
- /// An opaque identifier for the verification process.
- QString transactionId() const;
+ /// An opaque identifier for the verification process.
+ QUO_CONTENT_GETTER(QString, transactionId)
/// The device's ephemeral public key, encoded as unpadded base64.
- QString key() const;
+ QUO_CONTENT_GETTER(QString, key)
};
REGISTER_EVENT_TYPE(KeyVerificationKeyEvent)
@@ -167,15 +200,20 @@ class QUOTIENT_API KeyVerificationMacEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.key.verification.mac", KeyVerificationMacEvent)
- explicit KeyVerificationMacEvent(const QJsonObject &obj);
+ explicit KeyVerificationMacEvent(const QJsonObject &obj)
+ : Event(TypeId, obj)
+ {}
- /// An opaque identifier for the verification process.
- QString transactionId() const;
+ /// An opaque identifier for the verification process.
+ QUO_CONTENT_GETTER(QString, transactionId)
/// The device's ephemeral public key, encoded as unpadded base64.
- QString keys() const;
+ QUO_CONTENT_GETTER(QString, keys)
- QHash<QString, QString> mac() const;
+ QHash<QString, QString> mac() const
+ {
+ return contentPart<QHash<QString, QString>>("mac"_ls);
+ }
};
REGISTER_EVENT_TYPE(KeyVerificationMacEvent)
@@ -183,10 +221,12 @@ class QUOTIENT_API KeyVerificationDoneEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.key.verification.done", KeyVerificationRequestEvent)
- explicit KeyVerificationDoneEvent(const QJsonObject& obj);
+ explicit KeyVerificationDoneEvent(const QJsonObject& obj)
+ : Event(TypeId, obj)
+ {}
/// The same transactionId as before
- QString transactionId() const;
+ QUO_CONTENT_GETTER(QString, transactionId)
};
REGISTER_EVENT_TYPE(KeyVerificationDoneEvent)
diff --git a/lib/events/redactionevent.h b/lib/events/redactionevent.h
index be20bf52..63617e54 100644
--- a/lib/events/redactionevent.h
+++ b/lib/events/redactionevent.h
@@ -6,7 +6,7 @@
#include "roomevent.h"
namespace Quotient {
-class RedactionEvent : public RoomEvent {
+class QUOTIENT_API RedactionEvent : public RoomEvent {
public:
DEFINE_EVENT_TYPEID("m.room.redaction", RedactionEvent)
@@ -17,7 +17,7 @@ public:
{
return fullJson()["redacts"_ls].toString();
}
- QString reason() const { return contentPart<QString>("reason"_ls); }
+ QUO_CONTENT_GETTER(QString, reason)
};
REGISTER_EVENT_TYPE(RedactionEvent)
} // 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/roomcanonicalaliasevent.h b/lib/events/roomcanonicalaliasevent.h
index bb8654e5..60ca68ac 100644
--- a/lib/events/roomcanonicalaliasevent.h
+++ b/lib/events/roomcanonicalaliasevent.h
@@ -7,36 +7,31 @@
#include "stateevent.h"
namespace Quotient {
-namespace EventContent{
- class AliasesEventContent {
-
- public:
-
- template<typename T1, typename T2>
- AliasesEventContent(T1&& canonicalAlias, T2&& altAliases)
- : canonicalAlias(std::forward<T1>(canonicalAlias))
- , altAliases(std::forward<T2>(altAliases))
- { }
-
- AliasesEventContent(const QJsonObject& json)
- : canonicalAlias(fromJson<QString>(json["alias"]))
- , altAliases(fromJson<QStringList>(json["alt_aliases"]))
- { }
-
- auto toJson() const
- {
- QJsonObject jo;
- addParam<IfNotEmpty>(jo, QStringLiteral("alias"), canonicalAlias);
- addParam<IfNotEmpty>(jo, QStringLiteral("alt_aliases"), altAliases);
- return jo;
- }
-
+namespace EventContent {
+ struct AliasesEventContent {
QString canonicalAlias;
QStringList altAliases;
};
} // namespace EventContent
-class RoomCanonicalAliasEvent
+template<>
+inline EventContent::AliasesEventContent fromJson(const QJsonObject& jo)
+{
+ return EventContent::AliasesEventContent {
+ fromJson<QString>(jo["alias"_ls]),
+ fromJson<QStringList>(jo["alt_aliases"_ls])
+ };
+}
+template<>
+inline auto toJson(const EventContent::AliasesEventContent& c)
+{
+ QJsonObject jo;
+ addParam<IfNotEmpty>(jo, QStringLiteral("alias"), c.canonicalAlias);
+ addParam<IfNotEmpty>(jo, QStringLiteral("alt_aliases"), c.altAliases);
+ return jo;
+}
+
+class QUOTIENT_API RoomCanonicalAliasEvent
: public StateEvent<EventContent::AliasesEventContent> {
public:
DEFINE_EVENT_TYPEID("m.room.canonical_alias", RoomCanonicalAliasEvent)
diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp
index bb6de648..3b5024d5 100644
--- a/lib/events/roomcreateevent.cpp
+++ b/lib/events/roomcreateevent.cpp
@@ -6,20 +6,11 @@
using namespace Quotient;
template <>
-struct Quotient::JsonConverter<RoomType> {
- static RoomType load(const QJsonValue& jv)
- {
- const auto& roomTypeString = jv.toString();
- for (auto it = RoomTypeStrings.begin(); it != RoomTypeStrings.end();
- ++it)
- if (roomTypeString == *it)
- return RoomType(it - RoomTypeStrings.begin());
-
- if (!roomTypeString.isEmpty())
- qCWarning(EVENTS) << "Unknown Room Type: " << roomTypeString;
- return RoomType::Undefined;
- }
-};
+RoomType Quotient::fromJson(const QJsonValue& jv)
+{
+ return enumFromJsonString(jv.toString(), RoomTypeStrings,
+ RoomType::Undefined);
+}
bool RoomCreateEvent::isFederated() const
{
diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp
index 2f482871..e695e0ec 100644
--- a/lib/events/roomevent.cpp
+++ b/lib/events/roomevent.cpp
@@ -15,9 +15,9 @@ RoomEvent::RoomEvent(Type type, event_mtype_t matrixType,
RoomEvent::RoomEvent(Type type, const QJsonObject& json) : Event(type, json)
{
- if (const auto redaction = unsignedPart(RedactedCauseKeyL);
- redaction.isObject())
- _redactedBecause = makeEvent<RedactionEvent>(redaction.toObject());
+ if (const auto redaction = unsignedPart<QJsonObject>(RedactedCauseKeyL);
+ !redaction.isEmpty())
+ _redactedBecause = makeEvent<RedactionEvent>(redaction);
}
RoomEvent::~RoomEvent() = default; // Let the smart pointer do its job
@@ -101,22 +101,22 @@ void RoomEvent::dumpTo(QDebug dbg) const
dbg << " (made at " << originTimestamp().toString(Qt::ISODate) << ')';
}
-QJsonObject makeCallContentJson(const QString& callId, int version,
- QJsonObject content)
+QJsonObject CallEventBase::basicJson(const QString& matrixType,
+ const QString& callId, int version,
+ QJsonObject contentJson)
{
- content.insert(QStringLiteral("call_id"), callId);
- content.insert(QStringLiteral("version"), version);
- return content;
+ contentJson.insert(QStringLiteral("call_id"), callId);
+ contentJson.insert(QStringLiteral("version"), version);
+ return RoomEvent::basicJson(matrixType, contentJson);
}
CallEventBase::CallEventBase(Type type, event_mtype_t matrixType,
const QString& callId, int version,
const QJsonObject& contentJson)
- : RoomEvent(type, matrixType,
- makeCallContentJson(callId, version, contentJson))
+ : RoomEvent(type, basicJson(matrixType, callId, version, contentJson))
{}
-CallEventBase::CallEventBase(Event::Type type, const QJsonObject& json)
+CallEventBase::CallEventBase(Type type, const QJsonObject& json)
: RoomEvent(type, json)
{
if (callId().isEmpty())
diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h
index 5670f55f..9461340b 100644
--- a/lib/events/roomevent.h
+++ b/lib/events/roomevent.h
@@ -96,8 +96,13 @@ public:
~CallEventBase() override = default;
bool isCallEvent() const override { return true; }
- QString callId() const { return contentPart<QString>("call_id"_ls); }
- int version() const { return contentPart<int>("version"_ls); }
+ QUO_CONTENT_GETTER(QString, callId)
+ QUO_CONTENT_GETTER(int, version)
+
+protected:
+ static QJsonObject basicJson(const QString& matrixType,
+ const QString& callId, int version,
+ QJsonObject contentJson = {});
};
} // namespace Quotient
Q_DECLARE_METATYPE(Quotient::RoomEvent*)
diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h
index cb3fe7e7..9eb2854b 100644
--- a/lib/events/roomkeyevent.h
+++ b/lib/events/roomkeyevent.h
@@ -12,12 +12,17 @@ public:
DEFINE_EVENT_TYPEID("m.room_key", RoomKeyEvent)
explicit RoomKeyEvent(const QJsonObject& obj);
- explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, const QString &sessionId, const QString& sessionKey, const QString& senderId);
+ explicit RoomKeyEvent(const QString& algorithm, const QString& roomId,
+ const QString& sessionId, const QString& sessionKey,
+ const QString& senderId);
- QString algorithm() const { return contentPart<QString>("algorithm"_ls); }
- QString roomId() const { return contentPart<QString>(RoomIdKeyL); }
- QString sessionId() const { return contentPart<QString>("session_id"_ls); }
- QString sessionKey() const { return contentPart<QString>("session_key"_ls); }
+ QUO_CONTENT_GETTER(QString, algorithm)
+ QUO_CONTENT_GETTER(QString, roomId)
+ QUO_CONTENT_GETTER(QString, sessionId)
+ QByteArray sessionKey() const
+ {
+ return contentPart<QString>("session_key"_ls).toLatin1();
+ }
};
REGISTER_EVENT_TYPE(RoomKeyEvent)
} // namespace Quotient
diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp
index b4770224..953ff8ae 100644
--- a/lib/events/roommemberevent.cpp
+++ b/lib/events/roommemberevent.cpp
@@ -4,8 +4,6 @@
#include "roommemberevent.h"
-#include "logging.h"
-
#include <QtCore/QtAlgorithms>
namespace Quotient {
@@ -13,18 +11,10 @@ template <>
struct JsonConverter<Membership> {
static Membership load(const QJsonValue& jv)
{
- const auto& ms = jv.toString();
- if (ms.isEmpty())
- {
- qCWarning(EVENTS) << "Empty membership state";
- return Membership::Invalid;
- }
- const auto it =
- std::find(MembershipStrings.begin(), MembershipStrings.end(), ms);
- if (it != MembershipStrings.end())
- return Membership(1U << (it - MembershipStrings.begin()));
-
- qCWarning(EVENTS) << "Unknown Membership value: " << ms;
+ if (const auto& ms = jv.toString(); !ms.isEmpty())
+ return flagFromJsonString<Membership>(ms, MembershipStrings);
+
+ qCWarning(EVENTS) << "Empty membership state";
return Membership::Invalid;
}
};
@@ -43,19 +33,19 @@ MemberEventContent::MemberEventContent(const QJsonObject& json)
displayName = sanitized(*displayName);
}
-void MemberEventContent::fillJson(QJsonObject* o) const
+QJsonObject MemberEventContent::toJson() const
{
- Q_ASSERT(o);
+ QJsonObject o;
if (membership != Membership::Invalid)
- o->insert(QStringLiteral("membership"),
- MembershipStrings[qCountTrailingZeroBits(
- std::underlying_type_t<Membership>(membership))]);
+ o.insert(QStringLiteral("membership"),
+ flagToJsonString(membership, MembershipStrings));
if (displayName)
- o->insert(QStringLiteral("displayname"), *displayName);
+ o.insert(QStringLiteral("displayname"), *displayName);
if (avatarUrl && avatarUrl->isValid())
- o->insert(QStringLiteral("avatar_url"), avatarUrl->toString());
+ o.insert(QStringLiteral("avatar_url"), avatarUrl->toString());
if (!reason.isEmpty())
- o->insert(QStringLiteral("reason"), reason);
+ o.insert(QStringLiteral("reason"), reason);
+ return o;
}
bool RoomMemberEvent::changesMembership() const
diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h
index ceb7826b..dd33ea6b 100644
--- a/lib/events/roommemberevent.h
+++ b/lib/events/roommemberevent.h
@@ -5,18 +5,18 @@
#pragma once
-#include "eventcontent.h"
#include "stateevent.h"
#include "quotient_common.h"
namespace Quotient {
-class QUOTIENT_API MemberEventContent : public EventContent::Base {
+class QUOTIENT_API MemberEventContent {
public:
using MembershipType
[[deprecated("Use Quotient::Membership instead")]] = Membership;
QUO_IMPLICIT MemberEventContent(Membership ms) : membership(ms) {}
explicit MemberEventContent(const QJsonObject& json);
+ QJsonObject toJson() const;
Membership membership;
/// (Only for invites) Whether the invite is to a direct chat
@@ -24,9 +24,6 @@ public:
Omittable<QString> displayName;
Omittable<QUrl> avatarUrl;
QString reason;
-
-protected:
- void fillJson(QJsonObject* o) const override;
};
using MembershipType [[deprecated("Use Membership instead")]] = Membership;
diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp
index d63352cb..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,
@@ -302,17 +302,16 @@ TextContent::TextContent(const QJsonObject& json)
}
}
-void TextContent::fillJson(QJsonObject* json) const
+void TextContent::fillJson(QJsonObject &json) const
{
static const auto FormatKey = QStringLiteral("format");
- Q_ASSERT(json);
if (mimeType.inherits("text/html")) {
- json->insert(FormatKey, HtmlContentTypeId);
- json->insert(FormattedBodyKey, body);
+ json.insert(FormatKey, HtmlContentTypeId);
+ json.insert(FormattedBodyKey, body);
}
if (relatesTo) {
- json->insert(
+ json.insert(
QStringLiteral("m.relates_to"),
relatesTo->type == EventRelation::ReplyType
? QJsonObject { { relatesTo->type,
@@ -326,7 +325,7 @@ void TextContent::fillJson(QJsonObject* json) const
newContentJson.insert(FormatKey, HtmlContentTypeId);
newContentJson.insert(FormattedBodyKey, body);
}
- json->insert(QStringLiteral("m.new_content"), newContentJson);
+ json.insert(QStringLiteral("m.new_content"), newContentJson);
}
}
}
@@ -347,9 +346,8 @@ QMimeType LocationContent::type() const
return QMimeDatabase().mimeTypeForData(geoUri.toLatin1());
}
-void LocationContent::fillJson(QJsonObject* o) const
+void LocationContent::fillJson(QJsonObject& o) const
{
- Q_ASSERT(o);
- o->insert(QStringLiteral("geo_uri"), geoUri);
- o->insert(QStringLiteral("info"), toInfoJson(thumbnail));
+ o.insert(QStringLiteral("geo_uri"), geoUri);
+ o.insert(QStringLiteral("info"), toInfoJson(thumbnail));
}
diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h
index 03a51328..6968ad70 100644
--- a/lib/events/roommessageevent.h
+++ b/lib/events/roommessageevent.h
@@ -136,7 +136,7 @@ namespace EventContent {
Omittable<EventRelation> relatesTo;
protected:
- void fillJson(QJsonObject* json) const override;
+ void fillJson(QJsonObject& json) const override;
};
/**
@@ -164,28 +164,25 @@ namespace EventContent {
Thumbnail thumbnail;
protected:
- void fillJson(QJsonObject* o) const override;
+ void fillJson(QJsonObject& o) const override;
};
/**
* A base class for info types that include duration: audio and video
*/
- template <typename ContentT>
- class QUOTIENT_API PlayableContent : public ContentT {
+ template <typename InfoT>
+ class PlayableContent : public UrlBasedContent<InfoT> {
public:
- using ContentT::ContentT;
+ using UrlBasedContent<InfoT>::UrlBasedContent;
PlayableContent(const QJsonObject& json)
- : ContentT(json)
- , duration(ContentT::originalInfoJson["duration"_ls].toInt())
+ : UrlBasedContent<InfoT>(json)
+ , duration(FileInfo::originalInfoJson["duration"_ls].toInt())
{}
protected:
- void fillJson(QJsonObject* json) const override
+ void fillInfoJson(QJsonObject& infoJson) const override
{
- ContentT::fillJson(json);
- auto infoJson = json->take("info"_ls).toObject();
infoJson.insert(QStringLiteral("duration"), duration);
- json->insert(QStringLiteral("info"), infoJson);
}
public:
@@ -211,7 +208,7 @@ namespace EventContent {
* - mimeType
* - imageSize
*/
- using VideoContent = PlayableContent<UrlWithThumbnailContent<ImageInfo>>;
+ using VideoContent = PlayableContent<ImageInfo>;
/**
* Content class for m.audio
@@ -224,7 +221,13 @@ namespace EventContent {
* - payloadSize ("size" in JSON)
* - mimeType ("mimetype" in JSON)
* - duration
+ * - thumbnail.url ("thumbnail_url" in JSON - extension to the spec)
+ * - corresponding to the "info/thumbnail_info" subobject: contents of
+ * thumbnail field (extension to the spec):
+ * - payloadSize
+ * - mimeType
+ * - imageSize
*/
- using AudioContent = PlayableContent<UrlBasedContent<FileInfo>>;
+ using AudioContent = PlayableContent<FileInfo>;
} // namespace EventContent
} // namespace Quotient
diff --git a/lib/events/roompowerlevelsevent.cpp b/lib/events/roompowerlevelsevent.cpp
index 8d262ddf..d9bd010b 100644
--- a/lib/events/roompowerlevelsevent.cpp
+++ b/lib/events/roompowerlevelsevent.cpp
@@ -3,10 +3,10 @@
#include "roompowerlevelsevent.h"
-#include <QJsonDocument>
-
using namespace Quotient;
+// The default values used below are defined in
+// https://spec.matrix.org/v1.3/client-server-api/#mroompower_levels
PowerLevelsEventContent::PowerLevelsEventContent(const QJsonObject& json) :
invite(json["invite"_ls].toInt(50)),
kick(json["kick"_ls].toInt(50)),
@@ -18,48 +18,36 @@ PowerLevelsEventContent::PowerLevelsEventContent(const QJsonObject& json) :
users(fromJson<QHash<QString, int>>(json["users"_ls])),
usersDefault(json["users_default"_ls].toInt(0)),
notifications(Notifications{json["notifications"_ls].toObject()["room"_ls].toInt(50)})
-{
-}
+{}
-void PowerLevelsEventContent::fillJson(QJsonObject* o) const {
- o->insert(QStringLiteral("invite"), invite);
- o->insert(QStringLiteral("kick"), kick);
- o->insert(QStringLiteral("ban"), ban);
- o->insert(QStringLiteral("redact"), redact);
- o->insert(QStringLiteral("events"), Quotient::toJson(events));
- o->insert(QStringLiteral("events_default"), eventsDefault);
- o->insert(QStringLiteral("state_default"), stateDefault);
- o->insert(QStringLiteral("users"), Quotient::toJson(users));
- o->insert(QStringLiteral("users_default"), usersDefault);
- o->insert(QStringLiteral("notifications"), QJsonObject{{"room", notifications.room}});
+QJsonObject PowerLevelsEventContent::toJson() const
+{
+ QJsonObject o;
+ o.insert(QStringLiteral("invite"), invite);
+ o.insert(QStringLiteral("kick"), kick);
+ o.insert(QStringLiteral("ban"), ban);
+ o.insert(QStringLiteral("redact"), redact);
+ o.insert(QStringLiteral("events"), Quotient::toJson(events));
+ o.insert(QStringLiteral("events_default"), eventsDefault);
+ o.insert(QStringLiteral("state_default"), stateDefault);
+ o.insert(QStringLiteral("users"), Quotient::toJson(users));
+ o.insert(QStringLiteral("users_default"), usersDefault);
+ o.insert(QStringLiteral("notifications"),
+ QJsonObject { { "room", notifications.room } });
+ return o;
}
-int RoomPowerLevelsEvent::powerLevelForEvent(const QString &eventId) const {
- auto e = events();
-
- if (e.contains(eventId)) {
- return e[eventId];
- }
-
- return eventsDefault();
+int RoomPowerLevelsEvent::powerLevelForEvent(const QString& eventId) const
+{
+ return events().value(eventId, eventsDefault());
}
-int RoomPowerLevelsEvent::powerLevelForState(const QString &eventId) const {
- auto e = events();
-
- if (e.contains(eventId)) {
- return e[eventId];
- }
-
- return stateDefault();
+int RoomPowerLevelsEvent::powerLevelForState(const QString& eventId) const
+{
+ return events().value(eventId, stateDefault());
}
-int RoomPowerLevelsEvent::powerLevelForUser(const QString &userId) const {
- auto u = users();
-
- if (u.contains(userId)) {
- return u[userId];
- }
-
- return usersDefault();
+int RoomPowerLevelsEvent::powerLevelForUser(const QString& userId) const
+{
+ return users().value(userId, usersDefault());
}
diff --git a/lib/events/roompowerlevelsevent.h b/lib/events/roompowerlevelsevent.h
index 415cc814..a1638a27 100644
--- a/lib/events/roompowerlevelsevent.h
+++ b/lib/events/roompowerlevelsevent.h
@@ -3,17 +3,16 @@
#pragma once
-#include "eventcontent.h"
#include "stateevent.h"
namespace Quotient {
-class QUOTIENT_API PowerLevelsEventContent : public EventContent::Base {
-public:
+struct QUOTIENT_API PowerLevelsEventContent {
struct Notifications {
int room;
};
explicit PowerLevelsEventContent(const QJsonObject& json);
+ QJsonObject toJson() const;
int invite;
int kick;
@@ -29,9 +28,6 @@ public:
int usersDefault;
Notifications notifications;
-
-protected:
- void fillJson(QJsonObject* o) const override;
};
class QUOTIENT_API RoomPowerLevelsEvent
diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h
index 9610574b..a8eaab56 100644
--- a/lib/events/simplestateevents.h
+++ b/lib/events/simplestateevents.h
@@ -4,65 +4,51 @@
#pragma once
#include "stateevent.h"
+#include "single_key_value.h"
namespace Quotient {
-namespace EventContent {
- template <typename T>
- struct SimpleContent {
- using value_type = T;
-
- // The constructor is templated to enable perfect forwarding
- template <typename TT>
- SimpleContent(QString keyName, TT&& value)
- : value(std::forward<TT>(value)), key(std::move(keyName))
- {}
- SimpleContent(const QJsonObject& json, QString keyName)
- : value(fromJson<T>(json[keyName])), key(std::move(keyName))
- {}
- QJsonObject toJson() const
- {
- return { { key, Quotient::toJson(value) } };
- }
-
- T value;
- const QString key;
- };
-} // namespace EventContent
-
-#define DEFINE_SIMPLE_STATE_EVENT(_Name, _TypeId, _ValueType, _ContentKey) \
- class QUOTIENT_API _Name \
- : public StateEvent<EventContent::SimpleContent<_ValueType>> { \
- public: \
- using value_type = content_type::value_type; \
- DEFINE_EVENT_TYPEID(_TypeId, _Name) \
- template <typename T> \
- explicit _Name(T&& value) \
- : StateEvent(typeId(), matrixTypeId(), QString(), \
- QStringLiteral(#_ContentKey), std::forward<T>(value)) \
- {} \
- explicit _Name(QJsonObject obj) \
- : StateEvent(typeId(), std::move(obj), \
- QStringLiteral(#_ContentKey)) \
- {} \
- auto _ContentKey() const { return content().value; } \
- }; \
- REGISTER_EVENT_TYPE(_Name) \
+#define DEFINE_SIMPLE_STATE_EVENT(_Name, _TypeId, _ValueType, _ContentKey) \
+ constexpr auto _Name##Key = #_ContentKey##_ls; \
+ class QUOTIENT_API _Name \
+ : public StateEvent< \
+ EventContent::SingleKeyValue<_ValueType, &_Name##Key>> { \
+ public: \
+ using value_type = _ValueType; \
+ DEFINE_EVENT_TYPEID(_TypeId, _Name) \
+ template <typename T> \
+ explicit _Name(T&& value) \
+ : StateEvent(TypeId, matrixTypeId(), QString(), \
+ std::forward<T>(value)) \
+ {} \
+ explicit _Name(QJsonObject obj) \
+ : StateEvent(TypeId, std::move(obj)) \
+ {} \
+ auto _ContentKey() const { return content().value; } \
+ }; \
+ REGISTER_EVENT_TYPE(_Name) \
// End of macro
DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name)
DEFINE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic", QString, topic)
-DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEvent, "m.room.pinned_messages", QStringList, pinnedEvents)
+DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEvent, "m.room.pinned_messages",
+ QStringList, pinnedEvents)
-class [[deprecated(
- "m.room.aliases events are deprecated by the Matrix spec; use"
- " RoomCanonicalAliasEvent::altAliases() to get non-authoritative aliases")]] //
-RoomAliasesEvent : public StateEvent<EventContent::SimpleContent<QStringList>> {
+constexpr auto RoomAliasesEventKey = "aliases"_ls;
+class QUOTIENT_API RoomAliasesEvent
+ : public StateEvent<
+ EventContent::SingleKeyValue<QStringList, &RoomAliasesEventKey>> {
public:
DEFINE_EVENT_TYPEID("m.room.aliases", RoomAliasesEvent)
explicit RoomAliasesEvent(const QJsonObject& obj)
- : StateEvent(typeId(), obj, QStringLiteral("aliases"))
+ : StateEvent(typeId(), obj)
{}
+ Q_DECL_DEPRECATED_X(
+ "m.room.aliases events are deprecated by the Matrix spec; use"
+ " RoomCanonicalAliasEvent::altAliases() to get non-authoritative aliases")
QString server() const { return stateKey(); }
+ Q_DECL_DEPRECATED_X(
+ "m.room.aliases events are deprecated by the Matrix spec; use"
+ " RoomCanonicalAliasEvent::altAliases() to get non-authoritative aliases")
QStringList aliases() const { return content().value; }
};
} // namespace Quotient
diff --git a/lib/events/single_key_value.h b/lib/events/single_key_value.h
new file mode 100644
index 00000000..75ca8cd2
--- /dev/null
+++ b/lib/events/single_key_value.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "converters.h"
+
+namespace Quotient {
+
+namespace EventContent {
+ template <typename T, const QLatin1String* KeyStr>
+ struct SingleKeyValue {
+ T value;
+ };
+} // namespace EventContent
+
+template <typename ValueT, const QLatin1String* KeyStr>
+struct JsonConverter<EventContent::SingleKeyValue<ValueT, KeyStr>> {
+ using content_type = EventContent::SingleKeyValue<ValueT, KeyStr>;
+ static content_type load(const QJsonValue& jv)
+ {
+ return { fromJson<ValueT>(jv.toObject().value(JsonKey)) };
+ }
+ static QJsonObject dump(const content_type& c)
+ {
+ return { { JsonKey, toJson(c.value) } };
+ }
+ static inline const auto JsonKey = toSnakeCase(*KeyStr);
+};
+} // namespace Quotient
diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp
index e53d47d4..1df24df0 100644
--- a/lib/events/stateevent.cpp
+++ b/lib/events/stateevent.cpp
@@ -6,9 +6,9 @@
using namespace Quotient;
StateEventBase::StateEventBase(Type type, const QJsonObject& json)
- : RoomEvent(json.contains(StateKeyKeyL) ? type : unknownEventTypeId(), json)
+ : RoomEvent(json.contains(StateKeyKeyL) ? type : UnknownEventTypeId, json)
{
- if (Event::type() == unknownEventTypeId() && !json.contains(StateKeyKeyL))
+ if (Event::type() == UnknownEventTypeId && !json.contains(StateKeyKeyL))
qWarning(EVENTS) << "Attempt to create a state event with no stateKey -"
"forcing the event type to unknown to avoid damage";
}
@@ -16,12 +16,12 @@ StateEventBase::StateEventBase(Type type, const QJsonObject& json)
StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType,
const QString& stateKey,
const QJsonObject& contentJson)
- : RoomEvent(type, basicStateEventJson(matrixType, contentJson, stateKey))
+ : RoomEvent(type, basicJson(type, stateKey, contentJson))
{}
bool StateEventBase::repeatsState() const
{
- const auto prevContentJson = unsignedPart(PrevContentKeyL);
+ const auto prevContentJson = unsignedPart<QJsonObject>(PrevContentKeyL);
return fullJson().value(ContentKeyL) == prevContentJson;
}
diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h
index 88da68f8..9f1d7118 100644
--- a/lib/events/stateevent.h
+++ b/lib/events/stateevent.h
@@ -7,16 +7,6 @@
namespace Quotient {
-/// Make a minimal correct Matrix state event JSON
-inline QJsonObject basicStateEventJson(const QString& matrixTypeId,
- const QJsonObject& content,
- const QString& stateKey = {})
-{
- return { { TypeKey, matrixTypeId },
- { StateKeyKey, stateKey },
- { ContentKey, content } };
-}
-
class QUOTIENT_API StateEventBase : public RoomEvent {
public:
static inline EventFactory<StateEventBase> factory { "StateEvent" };
@@ -27,6 +17,16 @@ public:
const QJsonObject& contentJson = {});
~StateEventBase() override = default;
+ //! Make a minimal correct Matrix state event JSON
+ static QJsonObject basicJson(const QString& matrixTypeId,
+ const QString& stateKey = {},
+ const QJsonObject& contentJson = {})
+ {
+ return { { TypeKey, matrixTypeId },
+ { StateKeyKey, stateKey },
+ { ContentKey, contentJson } };
+ }
+
bool isStateEvent() const override { return true; }
QString replacedState() const;
void dumpTo(QDebug dbg) const override;
@@ -36,6 +36,14 @@ public:
using StateEventPtr = event_ptr_tt<StateEventBase>;
using StateEvents = EventsArray<StateEventBase>;
+[[deprecated("Use StateEventBase::basicJson() instead")]]
+inline QJsonObject basicStateEventJson(const QString& matrixTypeId,
+ const QJsonObject& content,
+ const QString& stateKey = {})
+{
+ return StateEventBase::basicJson(matrixTypeId, stateKey, content);
+}
+
//! \brief Override RoomEvent factory with that from StateEventBase if JSON has
//! stateKey
//!
@@ -64,7 +72,7 @@ inline bool is<StateEventBase>(const Event& e)
* \sa
* https://matrix.org/docs/spec/client_server/unstable.html#types-of-room-events
*/
-using StateEventKey = QPair<QString, QString>;
+using StateEventKey = std::pair<QString, QString>;
template <typename ContentT>
struct Prev {
@@ -72,7 +80,7 @@ struct Prev {
explicit Prev(const QJsonObject& unsignedJson,
ContentParamTs&&... contentParams)
: senderId(unsignedJson.value("prev_sender"_ls).toString())
- , content(unsignedJson.value(PrevContentKeyL).toObject(),
+ , content(fromJson<ContentT>(unsignedJson.value(PrevContentKeyL)),
std::forward<ContentParamTs>(contentParams)...)
{}
@@ -89,7 +97,8 @@ public:
explicit StateEvent(Type type, const QJsonObject& fullJson,
ContentParamTs&&... contentParams)
: StateEventBase(type, fullJson)
- , _content(contentJson(), std::forward<ContentParamTs>(contentParams)...)
+ , _content(fromJson<ContentT>(contentJson()),
+ std::forward<ContentParamTs>(contentParams)...)
{
const auto& unsignedData = unsignedJson();
if (unsignedData.contains(PrevContentKeyL))
@@ -101,9 +110,9 @@ public:
const QString& stateKey,
ContentParamTs&&... contentParams)
: StateEventBase(type, matrixType, stateKey)
- , _content(std::forward<ContentParamTs>(contentParams)...)
+ , _content{std::forward<ContentParamTs>(contentParams)...}
{
- editJson().insert(ContentKey, _content.toJson());
+ editJson().insert(ContentKey, toJson(_content));
}
const ContentT& content() const { return _content; }
@@ -111,7 +120,7 @@ public:
void editContent(VisitorT&& visitor)
{
visitor(_content);
- editJson()[ContentKeyL] = _content.toJson();
+ editJson()[ContentKeyL] = toJson(_content);
}
const ContentT* prevContent() const
{
diff --git a/lib/events/stickerevent.cpp b/lib/events/stickerevent.cpp
deleted file mode 100644
index 628fd154..00000000
--- a/lib/events/stickerevent.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-// SDPX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
-// SPDX-License-Identifier: LGPL-2.1-or-later
-
-#include "stickerevent.h"
-
-using namespace Quotient;
-
-StickerEvent::StickerEvent(const QJsonObject &obj)
- : RoomEvent(typeId(), obj)
- , m_imageContent(EventContent::ImageContent(obj["content"_ls].toObject()))
-{}
-
-QString StickerEvent::body() const
-{
- return contentPart<QString>("body"_ls);
-}
-
-const EventContent::ImageContent &StickerEvent::image() const
-{
- return m_imageContent;
-}
-
-QUrl StickerEvent::url() const
-{
- return m_imageContent.url;
-}
diff --git a/lib/events/stickerevent.h b/lib/events/stickerevent.h
index 0957dca3..e378422d 100644
--- a/lib/events/stickerevent.h
+++ b/lib/events/stickerevent.h
@@ -16,21 +16,32 @@ class QUOTIENT_API StickerEvent : public RoomEvent
public:
DEFINE_EVENT_TYPEID("m.sticker", StickerEvent)
- explicit StickerEvent(const QJsonObject &obj);
+ explicit StickerEvent(const QJsonObject& obj)
+ : RoomEvent(TypeId, obj)
+ , m_imageContent(
+ EventContent::ImageContent(obj["content"_ls].toObject()))
+ {}
/// \brief A textual representation or associated description of the
/// sticker image.
///
/// This could be the alt text of the original image, or a message to
/// accompany and further describe the sticker.
- QString body() const;
+ QUO_CONTENT_GETTER(QString, body)
/// \brief Metadata about the image referred to in url including a
/// thumbnail representation.
- const EventContent::ImageContent &image() const;
+ const EventContent::ImageContent& image() const
+ {
+ return m_imageContent;
+ }
/// \brief The URL to the sticker image. This must be a valid mxc:// URI.
- QUrl url() const;
+ QUrl url() const
+ {
+ return m_imageContent.url();
+ }
+
private:
EventContent::ImageContent m_imageContent;
};