diff options
author | n-peugnet <n.peugnet@free.fr> | 2022-10-06 19:27:24 +0200 |
---|---|---|
committer | n-peugnet <n.peugnet@free.fr> | 2022-10-06 19:27:24 +0200 |
commit | 08632625e1a04257b5c7d4a9db2246ac07436748 (patch) | |
tree | 9ddadf219a7e352ddd3549ad1683282c944adfb6 /lib/events | |
parent | e9c2e2a26d3711e755aa5eb8a8478917c71d612b (diff) | |
parent | d911b207f49e936b3e992200796110f0749ed150 (diff) | |
download | libquotient-08632625e1a04257b5c7d4a9db2246ac07436748.tar.gz libquotient-08632625e1a04257b5c7d4a9db2246ac07436748.zip |
Update upstream source from tag 'upstream/0.7.0'
Update to upstream version '0.7.0'
with Debian dir 30dcb77a77433e4a54eab77c0b82ae925dead2d8
Diffstat (limited to 'lib/events')
56 files changed, 2541 insertions, 2451 deletions
diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index a55016d9..324ce449 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -1,47 +1,26 @@ -/****************************************************************************** - * Copyright (C) 2018 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once -#include "converters.h" #include "event.h" -#include "eventcontent.h" namespace Quotient { -constexpr const char* FavouriteTag = "m.favourite"; -constexpr const char* LowPriorityTag = "m.lowpriority"; -constexpr const char* ServerNoticeTag = "m.server_notice"; +constexpr auto FavouriteTag [[maybe_unused]] = "m.favourite"_ls; +constexpr auto LowPriorityTag [[maybe_unused]] = "m.lowpriority"_ls; +constexpr auto ServerNoticeTag [[maybe_unused]] = "m.server_notice"_ls; struct TagRecord { - using order_type = Omittable<float>; - - order_type order; - - TagRecord(order_type order = none) : order(std::move(order)) {} - - bool operator<(const TagRecord& other) const - { - // Per The Spec, rooms with no order should be after those with order, - // against optional<>::operator<() convention. - return order && (!other.order || *order < *other.order); - } + Omittable<float> order = none; }; +inline bool operator<(TagRecord lhs, TagRecord rhs) +{ + // Per The Spec, rooms with no order should be after those with order, + // against std::optional<>::operator<() convention. + return lhs.order && (!rhs.order || *lhs.order < *rhs.order); +} + template <> struct JsonObjectConverter<TagRecord> { static void fillFrom(const QJsonObject& jo, TagRecord& rec) @@ -52,13 +31,13 @@ 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; } } - static void dumpTo(QJsonObject& jo, const TagRecord& rec) + static void dumpTo(QJsonObject& jo, TagRecord rec) { addParam<IfNotEmpty>(jo, QStringLiteral("order"), rec.order); } @@ -66,27 +45,21 @@ struct JsonObjectConverter<TagRecord> { using TagsMap = QHash<QString, TagRecord>; -#define DEFINE_SIMPLE_EVENT(_Name, _TypeId, _ContentType, _ContentKey) \ - class _Name : public Event { \ - public: \ - using content_type = _ContentType; \ - DEFINE_EVENT_TYPEID(_TypeId, _Name) \ - explicit _Name(QJsonObject obj) : Event(typeId(), std::move(obj)) {} \ - explicit _Name(_ContentType content) \ - : Event(typeId(), matrixTypeId(), \ - QJsonObject { { QStringLiteral(#_ContentKey), \ - toJson(std::move(content)) } }) \ - {} \ - auto _ContentKey() const \ - { \ - return content<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>, - ignored_users) +DEFINE_SIMPLE_EVENT(TagEvent, Event, "m.tag", TagsMap, tags, "tags") +DEFINE_SIMPLE_EVENT(ReadMarkerEventImpl, Event, "m.fully_read", QString, + eventId, "event_id") +class ReadMarkerEvent : public ReadMarkerEventImpl { +public: + using ReadMarkerEventImpl::ReadMarkerEventImpl; + [[deprecated("Use ReadMarkerEvent::eventId() instead")]] + auto event_id() const { return eventId(); } +}; +DEFINE_SIMPLE_EVENT(IgnoredUsersEventImpl, Event, "m.ignored_user_list", + QSet<QString>, ignoredUsers, "ignored_users") +class IgnoredUsersEvent : public IgnoredUsersEventImpl { +public: + using IgnoredUsersEventImpl::IgnoredUsersEventImpl; + [[deprecated("Use IgnoredUsersEvent::ignoredUsers() instead")]] + auto ignored_users() const { return ignoredUsers(); } +}; } // namespace Quotient diff --git a/lib/events/callanswerevent.cpp b/lib/events/callanswerevent.cpp deleted file mode 100644 index d6622b30..00000000 --- a/lib/events/callanswerevent.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard <marius@ubports.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "callanswerevent.h" - -#include "event.h" -#include "logging.h" - -#include <QtCore/QJsonDocument> - -/* -m.call.answer -{ - "age": 242352, - "content": { - "answer": { - "sdp": "v=0\r\no=- 6584580628695956864 2 IN IP4 127.0.0.1[...]", - "type": "answer" - }, - "call_id": "12345", - "lifetime": 60000, - "version": 0 - }, - "event_id": "$WLGTSEFSEF:localhost", - "origin_server_ts": 1431961217939, - "room_id": "!Cuyf34gef24t:localhost", - "sender": "@example:localhost", - "type": "m.call.answer" -} -*/ - -using namespace Quotient; - -CallAnswerEvent::CallAnswerEvent(const QJsonObject& obj) - : CallEventBase(typeId(), 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, - { { QStringLiteral("answer"), - QJsonObject { { QStringLiteral("type"), QStringLiteral("answer") }, - { QStringLiteral("sdp"), sdp } } } }) -{} diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h deleted file mode 100644 index 2709882b..00000000 --- a/lib/events/callanswerevent.h +++ /dev/null @@ -1,45 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard <marius@ubports.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#pragma once - -#include "roomevent.h" - -namespace Quotient { -class CallAnswerEvent : public CallEventBase { -public: - DEFINE_EVENT_TYPEID("m.call.answer", CallAnswerEvent) - - 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 content<int>("lifetime"_ls); - } // FIXME: Omittable<>? - QString sdp() const - { - return contentJson()["answer"_ls].toObject().value("sdp"_ls).toString(); - } -}; - -REGISTER_EVENT_TYPE(CallAnswerEvent) -} // namespace Quotient diff --git a/lib/events/callcandidatesevent.cpp b/lib/events/callcandidatesevent.cpp deleted file mode 100644 index 24f0dd46..00000000 --- a/lib/events/callcandidatesevent.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard <marius@ubports.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "callcandidatesevent.h" - -/* -m.call.candidates -{ - "age": 242352, - "content": { - "call_id": "12345", - "candidates": [ - { - "candidate": "candidate:863018703 1 udp 2122260223 10.9.64.156 -43670 typ host generation 0", "sdpMLineIndex": 0, "sdpMid": "audio" - } - ], - "version": 0 - }, - "event_id": "$WLGTSEFSEF:localhost", - "origin_server_ts": 1431961217939, - "room_id": "!Cuyf34gef24t:localhost", - "sender": "@example:localhost", - "type": "m.call.candidates" -} -*/ diff --git a/lib/events/callcandidatesevent.h b/lib/events/callcandidatesevent.h deleted file mode 100644 index e224f048..00000000 --- a/lib/events/callcandidatesevent.h +++ /dev/null @@ -1,45 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard <marius@ubports.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#pragma once - -#include "roomevent.h" - -namespace Quotient { -class CallCandidatesEvent : public CallEventBase { -public: - DEFINE_EVENT_TYPEID("m.call.candidates", CallCandidatesEvent) - - explicit CallCandidatesEvent(const QJsonObject& obj) - : CallEventBase(typeId(), obj) - {} - - explicit CallCandidatesEvent(const QString& callId, - const QJsonArray& candidates) - : CallEventBase(typeId(), matrixTypeId(), callId, 0, - { { QStringLiteral("candidates"), candidates } }) - {} - - QJsonArray candidates() const - { - return content<QJsonArray>("candidates"_ls); - } -}; - -REGISTER_EVENT_TYPE(CallCandidatesEvent) -} // namespace Quotient diff --git a/lib/events/callevents.cpp b/lib/events/callevents.cpp new file mode 100644 index 00000000..3873614d --- /dev/null +++ b/lib/events/callevents.cpp @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2022 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "callevents.h" + +#include "logging.h" + +using namespace Quotient; + +QJsonObject CallEvent::basicJson(const QString& matrixType, + const QString& callId, int version, + QJsonObject contentJson) +{ + contentJson.insert(QStringLiteral("call_id"), callId); + contentJson.insert(QStringLiteral("version"), version); + return RoomEvent::basicJson(matrixType, contentJson); +} + +CallEvent::CallEvent(const QJsonObject& json) + : RoomEvent(json) +{ + if (callId().isEmpty()) + qCWarning(EVENTS) << id() << "is a call event with an empty call id"; +} + +/* +m.call.invite +{ + "age": 242352, + "content": { + "call_id": "12345", + "lifetime": 60000, + "offer": { + "sdp": "v=0\r\no=- 6584580628695956864 2 IN IP4 127.0.0.1[...]", + "type": "offer" + }, + "version": 0 + }, + "event_id": "$WLGTSEFSEF:localhost", + "origin_server_ts": 1431961217939, + "room_id": "!Cuyf34gef24t:localhost", + "sender": "@example:localhost", + "type": "m.call.invite" +} +*/ + +CallInviteEvent::CallInviteEvent(const QString& callId, int lifetime, + const QString& sdp) + : EventTemplate( + callId, + { { QStringLiteral("lifetime"), lifetime }, + { QStringLiteral("offer"), + QJsonObject{ { QStringLiteral("type"), QStringLiteral("offer") }, + { QStringLiteral("sdp"), sdp } } } }) +{} + +/* +m.call.answer +{ + "age": 242352, + "content": { + "answer": { + "sdp": "v=0\r\no=- 6584580628695956864 2 IN IP4 127.0.0.1[...]", + "type": "answer" + }, + "call_id": "12345", + "version": 0 + }, + "event_id": "$WLGTSEFSEF:localhost", + "origin_server_ts": 1431961217939, + "room_id": "!Cuyf34gef24t:localhost", + "sender": "@example:localhost", + "type": "m.call.answer" +} +*/ + +CallAnswerEvent::CallAnswerEvent(const QString& callId, const QString& sdp) + : EventTemplate(callId, { { QStringLiteral("answer"), + QJsonObject { { QStringLiteral("type"), + QStringLiteral("answer") }, + { QStringLiteral("sdp"), sdp } } } }) +{} diff --git a/lib/events/callevents.h b/lib/events/callevents.h new file mode 100644 index 00000000..752e331d --- /dev/null +++ b/lib/events/callevents.h @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: 2022 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "roomevent.h" + +namespace Quotient { + +class QUOTIENT_API CallEvent : public RoomEvent { +public: + QUO_BASE_EVENT(CallEvent, "m.call.*"_ls, RoomEvent::BaseMetaType) + static bool matches(const QJsonObject&, const QString& mType) + { + return mType.startsWith("m.call."); + } + + QUO_CONTENT_GETTER(QString, callId) + QUO_CONTENT_GETTER(int, version) + +protected: + explicit CallEvent(const QJsonObject& json); + + static QJsonObject basicJson(const QString& matrixType, + const QString& callId, int version, + QJsonObject contentJson = {}); +}; +using CallEventBase + [[deprecated("CallEventBase is CallEvent now")]] = CallEvent; + +template <typename EventT> +class EventTemplate<EventT, CallEvent> : public CallEvent { +public: + using CallEvent::CallEvent; + explicit EventTemplate(const QString& callId, + const QJsonObject& contentJson = {}) + : EventTemplate(basicJson(EventT::TypeId, callId, 0, contentJson)) + {} +}; + +template <typename EventT, typename ContentT> +class EventTemplate<EventT, CallEvent, ContentT> + : public EventTemplate<EventT, CallEvent> { +public: + using EventTemplate<EventT, CallEvent>::EventTemplate; + template <typename... ContentParamTs> + explicit EventTemplate(const QString& callId, + ContentParamTs&&... contentParams) + : EventTemplate<EventT, CallEvent>( + callId, + toJson(ContentT{ std::forward<ContentParamTs>(contentParams)... })) + {} +}; + +class QUOTIENT_API CallInviteEvent + : public EventTemplate<CallInviteEvent, CallEvent> { +public: + QUO_EVENT(CallInviteEvent, "m.call.invite") + + using EventTemplate::EventTemplate; + + explicit CallInviteEvent(const QString& callId, int lifetime, + const QString& sdp); + + QUO_CONTENT_GETTER(int, lifetime) + QString sdp() const + { + return contentPart<QJsonObject>("offer"_ls).value("sdp"_ls).toString(); + } +}; + +DEFINE_SIMPLE_EVENT(CallCandidatesEvent, CallEvent, "m.call.candidates", + QJsonArray, candidates, "candidates") + +class QUOTIENT_API CallAnswerEvent + : public EventTemplate<CallAnswerEvent, CallEvent> { +public: + QUO_EVENT(CallAnswerEvent, "m.call.answer") + + using EventTemplate::EventTemplate; + + explicit CallAnswerEvent(const QString& callId, const QString& sdp); + + QString sdp() const + { + return contentPart<QJsonObject>("answer"_ls).value("sdp"_ls).toString(); + } +}; + +class QUOTIENT_API CallHangupEvent + : public EventTemplate<CallHangupEvent, CallEvent> { +public: + QUO_EVENT(CallHangupEvent, "m.call.hangup") + using EventTemplate::EventTemplate; +}; + +} // namespace Quotient +Q_DECLARE_METATYPE(Quotient::CallEvent*) +Q_DECLARE_METATYPE(const Quotient::CallEvent*) diff --git a/lib/events/callhangupevent.cpp b/lib/events/callhangupevent.cpp deleted file mode 100644 index d41849c3..00000000 --- a/lib/events/callhangupevent.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard <marius@ubports.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "callhangupevent.h" - -#include "event.h" -#include "logging.h" - -#include <QtCore/QJsonDocument> - -/* -m.call.hangup -{ - "age": 242352, - "content": { - "call_id": "12345", - "version": 0 - }, - "event_id": "$WLGTSEFSEF:localhost", - "origin_server_ts": 1431961217939, - "room_id": "!Cuyf34gef24t:localhost", - "sender": "@example:localhost", - "type": "m.call.hangup" -} -*/ - -using namespace Quotient; - -CallHangupEvent::CallHangupEvent(const QJsonObject& obj) - : CallEventBase(typeId(), obj) -{ - qCDebug(EVENTS) << "Call Hangup event"; -} - -CallHangupEvent::CallHangupEvent(const QString& callId) - : CallEventBase(typeId(), matrixTypeId(), callId, 0) -{} diff --git a/lib/events/callhangupevent.h b/lib/events/callhangupevent.h deleted file mode 100644 index 5d73fb62..00000000 --- a/lib/events/callhangupevent.h +++ /dev/null @@ -1,33 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard <marius@ubports.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#pragma once - -#include "roomevent.h" - -namespace Quotient { -class CallHangupEvent : public CallEventBase { -public: - DEFINE_EVENT_TYPEID("m.call.hangup", CallHangupEvent) - - explicit CallHangupEvent(const QJsonObject& obj); - explicit CallHangupEvent(const QString& callId); -}; - -REGISTER_EVENT_TYPE(CallHangupEvent) -} // namespace Quotient diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp deleted file mode 100644 index 54faac8d..00000000 --- a/lib/events/callinviteevent.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard <marius@ubports.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "callinviteevent.h" - -#include "event.h" -#include "logging.h" - -#include <QtCore/QJsonDocument> - -/* -m.call.invite -{ - "age": 242352, - "content": { - "call_id": "12345", - "lifetime": 60000, - "offer": { - "sdp": "v=0\r\no=- 6584580628695956864 2 IN IP4 127.0.0.1[...]", - "type": "offer" - }, - "version": 0 - }, - "event_id": "$WLGTSEFSEF:localhost", - "origin_server_ts": 1431961217939, - "room_id": "!Cuyf34gef24t:localhost", - "sender": "@example:localhost", - "type": "m.call.invite" -} -*/ - -using namespace Quotient; - -CallInviteEvent::CallInviteEvent(const QJsonObject& obj) - : CallEventBase(typeId(), obj) -{ - qCDebug(EVENTS) << "Call Invite event"; -} - -CallInviteEvent::CallInviteEvent(const QString& callId, const int lifetime, - const QString& sdp) - : CallEventBase( - typeId(), matrixTypeId(), callId, lifetime, - { { QStringLiteral("lifetime"), lifetime }, - { QStringLiteral("offer"), - QJsonObject { { QStringLiteral("type"), QStringLiteral("offer") }, - { QStringLiteral("sdp"), sdp } } } }) -{} diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h deleted file mode 100644 index b067a492..00000000 --- a/lib/events/callinviteevent.h +++ /dev/null @@ -1,44 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard <marius@ubports.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#pragma once - -#include "roomevent.h" - -namespace Quotient { -class CallInviteEvent : public CallEventBase { -public: - DEFINE_EVENT_TYPEID("m.call.invite", CallInviteEvent) - - explicit CallInviteEvent(const QJsonObject& obj); - - explicit CallInviteEvent(const QString& callId, const int lifetime, - const QString& sdp); - - int lifetime() const - { - return content<int>("lifetime"_ls); - } // FIXME: Omittable<>? - QString sdp() const - { - return contentJson()["offer"_ls].toObject().value("sdp"_ls).toString(); - } -}; - -REGISTER_EVENT_TYPE(CallInviteEvent) -} // namespace Quotient diff --git a/lib/events/directchatevent.cpp b/lib/events/directchatevent.cpp index b4027e16..83bb1e32 100644 --- a/lib/events/directchatevent.cpp +++ b/lib/events/directchatevent.cpp @@ -1,25 +1,8 @@ -/****************************************************************************** - * Copyright (C) 2018 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #include "directchatevent.h" -#include <QtCore/QJsonArray> - using namespace Quotient; QMultiHash<QString, QString> DirectChatEvent::usersToDirectChats() const diff --git a/lib/events/directchatevent.h b/lib/events/directchatevent.h index bb091c5c..0756d816 100644 --- a/lib/events/directchatevent.h +++ b/lib/events/directchatevent.h @@ -1,33 +1,17 @@ -/****************************************************************************** - * Copyright (C) 2018 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include "event.h" namespace Quotient { -class DirectChatEvent : public Event { +class QUOTIENT_API DirectChatEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.direct", DirectChatEvent) + QUO_EVENT(DirectChatEvent, "m.direct") - explicit DirectChatEvent(const QJsonObject& obj) : Event(typeId(), obj) {} + using Event::Event; QMultiHash<QString, QString> usersToDirectChats() const; }; -REGISTER_EVENT_TYPE(DirectChatEvent) } // namespace Quotient diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index dccfa540..540594d1 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -1,32 +1,69 @@ -#include "encryptedevent.h" +// SPDX-FileCopyrightText: 2019 Alexey Andreyev <aa13q@ya.ru> +// SPDX-License-Identifier: LGPL-2.1-or-later -#include "room.h" +#include "encryptedevent.h" +#include "e2ee/e2ee.h" +#include "logging.h" using namespace Quotient; -using namespace QtOlm; -EncryptedEvent::EncryptedEvent(const QJsonObject& ciphertext, +EncryptedEvent::EncryptedEvent(const QJsonObject& ciphertexts, const QString& senderKey) - : RoomEvent(typeId(), matrixTypeId(), - { { AlgorithmKeyL, OlmV1Curve25519AesSha2AlgoKey }, - { CiphertextKeyL, ciphertext }, - { SenderKeyKeyL, senderKey } }) + : RoomEvent(basicJson(TypeId, { { AlgorithmKeyL, OlmV1Curve25519AesSha2AlgoKey }, + { CiphertextKeyL, ciphertexts }, + { SenderKeyKeyL, senderKey } })) {} -EncryptedEvent::EncryptedEvent(QByteArray ciphertext, const QString& senderKey, +EncryptedEvent::EncryptedEvent(const QByteArray& ciphertext, + const QString& senderKey, const QString& deviceId, const QString& sessionId) - : RoomEvent(typeId(), matrixTypeId(), - { - { AlgorithmKeyL, MegolmV1AesSha2AlgoKey }, - { CiphertextKeyL, QString(ciphertext) }, - { DeviceIdKeyL, deviceId }, - { SenderKeyKeyL, senderKey }, - { SessionIdKeyL, sessionId }, - }) + : RoomEvent(basicJson(TypeId, { { AlgorithmKeyL, MegolmV1AesSha2AlgoKey }, + { CiphertextKeyL, QString(ciphertext) }, + { DeviceIdKeyL, deviceId }, + { SenderKeyKeyL, senderKey }, + { SessionIdKeyL, sessionId } })) {} EncryptedEvent::EncryptedEvent(const QJsonObject& obj) - : RoomEvent(typeId(), obj) + : RoomEvent(obj) { qCDebug(E2EE) << "Encrypted event from" << senderId(); } + +QString EncryptedEvent::algorithm() const +{ + const auto algo = contentPart<QString>(AlgorithmKeyL); + if (!isSupportedAlgorithm(algo)) + qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo + << "is not supported"; + + return algo; +} + +RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const +{ + auto eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); + eventObject["event_id"] = id(); + eventObject["sender"] = senderId(); + eventObject["origin_server_ts"] = originTimestamp().toMSecsSinceEpoch(); + if (const auto relatesToJson = contentPart<QJsonObject>("m.relates_to"_ls); + !relatesToJson.isEmpty()) { + auto content = eventObject["content"].toObject(); + content["m.relates_to"] = relatesToJson; + eventObject["content"] = content; + } + if (const auto redactsJson = unsignedPart<QString>("redacts"_ls); + !redactsJson.isEmpty()) { + auto unsign = eventObject["unsigned"].toObject(); + unsign["redacts"] = redactsJson; + eventObject["unsigned"] = unsign; + } + return loadEvent<RoomEvent>(eventObject); +} + +void EncryptedEvent::setRelation(const QJsonObject& relation) +{ + auto content = contentJson(); + content["m.relates_to"] = relation; + editJson()["content"] = content; +} diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 235b2aa4..e24e5745 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -1,10 +1,17 @@ +// SPDX-FileCopyrightText: 2019 Alexey Andreyev <aa13q@ya.ru> +// SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once -#include "e2ee.h" #include "roomevent.h" namespace Quotient { -class Room; + +constexpr auto CiphertextKeyL = "ciphertext"_ls; +constexpr auto SenderKeyKeyL = "sender_key"_ls; +constexpr auto DeviceIdKeyL = "device_id"_ls; +constexpr auto SessionIdKeyL = "session_id"_ls; + /* * While the specification states: * @@ -23,44 +30,39 @@ class Room; * in general. It's possible, because RoomEvent interface is similar to Event's * one and doesn't add new restrictions, just provides additional features. */ -class EncryptedEvent : public RoomEvent { - Q_GADGET +class QUOTIENT_API EncryptedEvent : public RoomEvent { public: - DEFINE_EVENT_TYPEID("m.room.encrypted", EncryptedEvent) + QUO_EVENT(EncryptedEvent, "m.room.encrypted") /* In case with Olm, the encrypted content of the event is * a map from the recipient Curve25519 identity key to ciphertext * information */ - explicit EncryptedEvent(const QJsonObject& ciphertext, + explicit EncryptedEvent(const QJsonObject& ciphertexts, const QString& senderKey); /* In case with Megolm, device_id and session_id are required */ - explicit EncryptedEvent(QByteArray ciphertext, const QString& senderKey, - const QString& deviceId, const QString& sessionId); + explicit EncryptedEvent(const QByteArray& ciphertext, + const QString& senderKey, const QString& deviceId, + const QString& sessionId); explicit EncryptedEvent(const QJsonObject& obj); - QString algorithm() const - { - QString algo = content<QString>(AlgorithmKeyL); - if (!SupportedAlgorithms.contains(algo)) { - qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo - << "is not supported"; - } - return algo; - } + QString algorithm() const; QByteArray ciphertext() const { - return content<QString>(CiphertextKeyL).toLatin1(); + return contentPart<QString>(CiphertextKeyL).toLatin1(); } QJsonObject ciphertext(const QString& identityKey) const { - return content<QJsonObject>(CiphertextKeyL).value(identityKey).toObject(); + return contentPart<QJsonObject>(CiphertextKeyL) + .value(identityKey) + .toObject(); } - QString senderKey() const { return content<QString>(SenderKeyKeyL); } + QString senderKey() const { return contentPart<QString>(SenderKeyKeyL); } /* device_id and session_id are required with Megolm */ - QString deviceId() const { return content<QString>(DeviceIdKeyL); } - QString sessionId() const { return content<QString>(SessionIdKeyL); } -}; -REGISTER_EVENT_TYPE(EncryptedEvent) + QString deviceId() const { return contentPart<QString>(DeviceIdKeyL); } + QString sessionId() const { return contentPart<QString>(SessionIdKeyL); } + RoomEventPtr createDecrypted(const QString &decrypted) const; + void setRelation(const QJsonObject& relation); +}; } // namespace Quotient diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index f1bde621..b1b04984 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -1,45 +1,53 @@ +// SPDX-FileCopyrightText: 2017 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-FileCopyrightText: 2019 Alexey Andreyev <aa13q@ya.ru> +// SPDX-License-Identifier: LGPL-2.1-or-later + #include "encryptionevent.h" +#include "logging.h" -#include "e2ee.h" +#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(Quotient::EncryptionType et) + : encryption(et) +{ + 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 cbd3ba4a..4bf7459c 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -1,73 +1,47 @@ -/****************************************************************************** - * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-FileCopyrightText: 2019 Alexey Andreyev <aa13q@ya.ru> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once -#include "eventcontent.h" +#include "quotient_common.h" #include "stateevent.h" namespace Quotient { -class 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; - explicit EncryptionEventContent(EncryptionType et = Undefined) - : encryption(et) - {} + // 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 EncryptionEvent : public StateEvent<EncryptionEventContent> { - Q_GADGET +class QUOTIENT_API EncryptionEvent + : public KeylessStateEventBase<EncryptionEvent, EncryptionEventContent> { public: - DEFINE_EVENT_TYPEID("m.room.encryption", EncryptionEvent) - - using EncryptionType = EncryptionEventContent::EncryptionType; + QUO_EVENT(EncryptionEvent, "m.room.encryption") - explicit EncryptionEvent(const QJsonObject& obj = {}) // TODO: apropriate - // default value - : StateEvent(typeId(), obj) - {} - template <typename... ArgTs> - EncryptionEvent(ArgTs&&... contentArgs) - : StateEvent(typeId(), matrixTypeId(), QString(), - std::forward<ArgTs>(contentArgs)...) - {} + using EncryptionType + [[deprecated("Use Quotient::EncryptionType instead")]] = + Quotient::EncryptionType; - EncryptionType encryption() const { return content().encryption; } + using KeylessStateEventBase::KeylessStateEventBase; + 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; } -private: - Q_ENUM(EncryptionType) + 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 7b34114d..da7de919 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -1,48 +1,54 @@ -/****************************************************************************** - * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2016 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #include "event.h" +#include "callevents.h" #include "logging.h" +#include "stateevent.h" #include <QtCore/QJsonDocument> using namespace Quotient; -event_type_t EventTypeRegistry::initializeTypeId(event_mtype_t matrixTypeId) -{ - const auto id = get().eventTypes.size(); - get().eventTypes.push_back(matrixTypeId); - if (strncmp(matrixTypeId, "", 1) == 0) - qDebug(EVENTS) << "Initialized unknown event type with id" << id; - else - qDebug(EVENTS) << "Initialized event type" << matrixTypeId << "with id" - << id; - return id; -} +QString EventTypeRegistry::getMatrixType(event_type_t typeId) { return typeId; } -QString EventTypeRegistry::getMatrixType(event_type_t typeId) +void AbstractEventMetaType::addDerived(AbstractEventMetaType* newType) { - return typeId < get().eventTypes.size() ? get().eventTypes[typeId] - : QString(); + if (const auto existing = + std::find_if(derivedTypes.cbegin(), derivedTypes.cend(), + [&newType](const AbstractEventMetaType* t) { + return t->matrixId == newType->matrixId; + }); + existing != derivedTypes.cend()) + { + if (*existing == newType) + return; + // Two different metatype objects claim the same Matrix type id; this + // is not normal, so give as much information as possible to diagnose + if ((*existing)->className == newType->className) { + qCritical(EVENTS) + << newType->className << "claims" << newType->matrixId + << "repeatedly; check that it's exported across translation " + "units or shared objects"; + Q_ASSERT(false); // That situation is plain wrong + return; // So maybe std::terminate() even? + } + qWarning(EVENTS).nospace() + << newType->matrixId << " is already mapped to " + << (*existing)->className << " before " << newType->className + << "; unless the two have different isValid() conditions, the " + "latter class will never be used"; + } + derivedTypes.emplace_back(newType); + qDebug(EVENTS).nospace() + << newType->matrixId << " -> " << newType->className << "; " + << derivedTypes.size() << " derived type(s) registered for " + << className; } -Event::Event(Type type, const QJsonObject& json) : _type(type), _json(json) +Event::Event(const QJsonObject& json) + : _json(json) { if (!json.contains(ContentKeyL) && !json.value(UnsignedKeyL).toObject().contains(RedactedCauseKeyL)) { @@ -51,29 +57,26 @@ 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::~Event() = default; QString Event::matrixType() const { return fullJson()[TypeKeyL].toString(); } QByteArray Event::originalJson() const { return QJsonDocument(_json).toJson(); } -// On const below: this is to catch accidental attempts to change event JSON -// NOLINTNEXTLINE(readability-const-return-type) const QJsonObject Event::contentJson() const { return fullJson()[ContentKeyL].toObject(); } -// NOLINTNEXTLINE(readability-const-return-type) const QJsonObject Event::unsignedJson() const { return fullJson()[UnsignedKeyL].toObject(); } +bool Event::isStateEvent() const { return is<StateEvent>(); } + +bool Event::isCallEvent() const { return is<CallEvent>(); } + void Event::dumpTo(QDebug dbg) const { dbg << QJsonDocument(contentJson()).toJson(QJsonDocument::Compact); diff --git a/lib/events/event.h b/lib/events/event.h index 6c8961ad..0abef1f0 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -1,32 +1,14 @@ -/****************************************************************************** - * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2016 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include "converters.h" -#include "logging.h" - -#ifdef ENABLE_EVENTTYPE_ALIAS -# define USE_EVENTTYPE_ALIAS 1 -#endif +#include "function_traits.h" +#include "single_key_value.h" namespace Quotient { -// === event_ptr_tt<> and type casting facilities === +// === event_ptr_tt<> and basic type casting facilities === template <typename EventT> using event_ptr_tt = std::unique_ptr<EventT>; @@ -45,198 +27,288 @@ inline TargetEventT* weakPtrCast(const event_ptr_tt<EventT>& ptr) return static_cast<TargetEventT*>(rawPtr(ptr)); } -/// Re-wrap a smart pointer to base into a smart pointer to derived -template <typename TargetT, typename SourceT> -[[deprecated("Consider using eventCast() or visit() instead")]] -inline event_ptr_tt<TargetT> ptrCast(event_ptr_tt<SourceT>&& ptr) -{ - return std::unique_ptr<TargetT>(static_cast<TargetT*>(ptr.release())); -} - // === Standard Matrix key names and basicEventJson() === -static const auto TypeKey = QStringLiteral("type"); -static const auto BodyKey = QStringLiteral("body"); -static const auto ContentKey = QStringLiteral("content"); -static const auto EventIdKey = QStringLiteral("event_id"); -static const auto UnsignedKey = QStringLiteral("unsigned"); -static const auto StateKeyKey = QStringLiteral("state_key"); -static const auto TypeKeyL = "type"_ls; -static const auto BodyKeyL = "body"_ls; -static const auto ContentKeyL = "content"_ls; -static const auto EventIdKeyL = "event_id"_ls; -static const auto UnsignedKeyL = "unsigned"_ls; -static const auto RedactedCauseKeyL = "redacted_because"_ls; -static const auto PrevContentKeyL = "prev_content"_ls; -static const auto StateKeyKeyL = "state_key"_ls; - -/// Make a minimal correct Matrix event JSON -template <typename StrT> -inline QJsonObject basicEventJson(StrT matrixType, const QJsonObject& content) -{ - return { { TypeKey, std::forward<StrT>(matrixType) }, - { ContentKey, content } }; -} - -// === Event types and event types registry === - -using event_type_t = size_t; -using event_mtype_t = const char*; +constexpr auto TypeKeyL = "type"_ls; +constexpr auto BodyKeyL = "body"_ls; +constexpr auto ContentKeyL = "content"_ls; +constexpr auto EventIdKeyL = "event_id"_ls; +constexpr auto SenderKeyL = "sender"_ls; +constexpr auto RoomIdKeyL = "room_id"_ls; +constexpr auto UnsignedKeyL = "unsigned"_ls; +constexpr auto RedactedCauseKeyL = "redacted_because"_ls; +constexpr auto PrevContentKeyL = "prev_content"_ls; +constexpr auto StateKeyKeyL = "state_key"_ls; +const QString TypeKey { TypeKeyL }; +const QString BodyKey { BodyKeyL }; +const QString ContentKey { ContentKeyL }; +const QString EventIdKey { EventIdKeyL }; +const QString SenderKey { SenderKeyL }; +const QString RoomIdKey { RoomIdKeyL }; +const QString UnsignedKey { UnsignedKeyL }; +const QString StateKeyKey { StateKeyKeyL }; + +using event_type_t = QLatin1String; + +// TODO: Remove in 0.8 +struct QUOTIENT_API EventTypeRegistry { + [[deprecated("event_type_t is a string since libQuotient 0.7, use it directly instead")]] + static QString getMatrixType(event_type_t typeId); -class EventTypeRegistry { -public: + EventTypeRegistry() = delete; ~EventTypeRegistry() = default; + Q_DISABLE_COPY_MOVE(EventTypeRegistry) +}; - static event_type_t initializeTypeId(event_mtype_t matrixTypeId); +// === EventMetaType === - template <typename EventT> - static inline event_type_t initializeTypeId() - { - return initializeTypeId(EventT::matrixTypeId()); - } +class Event; - static QString getMatrixType(event_type_t typeId); +// TODO: move over to std::derived_from<Event> once it's available everywhere +template <typename EventT, typename BaseEventT = Event> +concept EventClass = std::is_base_of_v<BaseEventT, EventT>; -private: - EventTypeRegistry() = default; - Q_DISABLE_COPY(EventTypeRegistry) - DISABLE_MOVE(EventTypeRegistry) +template <EventClass EventT> +bool is(const Event& e); - static EventTypeRegistry& get() +//! \brief The base class for event metatypes +//! +//! You should not normally have to use this directly, unless you need to devise +//! a whole new kind of event metatypes. +class QUOTIENT_API AbstractEventMetaType { +public: + // The public fields here are const and are not to be changeable anyway. + // NOLINTBEGIN(misc-non-private-member-variables-in-classes) + const char* const className; + const event_type_t matrixId; + const AbstractEventMetaType* const baseType = nullptr; + // NOLINTEND(misc-non-private-member-variables-in-classes) + + explicit AbstractEventMetaType(const char* className) + : className(className) + {} + explicit AbstractEventMetaType(const char* className, event_type_t matrixId, + AbstractEventMetaType& nearestBase) + : className(className), matrixId(matrixId), baseType(&nearestBase) { - static EventTypeRegistry etr; - return etr; + nearestBase.addDerived(this); } - std::vector<event_mtype_t> eventTypes; -}; - -template <> -inline event_type_t EventTypeRegistry::initializeTypeId<void>() -{ - return initializeTypeId(""); -} + void addDerived(AbstractEventMetaType *newType); -template <typename EventT> -struct EventTypeTraits { - static event_type_t id() - { - static const auto id = EventTypeRegistry::initializeTypeId<EventT>(); - return id; - } -}; + virtual ~AbstractEventMetaType() = default; -template <typename EventT> -inline event_type_t typeId() -{ - return EventTypeTraits<std::decay_t<EventT>>::id(); -} +protected: + // Allow template specialisations to call into one another + template <class EventT> + friend class EventMetaType; -inline event_type_t unknownEventTypeId() { return typeId<void>(); } + // The returned value indicates whether a generic object has to be created + // on the top level when `event` is empty, instead of returning nullptr + virtual bool doLoadFrom(const QJsonObject& fullJson, const QString& type, + Event*& event) const = 0; -// === EventFactory === +private: + std::vector<const AbstractEventMetaType*> derivedTypes{}; + Q_DISABLE_COPY_MOVE(AbstractEventMetaType) +}; -/** Create an event of arbitrary type from its arguments */ -template <typename EventT, typename... ArgTs> -inline event_ptr_tt<EventT> makeEvent(ArgTs&&... args) +// Any event metatype is unique (note Q_DISABLE_COPY_MOVE above) so can be +// identified by its address +inline bool operator==(const AbstractEventMetaType& lhs, + const AbstractEventMetaType& rhs) { - return std::make_unique<EventT>(std::forward<ArgTs>(args)...); + return &lhs == &rhs; } -template <typename BaseEventT> -class EventFactory { +//! \brief A family of event meta-types to load and match events +//! +//! TL;DR for the loadFrom() story: +//! - for base event types, use QUO_BASE_EVENT and, if you have additional +//! validation (e.g., JSON has to contain a certain key - see StateEvent +//! for a real example), define it in the static EventT::isValid() member +//! function accepting QJsonObject and returning bool. +//! - for leaf (specific) event types - simply use QUO_EVENT and it will do +//! everything necessary, including the TypeId definition. +//! \sa QUO_EVENT, QUO_BASE_EVENT +template <class EventT> +class QUOTIENT_API EventMetaType : public AbstractEventMetaType { + // Above: can't constrain EventT to be EventClass because it's incomplete + // at the point of EventMetaType<EventT> instantiation. public: - template <typename FnT> - static auto addMethod(FnT&& method) - { - factories().emplace_back(std::forward<FnT>(method)); - return 0; - } - - /** Chain two type factories - * Adds the factory class of EventT2 (EventT2::factory_t) to - * the list in factory class of EventT1 (EventT1::factory_t) so - * that when EventT1::factory_t::make() is invoked, types of - * EventT2 factory are looked through as well. This is used - * to include RoomEvent types into the more general Event factory, - * and state event types into the RoomEvent factory. - */ - template <typename EventT> - static auto chainFactory() + using AbstractEventMetaType::AbstractEventMetaType; + + //! \brief Try to load an event from JSON, with dynamic type resolution + //! + //! The generic logic defined in this class template and invoked applies to + //! all event types defined in the library and boils down to the following: + //! 1. + //! a. If EventT has TypeId defined (which normally is a case of + //! all leaf - specific - event types, via QUO_EVENT macro) and + //! \p type doesn't exactly match it, nullptr is immediately returned. + //! b. In absence of TypeId, an event type is assumed to be a base; + //! its derivedTypes are examined, and this algorithm is applied + //! recursively on each. + //! 2. Optional validation: if EventT (or, due to the way inheritance works, + //! any of its base event types) has a static isValid() predicate and + //! the event JSON does not satisfy it, nullptr is immediately returned + //! to the upper level or to the loadFrom() caller. This is how existence + //! of `state_key` is checked in any type derived from StateEvent. + //! 3. If step 1b above returned non-nullptr, immediately return it. + //! 4. + //! a. If EventT::isValid() or EventT::TypeId (either, or both) exist and + //! are satisfied (see steps 1a and 2 above), an object of this type + //! is created from the passed JSON and returned. In case of a base + //! event type, this will be a generic (aka "unknown") event. + //! b. If neither exists, a generic event is only created and returned + //! when on the top level (i.e., outside of recursion into + //! derivedTypes); lower levels return nullptr instead and the type + //! lookup continues. The latter is a case of a derived base event + //! metatype (e.g. RoomEvent) called from its base event metatype + //! (i.e., Event). If no matching type derived from RoomEvent is found, + //! the nested lookup returns nullptr rather than a generic RoomEvent, + //! so that other types derived from Event could be examined. + event_ptr_tt<EventT> loadFrom(const QJsonObject& fullJson, + const QString& type) const { - return addMethod(&EventT::factory_t::make); - } - - static event_ptr_tt<BaseEventT> make(const QJsonObject& json, - const QString& matrixType) - { - for (const auto& f : factories()) - if (auto e = f(json, matrixType)) - return e; - return nullptr; + Event* event = nullptr; + const bool goodEnough = doLoadFrom(fullJson, type, event); + if (!event && goodEnough) + return event_ptr_tt<EventT>{ new EventT(fullJson) }; + return event_ptr_tt<EventT>{ static_cast<EventT*>(event) }; } private: - static auto& factories() + bool doLoadFrom(const QJsonObject& fullJson, const QString& type, + Event*& event) const override { - using inner_factory_tt = std::function<event_ptr_tt<BaseEventT>( - const QJsonObject&, const QString&)>; - static std::vector<inner_factory_tt> _factories {}; - return _factories; + if constexpr (requires { EventT::TypeId; }) { + if (EventT::TypeId != type) + return false; + } else { + for (const auto& p : derivedTypes) { + p->doLoadFrom(fullJson, type, event); + if (event) { + Q_ASSERT(is<EventT>(*event)); + return false; + } + } + } + if constexpr (requires { EventT::isValid; }) { + if (!EventT::isValid(fullJson)) + return false; + } else if constexpr (!requires { EventT::TypeId; }) + return true; // Create a generic event object if on the top level + event = new EventT(fullJson); + return false; } }; -/** Add a type to its default factory - * Adds a standard factory method (via makeEvent<>) for a given - * type to EventT::factory_t factory class so that it can be - * created dynamically from loadEvent<>(). - * - * \tparam EventT the type to enable dynamic creation of - * \return the registered type id - * \sa loadEvent, Event::type - */ -template <typename EventT> -inline auto setupFactory() +// === Event creation facilities === + +//! \brief Create an event of arbitrary type from its arguments +//! +//! This should not be used to load events from JSON - use loadEvent() for that. +//! \sa loadEvent +template <EventClass EventT, typename... ArgTs> +inline event_ptr_tt<EventT> makeEvent(ArgTs&&... args) { - qDebug(EVENTS) << "Adding factory method for" << EventT::matrixTypeId(); - return EventT::factory_t::addMethod([](const QJsonObject& json, - const QString& jsonMatrixType) { - return EventT::matrixTypeId() == jsonMatrixType ? makeEvent<EventT>(json) - : nullptr; - }); + return std::make_unique<EventT>(std::forward<ArgTs>(args)...); } -template <typename EventT> -inline auto registerEventType() +template <EventClass EventT> +constexpr const auto& mostSpecificMetaType() { - // Initialise exactly once, even if this function is called twice for - // the same type (for whatever reason - you never know the ways of - // static initialisation is done). - static const auto _ = setupFactory<EventT>(); - return _; // Only to facilitate usage in static initialisation + if constexpr (requires { EventT::MetaType; }) + return EventT::MetaType; + else + return EventT::BaseMetaType; } +//! \brief Create an event with proper type from a JSON object +//! +//! Use this factory template to detect the type from the JSON object +//! contents (the detected event type should derive from the template +//! parameter type) and create an event object of that type. +template <EventClass EventT> +inline event_ptr_tt<EventT> loadEvent(const QJsonObject& fullJson) +{ + return mostSpecificMetaType<EventT>().loadFrom( + fullJson, fullJson[TypeKeyL].toString()); +} + +//! \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 <EventClass EventT> +inline event_ptr_tt<EventT> loadEvent(const QString& matrixType, + const auto&... otherBasicJsonParams) +{ + return mostSpecificMetaType<EventT>().loadFrom( + EventT::basicJson(matrixType, otherBasicJsonParams...), matrixType); +} + +template <EventClass EventT> +struct JsonConverter<event_ptr_tt<EventT>> + : JsonObjectUnpacker<event_ptr_tt<EventT>> { + // No dump() to avoid any ambiguity on whether a given export to JSON uses + // fullJson() or only contentJson() + using JsonObjectUnpacker<event_ptr_tt<EventT>>::load; + static auto load(const QJsonObject& jo) + { + return loadEvent<EventT>(jo); + } +}; + // === Event === -class Event { - Q_GADGET - Q_PROPERTY(Type type READ type CONSTANT) - Q_PROPERTY(QJsonObject contentJson READ contentJson CONSTANT) +class QUOTIENT_API Event { public: using Type = event_type_t; - using factory_t = EventFactory<Event>; + static inline EventMetaType<Event> BaseMetaType { "Event" }; + virtual const AbstractEventMetaType& metaType() const + { + return BaseMetaType; + } - explicit Event(Type type, const QJsonObject& json); - explicit Event(Type type, event_mtype_t matrixType, - const QJsonObject& contentJson = {}); Q_DISABLE_COPY(Event) - Event(Event&&) = default; + Event(Event&&) noexcept = default; Event& operator=(Event&&) = delete; virtual ~Event(); - Type type() const { return _type; } + /// Make a minimal correct Matrix event JSON + static QJsonObject basicJson(const QString& matrixType, + const QJsonObject& content) + { + return { { TypeKey, matrixType }, { ContentKey, content } }; + } + + //! \brief Event Matrix type, as identified by its metatype object + //! + //! For generic/unknown events it will contain a descriptive/generic string + //! defined by the respective base event type (that can be empty). + //! \sa matrixType + Type type() const { return metaType().matrixId; } + + //! \brief Exact Matrix type stored in JSON + //! + //! Coincides with the result of type() (but is slower) for events defined + //! in C++ (not necessarily in the library); for generic/unknown events + //! the returned value will be different. QString matrixType() const; + + template <EventClass EventT> + bool is() const + { + return Quotient::is<EventT>(*this); + } + + [[deprecated("Use fullJson() and stringify it with QJsonDocument::toJson() " + "or by other means")]] QByteArray originalJson() const; + [[deprecated("Use fullJson() instead")]] // QJsonObject originalJsonObject() const { return fullJson(); } const QJsonObject& fullJson() const { return _json; } @@ -245,148 +317,320 @@ public: // a "content" object; but since its structure is different for // different types, we're implementing it per-event type. + // NB: const return types below are meant to catch accidental attempts + // to change event JSON (e.g., consider contentJson()["inexistentKey"]). + const QJsonObject contentJson() const; - const QJsonObject unsignedJson() const; + + //! \brief Get a part of the content object, assuming a given type + //! + //! This retrieves the value under `content.<key>` from the event JSON and + //! then converts it to \p T using fromJson(). + //! \sa contentJson, fromJson + template <typename T, typename KeyT> + const T contentPart(KeyT&& key) const + { + return fromJson<T>(contentJson()[std::forward<KeyT>(key)]); + } template <typename T> + [[deprecated("Use contentPart() to get a part of the event content")]] // T content(const QString& key) const { - return fromJson<T>(contentJson()[key]); + return contentPart<T>(key); } - template <typename T> - T content(QLatin1String key) const + const QJsonObject unsignedJson() const; + + //! \brief Get a part of the unsigned object, assuming a given type + //! + //! This retrieves the value under `unsigned.<key>` from the event JSON and + //! then converts it to \p T using fromJson(). + //! \sa unsignedJson, fromJson + template <typename T, typename KeyT> + const T unsignedPart(KeyT&& key) const { - return fromJson<T>(contentJson()[key]); + return fromJson<T>(unsignedJson()[std::forward<KeyT>(key)]); } - friend QDebug operator<<(QDebug dbg, const Event& e) + friend QUOTIENT_API QDebug operator<<(QDebug dbg, const Event& e) { QDebugStateSaver _dss { dbg }; - dbg.noquote().nospace() << e.matrixType() << '(' << e.type() << "): "; + dbg.noquote().nospace() + << e.matrixType() << '(' << e.metaType().className << "): "; e.dumpTo(dbg); return dbg; } - virtual bool isStateEvent() const { return false; } - virtual bool isCallEvent() const { return false; } - virtual void dumpTo(QDebug dbg) const; + // State events are quite special in Matrix; so isStateEvent() is here, + // as an exception. For other base events, Event::is<>() and + // Quotient::is<>() should be used; don't add is* methods here + bool isStateEvent() const; + [[deprecated("Use is<CallEvent>() instead")]] bool isCallEvent() const; protected: + friend class EventMetaType<Event>; // To access the below constructor + + explicit Event(const QJsonObject& json); + QJsonObject& editJson() { return _json; } + virtual void dumpTo(QDebug dbg) const; private: - Type _type; QJsonObject _json; }; using EventPtr = event_ptr_tt<Event>; -template <typename EventT> +template <EventClass EventT> using EventsArray = std::vector<event_ptr_tt<EventT>>; using Events = EventsArray<Event>; -// === Macros used with event class definitions === +// === Facilities for event class definitions === + +//! \brief A template base class to derive your event type from +//! +//! This simple class template generates commonly used event constructor +//! signatures and the content() method with the appropriate return type. +//! The generic version here is only used with non-trivial \p ContentT (if you +//! don't need to create an event from its content structure, just go and derive +//! straight from the respective \p EventBaseT instead of using EventTemplate); +//! specialisations may override that and provide useful semantics even without +//! \p ContentT (see EventTemplate<CallEvent>, e.g.). +//! +//! The template uses CRTP to pick the event type id from the actual class; +//! it will fail to compile if \p EventT doesn't provide TypeId. It also uses +//! the base event type's basicJson(); if you need extra keys to be inserted +//! you may want to bypass this template as writing the code to that effect in +//! your class will likely be clearer and more concise. +//! \sa https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern +//! \sa DEFINE_SIMPLE_EVENT +template <typename EventT, EventClass BaseEventT, typename ContentT = void> +class EventTemplate : public BaseEventT { + // Above: can't constrain EventT to be EventClass because it's incomplete + // by CRTP definition. +public: + static_assert( + !std::is_same_v<ContentT, void>, + "If you see this, you tried to use EventTemplate with the default" + " ContentT type, which is void. This default is only used with explicit" + " specialisations (see CallEvent, e.g.). Otherwise, if you don't intend" + " to use the content part of EventTemplate then you don't need" + " EventTemplate; just use the base event class directly"); + using content_type = ContentT; + + explicit EventTemplate(const QJsonObject& json) + : BaseEventT(json) + {} + explicit EventTemplate(const ContentT& c) + : BaseEventT(EventT::basicJson(EventT::TypeId, toJson(c))) + {} + + ContentT content() const { return fromJson<ContentT>(this->contentJson()); } +}; -// This macro should be used in a public section of an event class to -// provide matrixTypeId() and typeId(). -#define DEFINE_EVENT_TYPEID(_Id, _Type) \ - static constexpr event_mtype_t matrixTypeId() { return _Id; } \ - static auto typeId() { return Quotient::typeId<_Type>(); } \ +//! \brief Supply event metatype information in base event types +//! +//! Use this macro in a public section of your base event class to provide +//! type identity and enable dynamic loading of generic events of that type. +//! Do _not_ add this macro if your class is an intermediate wrapper and is not +//! supposed to be instantiated on its own. Provides BaseMetaType static field +//! initialised by parameters passed to the macro, and a metaType() override +//! pointing to that BaseMetaType. +//! \sa EventMetaType, EventMetaType::SuppressLoadDerived +#define QUO_BASE_EVENT(CppType_, ...) \ + friend class EventMetaType<CppType_>; \ + static inline EventMetaType<CppType_> BaseMetaType{ \ + #CppType_ __VA_OPT__(,) __VA_ARGS__ }; \ + const AbstractEventMetaType& metaType() const override \ + { \ + return BaseMetaType; \ + } \ // End of macro -// This macro should be put after an event class definition (in .h or .cpp) -// to enable its deserialisation from a /sync and other -// polymorphic event arrays -#define REGISTER_EVENT_TYPE(_Type) \ - namespace { \ - [[maybe_unused]] static const auto _factoryAdded##_Type = \ - registerEventType<_Type>(); \ - } \ +//! Supply event metatype information in (specific) event types +//! +//! Use this macro in a public section of your event class to provide type +//! identity and enable dynamic loading of generic events of that type. +//! Do _not_ use this macro if your class is an intermediate wrapper and is not +//! supposed to be instantiated on its own. Provides MetaType static field +//! initialised as described below; a metaType() override pointing to it; and +//! the TypeId static field that is equal to MetaType.matrixId. +//! +//! The first two macro parameters are used as the first two EventMetaType +//! constructor parameters; the third EventMetaType parameter is always +//! BaseMetaType; and additional base types can be passed in extra macro +//! parameters if you need to include the same event type in more than one +//! event factory hierarchy (e.g., EncryptedEvent). +//! \sa EventMetaType +#define QUO_EVENT(CppType_, MatrixType_, ...) \ + static inline const auto& TypeId = MatrixType_##_ls; \ + friend class EventMetaType<CppType_>; \ + static inline const EventMetaType<CppType_> MetaType{ \ + #CppType_, TypeId, BaseMetaType __VA_OPT__(,) __VA_ARGS__ \ + }; \ + const AbstractEventMetaType& metaType() const override \ + { \ + return MetaType; \ + } \ + [[deprecated("Use " #CppType_ "::TypeId directly instead")]] \ + static constexpr const char* matrixTypeId() { return MatrixType_; } \ + [[deprecated("Use " #CppType_ "::TypeId directly instead")]] \ + static event_type_t typeId() { return TypeId; } \ // End of macro -// === is<>(), eventCast<>() and visit<>() === +//! \deprecated This is the old name for what is now known as QUO_EVENT +#define DEFINE_EVENT_TYPEID(Type_, Id_) QUO_EVENT(Type_, Id_) -template <typename EventT> -inline bool is(const Event& e) -{ - return e.type() == typeId<EventT>(); -} +#define QUO_CONTENT_GETTER_X(PartType_, PartName_, JsonKey_) \ + PartType_ PartName_() const \ + { \ + static const auto PartName_##JsonKey = JsonKey_; \ + return contentPart<PartType_>(PartName_##JsonKey); \ + } -inline bool isUnknown(const Event& e) +//! \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_) \ + QUO_CONTENT_GETTER_X(PartType_, PartName_, toSnakeCase(#PartName_##_ls)) + +//! \deprecated This macro was used after an event class definition +//! to enable its dynamic loading; it is completely superseded by QUO_EVENT +#define REGISTER_EVENT_TYPE(Type_) + +/// \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_, \ + JsonKey_) \ + constexpr auto Name_##ContentKey = JsonKey_##_ls; \ + class QUOTIENT_API Name_ \ + : public EventTemplate< \ + Name_, Base_, \ + EventContent::SingleKeyValue<ValueType_, Name_##ContentKey>> { \ + public: \ + QUO_EVENT(Name_, TypeId_) \ + using value_type = ValueType_; \ + using EventTemplate::EventTemplate; \ + QUO_CONTENT_GETTER_X(ValueType_, GetterName_, Name_##ContentKey) \ + }; \ + // End of macro + +// === is<>(), eventCast<>() and switchOnType<>() === + +template <EventClass EventT> +inline bool is(const Event& e) { - return e.type() == unknownEventTypeId(); + if constexpr (requires { EventT::MetaType; }) { + return &e.metaType() == &EventT::MetaType; + } else { + const auto* p = &e.metaType(); + do { + if (p == &EventT::BaseMetaType) + return true; + } while ((p = p->baseType) != nullptr); + return false; + } } -template <typename EventT, typename BasePtrT> +//! \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 <EventClass 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 single generic catch-all visitor -template <typename BaseEventT, typename FnT> -inline auto visit(const BaseEventT& event, FnT&& visitor) - -> decltype(visitor(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 <EventClass EventT, typename BaseEventT> +inline auto eventCast(event_ptr_tt<BaseEventT>&& eptr) { - return visitor(event); + return eptr && is<std::decay_t<EventT>>(*eptr) + ? event_ptr_tt<EventT>(static_cast<EventT*>(eptr.release())) + : nullptr; } namespace _impl { - template <typename T, typename FnT> - constexpr auto needs_downcast() + template <typename FnT, typename BaseT> + concept Invocable_With_Downcast = requires { - return !std::is_convertible_v<T, fn_arg_t<FnT>>; - } + requires EventClass<BaseT>; + std::is_base_of_v<BaseT, std::remove_cvref_t<fn_arg_t<FnT>>>; + }; } -// A single type-specific void visitor -template <typename BaseEventT, typename FnT> -inline std::enable_if_t<_impl::needs_downcast<BaseEventT, FnT>() - && std::is_void_v<fn_return_t<FnT>>> -visit(const BaseEventT& event, FnT&& visitor) +template <EventClass 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)) - visitor(static_cast<event_type>(event)); + 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 single type-specific non-void visitor with an optional default value -// non-voidness is guarded by defaultValue type -template <typename BaseEventT, typename FnT> -inline std::enable_if_t<_impl::needs_downcast<BaseEventT, FnT>(), fn_return_t<FnT>> -visit(const BaseEventT& event, FnT&& visitor, - fn_return_t<FnT>&& defaultValue = {}) +template <EventClass BaseT, typename FnT1, typename... FnTs> +inline auto switchOnType(const BaseT& event, FnT1&& fn1, FnTs&&... fns) { - using event_type = fn_arg_t<FnT>; - if (is<std::decay_t<event_type>>(event)) - return visitor(static_cast<event_type>(event)); - return std::forward<fn_return_t<FnT>>(defaultValue); + 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<FnTs>(fns)...); } -// A chain of 2 or more visitors -template <typename BaseEventT, typename FnT1, typename FnT2, typename... FnTs> -inline fn_return_t<FnT1> visit(const BaseEventT& event, FnT1&& visitor1, - FnT2&& visitor2, FnTs&&... visitors) +template <EventClass BaseT, typename... FnTs> +[[deprecated("The new name for visit() is switchOnType()")]] // +inline auto visit(const BaseT& event, FnTs&&... fns) { - using event_type1 = fn_arg_t<FnT1>; - if (is<std::decay_t<event_type1>>(event)) - return visitor1(static_cast<event_type1&>(event)); - return visit(event, std::forward<FnT2>(visitor2), - std::forward<FnTs>(visitors)...); + return switchOnType(event, std::forward<FnTs>(fns)...); } -// A facility overload that calls void-returning visit() on each event + // A facility overload that calls void-returning switchOnType() on each event // over a range of event pointers +// TODO: replace with ranges::for_each once all standard libraries have it template <typename RangeT, typename... FnTs> -inline auto visitEach(RangeT&& events, FnTs&&... visitors) - -> std::enable_if_t<std::is_convertible_v< - std::decay_t<decltype(**events.begin())>, Event>> +inline auto visitEach(RangeT&& events, FnTs&&... fns) + requires std::is_void_v< + decltype(switchOnType(**begin(events), std::forward<FnTs>(fns)...))> { for (auto&& evtPtr: events) - visit(*evtPtr, std::forward<FnTs>(visitors)...); + switchOnType(*evtPtr, std::forward<FnTs>(fns)...); } } // namespace Quotient Q_DECLARE_METATYPE(Quotient::Event*) diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 802d8176..8db3b7e3 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -1,53 +1,55 @@ -/****************************************************************************** - * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #include "eventcontent.h" #include "converters.h" -#include "util.h" +#include "logging.h" #include <QtCore/QMimeDatabase> +#include <QtCore/QFileInfo> using namespace Quotient::EventContent; +using std::move; QJsonObject Base::toJson() const { QJsonObject o; - fillJson(&o); + fillJson(o); return o; } -FileInfo::FileInfo(const QUrl& u, qint64 payloadSize, const QMimeType& mimeType, - const QString& originalFilename) - : mimeType(mimeType) - , url(u) +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(FileSourceInfo sourceInfo, qint64 payloadSize, + const QMimeType& mimeType, QString originalFilename) + : source(move(sourceInfo)) + , mimeType(mimeType) , payloadSize(payloadSize) - , originalName(originalFilename) -{} + , originalName(move(originalFilename)) +{ + if (!isValid()) + qCWarning(MESSAGES) + << "To client developers: using FileInfo(QUrl, qint64, ...) " + "constructor for non-mxc resources is deprecated since Quotient " + "0.7; for local resources, use FileInfo(QFileInfo) instead"; +} -FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename) - : originalInfoJson(infoJson) +FileInfo::FileInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson, + QString originalFilename) + : source(move(sourceInfo)) + , originalInfoJson(infoJson) , mimeType( QMimeDatabase().mimeTypeForName(infoJson["mimetype"_ls].toString())) - , url(u) , payloadSize(fromJson<qint64>(infoJson["size"_ls])) - , originalName(originalFilename) + , originalName(move(originalFilename)) { if (!mimeType.isValid()) mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); @@ -55,49 +57,66 @@ FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson, 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()); + return getUrlFromSourceInfo(source); } -ImageInfo::ImageInfo(const QUrl& u, qint64 fileSize, QMimeType mimeType, - const QSize& imageSize, const QString& originalFilename) - : FileInfo(u, fileSize, mimeType, originalFilename), imageSize(imageSize) +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(FileSourceInfo sourceInfo, qint64 fileSize, + const QMimeType& type, QSize imageSize, + const QString& originalFilename) + : FileInfo(move(sourceInfo), fileSize, type, originalFilename) + , imageSize(imageSize) {} -ImageInfo::ImageInfo(const QUrl& u, const QJsonObject& infoJson, +ImageInfo::ImageInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson, const QString& originalFilename) - : FileInfo(u, infoJson, 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) - : ImageInfo(infoJson["thumbnail_url"_ls].toString(), +Thumbnail::Thumbnail(const QJsonObject& infoJson, + const Omittable<EncryptedFileMetadata>& efm) + : ImageInfo(QUrl(infoJson["thumbnail_url"_ls].toString()), 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 0d4c047e..af26c0a4 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -1,282 +1,254 @@ -/****************************************************************************** - * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once // This file contains generic event content definitions, applicable to room // message events as well as other events (e.g., avatars). +#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> -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 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: - // FileInfo - // FileContent : UrlBasedContent<FileInfo, Thumbnail> - // AudioContent : UrlBasedContent<FileInfo, Duration> - // ImageInfo : FileInfo + imageSize attribute - // ImageContent : UrlBasedContent<ImageInfo, Thumbnail> - // VideoContent : UrlBasedContent<ImageInfo, Thumbnail, Duration> - - /** - * 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 FileInfo { - public: - explicit FileInfo(const QUrl& u, qint64 payloadSize = -1, - const QMimeType& mimeType = {}, - const QString& originalFilename = {}); - FileInfo(const QUrl& u, const QJsonObject& infoJson, - const 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; - QString originalName; - }; - - template <typename InfoT> - QJsonObject toInfoJson(const InfoT& info) +class QFileInfo; + +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 ImageInfo : public FileInfo { - public: - explicit ImageInfo(const QUrl& u, qint64 fileSize = -1, - QMimeType mimeType = {}, const QSize& imageSize = {}, - const QString& originalFilename = {}); - ImageInfo(const QUrl& u, const QJsonObject& infoJson, - 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 Thumbnail : public ImageInfo { - public: - Thumbnail() : ImageInfo(QUrl()) {} // To allow empty thumbnails - Thumbnail(const QJsonObject& infoJson); - Thumbnail(const ImageInfo& info) : ImageInfo(info) {} - using ImageInfo::ImageInfo; - - /** - * Writes thumbnail information to "thumbnail_info" subobject - * and thumbnail URL to "thumbnail_url" node inside "info". - */ - void fillInfoJson(QJsonObject* infoJson) const; - }; - - class TypedBase : public Base { - public: - explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} - 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: - 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 UrlBasedContent : public TypedBase, public InfoT { - public: - using InfoT::InfoT; - explicit UrlBasedContent(const QJsonObject& json) - : TypedBase(json) - , InfoT(json["url"].toString(), json["info"].toObject(), - 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); - json->insert("url", InfoT::url.toString()); - if (!InfoT::originalName.isEmpty()) - json->insert("filename", InfoT::originalName); - json->insert("info", toInfoJson<InfoT>(*this)); - } - }; - - template <typename InfoT> - class 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 ebb96441..b4ac154c 100644 --- a/lib/events/eventloader.h +++ b/lib/events/eventloader.h @@ -1,86 +1,13 @@ -/****************************************************************************** - * Copyright (C) 2018 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include "stateevent.h" namespace Quotient { -namespace _impl { - template <typename BaseEventT> - static inline auto loadEvent(const QJsonObject& json, - const QString& matrixType) - { - if (auto e = EventFactory<BaseEventT>::make(json, matrixType)) - return e; - return makeEvent<BaseEventT>(unknownEventTypeId(), json); - } -} // namespace _impl - -/*! Create an event with proper type from a JSON object - * - * Use this factory template to detect the type from the JSON object - * contents (the detected event type should derive from the template - * parameter type) and create an event object of that type. - */ -template <typename BaseEventT> -inline event_ptr_tt<BaseEventT> loadEvent(const QJsonObject& fullJson) -{ - return _impl::loadEvent<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) -{ - return _impl::loadEvent<BaseEventT>(basicEventJson(matrixType, content), - matrixType); +struct [[deprecated( + "This header is obsolete since libQuotient 0.7; include a header with" + " the respective event type definition instead")]] EventLoaderH; +StateEventPtr eventLoaderH(EventLoaderH&); } - -/*! 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 _impl::loadEvent<StateEventBase>( - basicStateEventJson(matrixType, content, stateKey), matrixType); -} - -template <typename EventT> -struct JsonConverter<event_ptr_tt<EventT>> { - static auto load(const QJsonValue& jv) - { - return loadEvent<EventT>(jv.toObject()); - } - static auto load(const QJsonDocument& jd) - { - return loadEvent<EventT>(jd.object()); - } -}; -} // namespace Quotient diff --git a/lib/events/eventrelation.cpp b/lib/events/eventrelation.cpp new file mode 100644 index 00000000..04972f45 --- /dev/null +++ b/lib/events/eventrelation.cpp @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2022 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "eventrelation.h" + +#include "../logging.h" +#include "event.h" + +using namespace Quotient; + +void JsonObjectConverter<EventRelation>::dumpTo(QJsonObject& jo, + const EventRelation& pod) +{ + if (pod.type.isEmpty()) { + qCWarning(MAIN) << "Empty relation type; won't dump to JSON"; + return; + } + jo.insert(RelTypeKey, pod.type); + jo.insert(EventIdKey, pod.eventId); + if (pod.type == EventRelation::AnnotationType) + jo.insert(QStringLiteral("key"), pod.key); +} + +void JsonObjectConverter<EventRelation>::fillFrom(const QJsonObject& jo, + EventRelation& pod) +{ + if (const auto replyJson = jo.value(EventRelation::ReplyType).toObject(); + !replyJson.isEmpty()) { + pod.type = EventRelation::ReplyType; + fromJson(replyJson[EventIdKeyL], pod.eventId); + } else { + // The experimental logic for generic relationships (MSC1849) + fromJson(jo[RelTypeKey], pod.type); + fromJson(jo[EventIdKeyL], pod.eventId); + if (pod.type == EventRelation::AnnotationType) + fromJson(jo["key"_ls], pod.key); + } +} diff --git a/lib/events/eventrelation.h b/lib/events/eventrelation.h new file mode 100644 index 00000000..2a841cf1 --- /dev/null +++ b/lib/events/eventrelation.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2022 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "converters.h" + +namespace Quotient { + +[[maybe_unused]] constexpr auto RelatesToKey = "m.relates_to"_ls; +constexpr auto RelTypeKey = "rel_type"_ls; + +struct QUOTIENT_API EventRelation { + using reltypeid_t = QLatin1String; + + QString type; + QString eventId; + QString key = {}; // Only used for m.annotation for now + + static constexpr auto ReplyType = "m.in_reply_to"_ls; + static constexpr auto AnnotationType = "m.annotation"_ls; + static constexpr auto ReplacementType = "m.replace"_ls; + + static EventRelation replyTo(QString eventId) + { + return { ReplyType, std::move(eventId) }; + } + static EventRelation annotate(QString eventId, QString key) + { + return { AnnotationType, std::move(eventId), std::move(key) }; + } + static EventRelation replace(QString eventId) + { + return { ReplacementType, std::move(eventId) }; + } + + [[deprecated("Use ReplyType variable instead")]] + static constexpr auto Reply() { return ReplyType; } + [[deprecated("Use AnnotationType variable instead")]] // + static constexpr auto Annotation() { return AnnotationType; } + [[deprecated("Use ReplacementType variable instead")]] // + static constexpr auto Replacement() { return ReplacementType; } +}; + +template <> +struct QUOTIENT_API JsonObjectConverter<EventRelation> { + static void dumpTo(QJsonObject& jo, const EventRelation& pod); + static void fillFrom(const QJsonObject& jo, EventRelation& pod); +}; + +} + diff --git a/lib/events/filesourceinfo.cpp b/lib/events/filesourceinfo.cpp new file mode 100644 index 00000000..a60d86d2 --- /dev/null +++ b/lib/events/filesourceinfo.cpp @@ -0,0 +1,163 @@ +// 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 + auto k = RandomBuffer(32); + auto kBase64 = k.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals); + auto iv = RandomBuffer(16); + JWK key = { + "oct"_ls, { "encrypt"_ls, "decrypt"_ls }, "A256CTR"_ls, kBase64, true + }; + + int length = -1; + auto* ctx = EVP_CIPHER_CTX_new(); + EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, k.bytes(), iv.bytes()); + 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(QByteArray::OmitTrailingEquals); + auto ivBase64 = iv.toBase64(QByteArray::OmitTrailingEquals); + EncryptedFileMetadata efm = { + {}, key, ivBase64, { { QStringLiteral("sha256"), hash } }, "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.h b/lib/events/keyverificationevent.h new file mode 100644 index 00000000..80aebcf3 --- /dev/null +++ b/lib/events/keyverificationevent.h @@ -0,0 +1,258 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "event.h" + +namespace Quotient { + +static constexpr auto SasV1Method = "m.sas.v1"_ls; + +class QUOTIENT_API KeyVerificationEvent : public Event { +public: + QUO_BASE_EVENT(KeyVerificationEvent, "m.key.*"_ls, Event::BaseMetaType) + + using Event::Event; + + /// An opaque identifier for the verification request. Must + /// be unique with respect to the devices involved. + QUO_CONTENT_GETTER(QString, transactionId) +}; + +/// Requests a key verification with another user's devices. +/// Typically sent as a to-device event. +class QUOTIENT_API KeyVerificationRequestEvent : public KeyVerificationEvent { +public: + QUO_EVENT(KeyVerificationRequestEvent, "m.key.verification.request") + + using KeyVerificationEvent::KeyVerificationEvent; + KeyVerificationRequestEvent(const QString& transactionId, + const QString& fromDevice, + const QStringList& methods, + const QDateTime& timestamp) + : KeyVerificationRequestEvent( + basicJson(TypeId, { { "transaction_id"_ls, transactionId }, + { "from_device"_ls, fromDevice }, + { "methods"_ls, toJson(methods) }, + { "timestamp"_ls, toJson(timestamp) } })) + {} + + /// The device ID which is initiating the request. + QUO_CONTENT_GETTER(QString, fromDevice) + + /// The verification methods supported by the sender. + 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. + QUO_CONTENT_GETTER(QDateTime, timestamp) +}; + +class QUOTIENT_API KeyVerificationReadyEvent : public KeyVerificationEvent { +public: + QUO_EVENT(KeyVerificationReadyEvent, "m.key.verification.ready") + + using KeyVerificationEvent::KeyVerificationEvent; + KeyVerificationReadyEvent(const QString& transactionId, + const QString& fromDevice, + const QStringList& methods) + : KeyVerificationReadyEvent( + basicJson(TypeId, { { "transaction_id"_ls, transactionId }, + { "from_device"_ls, fromDevice }, + { "methods"_ls, toJson(methods) } })) + {} + + /// The device ID which is accepting the request. + QUO_CONTENT_GETTER(QString, fromDevice) + + /// The verification methods supported by the sender. + QUO_CONTENT_GETTER(QStringList, methods) +}; + +/// Begins a key verification process. +class QUOTIENT_API KeyVerificationStartEvent : public KeyVerificationEvent { +public: + QUO_EVENT(KeyVerificationStartEvent, "m.key.verification.start") + + using KeyVerificationEvent::KeyVerificationEvent; + KeyVerificationStartEvent(const QString& transactionId, + const QString& fromDevice) + : KeyVerificationStartEvent( + basicJson(TypeId, { { "transaction_id"_ls, transactionId }, + { "from_device"_ls, fromDevice }, + { "method"_ls, SasV1Method }, + { "hashes"_ls, QJsonArray{ "sha256"_ls } }, + { "key_agreement_protocols"_ls, + QJsonArray{ "curve25519-hkdf-sha256"_ls } }, + { "message_authentication_codes"_ls, + QJsonArray{ "hkdf-hmac-sha256"_ls } }, + { "short_authentication_string"_ls, + QJsonArray{ "decimal"_ls, "emoji"_ls } } })) + {} + + /// The device ID which is initiating the process. + QUO_CONTENT_GETTER(QString, fromDevice) + + /// The verification method to use. + QUO_CONTENT_GETTER(QString, method) + + /// Optional method to use to verify the other user's key with. + 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 + { + 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 + { + 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 + { + 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 + { + Q_ASSERT(method() == SasV1Method); + return contentPart<QString>("short_authentification_string"_ls); + } +}; + +/// Accepts a previously sent m.key.verification.start message. +/// Typically sent as a to-device event. +class QUOTIENT_API KeyVerificationAcceptEvent : public KeyVerificationEvent { +public: + QUO_EVENT(KeyVerificationAcceptEvent, "m.key.verification.accept") + + using KeyVerificationEvent::KeyVerificationEvent; + KeyVerificationAcceptEvent(const QString& transactionId, + const QString& commitment) + : KeyVerificationAcceptEvent(basicJson( + TypeId, { { "transaction_id"_ls, transactionId }, + { "method"_ls, SasV1Method }, + { "key_agreement_protocol"_ls, "curve25519-hkdf-sha256" }, + { "hash"_ls, "sha256" }, + { "message_authentication_code"_ls, "hkdf-hmac-sha256" }, + { "short_authentication_string"_ls, + QJsonArray{ "decimal"_ls, "emoji"_ls, } }, + { "commitment"_ls, commitment } })) + {} + + /// The verification method to use. Must be 'm.sas.v1'. + 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. + 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. + QUO_CONTENT_GETTER_X(QString, hashData, "hash"_ls) + + /// The message authentication code the device is choosing to use, out + /// of the options in the m.key.verification.start message. + QUO_CONTENT_GETTER(QString, messageAuthenticationCode) + + /// The SAS methods both devices involved in the verification process understand. + 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. + QUO_CONTENT_GETTER(QString, commitment) +}; + +class QUOTIENT_API KeyVerificationCancelEvent : public KeyVerificationEvent { +public: + QUO_EVENT(KeyVerificationCancelEvent, "m.key.verification.cancel") + + using KeyVerificationEvent::KeyVerificationEvent; + KeyVerificationCancelEvent(const QString& transactionId, + const QString& reason) + : KeyVerificationCancelEvent( + basicJson(TypeId, { + { "transaction_id"_ls, transactionId }, + { "reason"_ls, reason }, + { "code"_ls, reason } // Not a typo + })) + {} + + /// A human readable description of the code. The client should only + /// rely on this string if it does not understand the code. + QUO_CONTENT_GETTER(QString, reason) + + /// The error code for why the process/request was cancelled by the user. + QUO_CONTENT_GETTER(QString, code) +}; + +/// Sends the ephemeral public key for a device to the partner device. +/// Typically sent as a to-device event. +class QUOTIENT_API KeyVerificationKeyEvent : public KeyVerificationEvent { +public: + QUO_EVENT(KeyVerificationKeyEvent, "m.key.verification.key") + + using KeyVerificationEvent::KeyVerificationEvent; + KeyVerificationKeyEvent(const QString& transactionId, const QString& key) + : KeyVerificationKeyEvent( + basicJson(TypeId, { { "transaction_id"_ls, transactionId }, + { "key"_ls, key } })) + {} + + /// The device's ephemeral public key, encoded as unpadded base64. + QUO_CONTENT_GETTER(QString, key) +}; + +/// Sends the MAC of a device's key to the partner device. +class QUOTIENT_API KeyVerificationMacEvent : public KeyVerificationEvent { +public: + QUO_EVENT(KeyVerificationMacEvent, "m.key.verification.mac") + + using KeyVerificationEvent::KeyVerificationEvent; + KeyVerificationMacEvent(const QString& transactionId, const QString& keys, + const QJsonObject& mac) + : KeyVerificationMacEvent( + basicJson(TypeId, { { "transaction_id"_ls, transactionId }, + { "keys"_ls, keys }, + { "mac"_ls, mac } })) + {} + + /// The device's ephemeral public key, encoded as unpadded base64. + QUO_CONTENT_GETTER(QString, keys) + + QHash<QString, QString> mac() const + { + return contentPart<QHash<QString, QString>>("mac"_ls); + } +}; + +class QUOTIENT_API KeyVerificationDoneEvent : public KeyVerificationEvent { +public: + QUO_EVENT(KeyVerificationDoneEvent, "m.key.verification.done") + + using KeyVerificationEvent::KeyVerificationEvent; + explicit KeyVerificationDoneEvent(const QString& transactionId) + : KeyVerificationDoneEvent( + basicJson(TypeId, { { "transaction_id"_ls, transactionId } })) + {} +}; +} // namespace Quotient diff --git a/lib/events/reactionevent.cpp b/lib/events/reactionevent.cpp deleted file mode 100644 index 003c8ead..00000000 --- a/lib/events/reactionevent.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2019 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "reactionevent.h" - -using namespace Quotient; - -void JsonObjectConverter<EventRelation>::dumpTo( - QJsonObject& jo, const EventRelation& pod) -{ - if (pod.type.isEmpty()) { - qCWarning(MAIN) << "Empty relation type; won't dump to JSON"; - return; - } - jo.insert(QStringLiteral("rel_type"), pod.type); - jo.insert(EventIdKey, pod.eventId); - if (pod.type == EventRelation::Annotation()) - jo.insert(QStringLiteral("key"), pod.key); -} - -void JsonObjectConverter<EventRelation>::fillFrom( - const QJsonObject& jo, EventRelation& pod) -{ - // The experimental logic for generic relationships (MSC1849) - fromJson(jo["rel_type"_ls], pod.type); - fromJson(jo[EventIdKeyL], pod.eventId); - if (pod.type == EventRelation::Annotation()) - fromJson(jo["key"_ls], pod.key); -} diff --git a/lib/events/reactionevent.h b/lib/events/reactionevent.h index 75c6528c..8d873441 100644 --- a/lib/events/reactionevent.h +++ b/lib/events/reactionevent.h @@ -1,73 +1,14 @@ -/****************************************************************************** - * Copyright (C) 2019 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2019 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include "roomevent.h" +#include "eventrelation.h" namespace Quotient { -struct EventRelation { - using reltypeid_t = const char*; - static constexpr reltypeid_t Reply() { return "m.in_reply_to"; } - static constexpr reltypeid_t Annotation() { return "m.annotation"; } - static constexpr reltypeid_t Replacement() { return "m.replace"; } - - QString type; - QString eventId; - QString key = {}; // Only used for m.annotation for now - - static EventRelation replyTo(QString eventId) - { - return { Reply(), std::move(eventId) }; - } - static EventRelation annotate(QString eventId, QString key) - { - return { Annotation(), std::move(eventId), std::move(key) }; - } - static EventRelation replace(QString eventId) - { - return { Replacement(), std::move(eventId) }; - } -}; -template <> -struct JsonObjectConverter<EventRelation> { - static void dumpTo(QJsonObject& jo, const EventRelation& pod); - static void fillFrom(const QJsonObject& jo, EventRelation& pod); -}; - -class ReactionEvent : public RoomEvent { -public: - DEFINE_EVENT_TYPEID("m.reaction", ReactionEvent) - - explicit ReactionEvent(const EventRelation& value) - : RoomEvent(typeId(), matrixTypeId(), - { { QStringLiteral("m.relates_to"), toJson(value) } }) - {} - explicit ReactionEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj) {} - EventRelation relation() const - { - return content<EventRelation>(QStringLiteral("m.relates_to")); - } - -private: - EventRelation _relation; -}; -REGISTER_EVENT_TYPE(ReactionEvent) +DEFINE_SIMPLE_EVENT(ReactionEvent, RoomEvent, "m.reaction", EventRelation, + relation, "m.relates_to") } // namespace Quotient diff --git a/lib/events/receiptevent.cpp b/lib/events/receiptevent.cpp index bf050cb2..d8f9fa0b 100644 --- a/lib/events/receiptevent.cpp +++ b/lib/events/receiptevent.cpp @@ -1,20 +1,5 @@ -/****************************************************************************** - * Copyright (C) 2016 Felix Rohrbach <kde@fxrh.de> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later /* Example of a Receipt Event: @@ -35,31 +20,49 @@ Example of a Receipt Event: #include "receiptevent.h" -#include "converters.h" #include "logging.h" using namespace Quotient; -ReceiptEvent::ReceiptEvent(const QJsonObject& obj) : Event(typeId(), obj) +// The library loads the event-ids-to-receipts JSON map into a vector because +// map lookups are not used and vectors are massively faster. Same goes for +// de-/serialization of ReceiptsForEvent::receipts. +// (XXX: would this be generally preferred across CS API JSON maps?..) +QJsonObject Quotient::toJson(const EventsWithReceipts& ewrs) { - const auto& contents = contentJson(); - _eventsWithReceipts.reserve(contents.size()); - for (auto eventIt = contents.begin(); eventIt != contents.end(); ++eventIt) { + QJsonObject json; + for (const auto& e : ewrs) { + QJsonObject receiptsJson; + for (const auto& r : e.receipts) + receiptsJson.insert(r.userId, + QJsonObject { { "ts"_ls, toJson(r.timestamp) } }); + json.insert(e.evtId, QJsonObject { { "m.read"_ls, receiptsJson } }); + } + return json; +} + +template<> +EventsWithReceipts Quotient::fromJson(const QJsonObject& json) +{ + EventsWithReceipts result; + result.reserve(json.size()); + for (auto eventIt = json.begin(); eventIt != json.end(); ++eventIt) { if (eventIt.key().isEmpty()) { qCWarning(EPHEMERAL) << "ReceiptEvent has an empty event id, skipping"; - qCDebug(EPHEMERAL) << "ReceiptEvent content follows:\n" << contents; + qCDebug(EPHEMERAL) << "ReceiptEvent content follows:\n" << json; continue; } - const QJsonObject reads = + const auto reads = eventIt.value().toObject().value("m.read"_ls).toObject(); - QVector<Receipt> receipts; - receipts.reserve(reads.size()); + QVector<UserTimestamp> usersAtEvent; + usersAtEvent.reserve(reads.size()); for (auto userIt = reads.begin(); userIt != reads.end(); ++userIt) { - const QJsonObject user = userIt.value().toObject(); - receipts.push_back( + const auto user = userIt.value().toObject(); + usersAtEvent.push_back( { userIt.key(), fromJson<QDateTime>(user["ts"_ls]) }); } - _eventsWithReceipts.push_back({ eventIt.key(), std::move(receipts) }); + result.push_back({ eventIt.key(), std::move(usersAtEvent) }); } + return result; } diff --git a/lib/events/receiptevent.h b/lib/events/receiptevent.h index dd54a476..b87e00f6 100644 --- a/lib/events/receiptevent.h +++ b/lib/events/receiptevent.h @@ -1,20 +1,5 @@ -/****************************************************************************** - * Copyright (C) 2016 Felix Rohrbach <kde@fxrh.de> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once @@ -24,28 +9,27 @@ #include <QtCore/QVector> namespace Quotient { -struct Receipt { +struct UserTimestamp { QString userId; QDateTime timestamp; }; struct ReceiptsForEvent { QString evtId; - QVector<Receipt> receipts; + QVector<UserTimestamp> receipts; }; using EventsWithReceipts = QVector<ReceiptsForEvent>; -class ReceiptEvent : public Event { -public: - DEFINE_EVENT_TYPEID("m.receipt", ReceiptEvent) - explicit ReceiptEvent(const QJsonObject& obj); +template <> +QUOTIENT_API EventsWithReceipts fromJson(const QJsonObject& json); +QUOTIENT_API QJsonObject toJson(const EventsWithReceipts& ewrs); - const EventsWithReceipts& eventsWithReceipts() const - { - return _eventsWithReceipts; - } +class QUOTIENT_API ReceiptEvent + : public EventTemplate<ReceiptEvent, Event, EventsWithReceipts> { +public: + QUO_EVENT(ReceiptEvent, "m.receipt") + using EventTemplate::EventTemplate; -private: - EventsWithReceipts _eventsWithReceipts; + [[deprecated("Use content() instead")]] + EventsWithReceipts eventsWithReceipts() const { return content(); } }; -REGISTER_EVENT_TYPE(ReceiptEvent) } // namespace Quotient diff --git a/lib/events/redactionevent.cpp b/lib/events/redactionevent.cpp deleted file mode 100644 index bf467718..00000000 --- a/lib/events/redactionevent.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "redactionevent.h" diff --git a/lib/events/redactionevent.h b/lib/events/redactionevent.h index 3b3af18e..a2e0b73b 100644 --- a/lib/events/redactionevent.h +++ b/lib/events/redactionevent.h @@ -1,38 +1,21 @@ -/****************************************************************************** - * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include "roomevent.h" namespace Quotient { -class RedactionEvent : public RoomEvent { +class QUOTIENT_API RedactionEvent : public RoomEvent { public: - DEFINE_EVENT_TYPEID("m.room.redaction", RedactionEvent) + QUO_EVENT(RedactionEvent, "m.room.redaction") - explicit RedactionEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj) - {} + using RoomEvent::RoomEvent; QString redactedEvent() const { return fullJson()["redacts"_ls].toString(); } - QString reason() const { return contentJson()["reason"_ls].toString(); } + QUO_CONTENT_GETTER(QString, reason) }; -REGISTER_EVENT_TYPE(RedactionEvent) } // namespace Quotient diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index c2100eaa..1986f852 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -1,20 +1,5 @@ -/****************************************************************************** - * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once @@ -22,28 +7,17 @@ #include "stateevent.h" namespace Quotient { -class RoomAvatarEvent : public StateEvent<EventContent::ImageContent> { +class QUOTIENT_API RoomAvatarEvent + : public KeylessStateEventBase<RoomAvatarEvent, + EventContent::ImageContent> { // It's a bit of an overkill to use a full-fledged ImageContent // because in reality m.room.avatar usually only has a single URL, // without a thumbnail. But The Spec says there be thumbnails, and - // we follow The Spec. + // we follow The Spec (and ImageContent is very convenient to reuse here). public: - DEFINE_EVENT_TYPEID("m.room.avatar", RoomAvatarEvent) - explicit RoomAvatarEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) - {} - explicit RoomAvatarEvent(const EventContent::ImageContent& avatar) - : StateEvent(typeId(), matrixTypeId(), QString(), avatar) - {} - // A replica of EventContent::ImageInfo constructor - explicit RoomAvatarEvent(const QUrl& u, qint64 fileSize = -1, - QMimeType mimeType = {}, - const QSize& imageSize = {}, - const QString& originalFilename = {}) - : RoomAvatarEvent(EventContent::ImageContent { - u, fileSize, mimeType, imageSize, originalFilename }) - {} + QUO_EVENT(RoomAvatarEvent, "m.room.avatar") + using KeylessStateEventBase::KeylessStateEventBase; - 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 fadfece0..c73bc92a 100644 --- a/lib/events/roomcanonicalaliasevent.h +++ b/lib/events/roomcanonicalaliasevent.h @@ -1,78 +1,44 @@ -/****************************************************************************** - * Copyright (C) 2020 QMatrixClient project - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2020 Ram Nad <ramnad1999@gmail.com> +// SPDX-FileCopyrightText: 2020 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #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 - : public StateEvent<EventContent::AliasesEventContent> { +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 KeylessStateEventBase<RoomCanonicalAliasEvent, + EventContent::AliasesEventContent> { public: - DEFINE_EVENT_TYPEID("m.room.canonical_alias", RoomCanonicalAliasEvent) - - explicit RoomCanonicalAliasEvent(const QJsonObject& obj) - : StateEvent(typeId(), obj) - { } - - explicit RoomCanonicalAliasEvent(const QString& canonicalAlias, - const QStringList& altAliases = {}) - : StateEvent(typeId(), matrixTypeId(), QString(), - canonicalAlias, altAliases) - { } - - explicit RoomCanonicalAliasEvent(QString&& canonicalAlias, - QStringList&& altAliases = {}) - : StateEvent(typeId(), matrixTypeId(), QString(), - std::move(canonicalAlias), std::move(altAliases)) - { } + QUO_EVENT(RoomCanonicalAliasEvent, "m.room.canonical_alias") + using KeylessStateEventBase::KeylessStateEventBase; QString alias() const { return content().canonicalAlias; } QStringList altAliases() const { return content().altAliases; } }; -REGISTER_EVENT_TYPE(RoomCanonicalAliasEvent) } // namespace Quotient diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp index c72b5bc2..3b5024d5 100644 --- a/lib/events/roomcreateevent.cpp +++ b/lib/events/roomcreateevent.cpp @@ -1,43 +1,40 @@ -/****************************************************************************** - * Copyright (C) 2019 QMatrixClient project - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2019 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #include "roomcreateevent.h" using namespace Quotient; +template <> +RoomType Quotient::fromJson(const QJsonValue& jv) +{ + return enumFromJsonString(jv.toString(), RoomTypeStrings, + RoomType::Undefined); +} + bool RoomCreateEvent::isFederated() const { - return fromJson<bool>(contentJson()["m.federate"_ls]); + return contentPart<bool>("m.federate"_ls); } QString RoomCreateEvent::version() const { - return fromJson<QString>(contentJson()["room_version"_ls]); + return contentPart<QString>("room_version"_ls); } RoomCreateEvent::Predecessor RoomCreateEvent::predecessor() const { - const auto predJson = contentJson()["predecessor"_ls].toObject(); - return { fromJson<QString>(predJson["room_id"_ls]), - fromJson<QString>(predJson["event_id"_ls]) }; + const auto predJson = contentPart<QJsonObject>("predecessor"_ls); + return { fromJson<QString>(predJson[RoomIdKeyL]), + fromJson<QString>(predJson[EventIdKeyL]) }; } bool RoomCreateEvent::isUpgrade() const { return contentJson().contains("predecessor"_ls); } + +RoomType RoomCreateEvent::roomType() const +{ + return contentPart<RoomType>("type"_ls); +} diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h index 91aefe9e..5968e187 100644 --- a/lib/events/roomcreateevent.h +++ b/lib/events/roomcreateevent.h @@ -1,34 +1,17 @@ -/****************************************************************************** - * Copyright (C) 2019 QMatrixClient project - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2019 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include "stateevent.h" +#include "quotient_common.h" namespace Quotient { -class RoomCreateEvent : public StateEventBase { +class QUOTIENT_API RoomCreateEvent : public StateEvent { public: - DEFINE_EVENT_TYPEID("m.room.create", RoomCreateEvent) + QUO_EVENT(RoomCreateEvent, "m.room.create") - explicit RoomCreateEvent() : StateEventBase(typeId(), matrixTypeId()) {} - explicit RoomCreateEvent(const QJsonObject& obj) - : StateEventBase(typeId(), obj) - {} + using StateEvent::StateEvent; struct Predecessor { QString roomId; @@ -39,6 +22,6 @@ public: QString version() const; Predecessor predecessor() const; bool isUpgrade() const; + RoomType roomType() const; }; -REGISTER_EVENT_TYPE(RoomCreateEvent) } // namespace Quotient diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index a59cd6e0..e98cb591 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -1,43 +1,18 @@ -/****************************************************************************** - * Copyright (C) 2018 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #include "roomevent.h" -#include "converters.h" #include "logging.h" #include "redactionevent.h" using namespace Quotient; -[[maybe_unused]] static auto roomEventTypeInitialised = - Event::factory_t::chainFactory<RoomEvent>(); - -RoomEvent::RoomEvent(Type type, event_mtype_t matrixType, - const QJsonObject& contentJson) - : Event(type, matrixType, contentJson) -{} - -RoomEvent::RoomEvent(Type type, const QJsonObject& json) : Event(type, json) +RoomEvent::RoomEvent(const QJsonObject& json) : Event(json) { - const auto unsignedData = json[UnsignedKeyL].toObject(); - const auto redaction = unsignedData[RedactedCauseKeyL]; - if (redaction.isObject()) - _redactedBecause = makeEvent<RedactionEvent>(redaction.toObject()); + if (const auto redaction = unsignedPart<QJsonObject>(RedactedCauseKeyL); + !redaction.isEmpty()) + _redactedBecause = loadEvent<RedactionEvent>(redaction); } RoomEvent::~RoomEvent() = default; // Let the smart pointer do its job @@ -51,24 +26,24 @@ QDateTime RoomEvent::originTimestamp() const QString RoomEvent::roomId() const { - return fullJson()["room_id"_ls].toString(); + return fullJson()[RoomIdKeyL].toString(); } QString RoomEvent::senderId() const { - return fullJson()["sender"_ls].toString(); + return fullJson()[SenderKeyL].toString(); } bool RoomEvent::isReplaced() const { - return unsignedJson()["m.relations"_ls].toObject().contains("m.replace"); + return unsignedPart<QJsonObject>("m.relations"_ls).contains("m.replace"); } QString RoomEvent::replacedBy() const { // clang-format off - return unsignedJson()["m.relations"_ls].toObject() - .value("m.replace").toObject() + return unsignedPart<QJsonObject>("m.relations"_ls) + .value("m.replace"_ls).toObject() .value(EventIdKeyL).toString(); // clang-format on } @@ -80,7 +55,7 @@ QString RoomEvent::redactionReason() const QString RoomEvent::transactionId() const { - return unsignedJson()["transaction_id"_ls].toString(); + return unsignedPart<QString>("transaction_id"_ls); } QString RoomEvent::stateKey() const @@ -90,12 +65,12 @@ QString RoomEvent::stateKey() const void RoomEvent::setRoomId(const QString& roomId) { - editJson().insert(QStringLiteral("room_id"), roomId); + editJson().insert(RoomIdKey, roomId); } void RoomEvent::setSender(const QString& senderId) { - editJson().insert(QStringLiteral("sender"), senderId); + editJson().insert(SenderKey, senderId); } void RoomEvent::setTransactionId(const QString& txnId) @@ -115,24 +90,23 @@ void RoomEvent::addId(const QString& newId) Q_ASSERT(id() == newId); } -QJsonObject makeCallContentJson(const QString& callId, int version, - QJsonObject content) +void RoomEvent::dumpTo(QDebug dbg) const { - content.insert(QStringLiteral("call_id"), callId); - content.insert(QStringLiteral("version"), version); - return content; + Event::dumpTo(dbg); + dbg << " (made at " << originTimestamp().toString(Qt::ISODate) << ')'; } -CallEventBase::CallEventBase(Type type, event_mtype_t matrixType, - const QString& callId, int version, - const QJsonObject& contentJson) - : RoomEvent(type, matrixType, - makeCallContentJson(callId, version, contentJson)) -{} +#ifdef Quotient_E2EE_ENABLED +void RoomEvent::setOriginalEvent(event_ptr_tt<RoomEvent>&& originalEvent) +{ + _originalEvent = std::move(originalEvent); +} -CallEventBase::CallEventBase(Event::Type type, const QJsonObject& json) - : RoomEvent(type, json) +const QJsonObject RoomEvent::encryptedJson() const { - if (callId().isEmpty()) - qCWarning(EVENTS) << id() << "is a call event with an empty call id"; + if(!_originalEvent) { + return {}; + } + return _originalEvent->fullJson(); } +#endif diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 621652cb..203434f6 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -1,20 +1,5 @@ -/****************************************************************************** - * Copyright (C) 2018 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once @@ -25,33 +10,22 @@ namespace Quotient { class RedactionEvent; -/** This class corresponds to m.room.* events */ -class RoomEvent : public Event { - Q_GADGET - Q_PROPERTY(QString id READ id) - Q_PROPERTY(QDateTime timestamp READ timestamp CONSTANT) - Q_PROPERTY(QString roomId READ roomId CONSTANT) - Q_PROPERTY(QString senderId READ senderId CONSTANT) - Q_PROPERTY(QString redactionReason READ redactionReason) - Q_PROPERTY(bool isRedacted READ isRedacted) - Q_PROPERTY(QString transactionId READ transactionId WRITE setTransactionId) +// That check could look into Event and find most stuff already deleted... +// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) +class QUOTIENT_API RoomEvent : public Event { public: - using factory_t = EventFactory<RoomEvent>; + QUO_BASE_EVENT(RoomEvent, {}, Event::BaseMetaType) - // RedactionEvent is an incomplete type here so we cannot inline - // constructors and destructors and we cannot use 'using'. - RoomEvent(Type type, event_mtype_t matrixType, - const QJsonObject& contentJson = {}); - RoomEvent(Type type, const QJsonObject& json); - ~RoomEvent() override; + ~RoomEvent() override; // Don't inline this - see the private section QString id() const; QDateTime originTimestamp() const; - [[deprecated("Use originTimestamp()")]] QDateTime timestamp() const { - return originTimestamp(); - } QString roomId() const; QString senderId() const; + //! \brief Determine whether the event has been replaced + //! + //! \return true if this event has been overridden by another event + //! with `"rel_type": "m.replace"`; false otherwise bool isReplaced() const; QString replacedBy() const; bool isRedacted() const { return bool(_redactedBecause); } @@ -63,48 +37,48 @@ public: QString transactionId() const; QString stateKey() const; + //! \brief Fill the pending event object with the room id void setRoomId(const QString& roomId); + //! \brief Fill the pending event object with the sender id void setSender(const QString& senderId); - - /** - * Sets the transaction id for locally created events. This should be - * done before the event is exposed to any code using the respective - * Q_PROPERTY. - * - * \param txnId - transaction id, normally obtained from - * Connection::generateTxnId() - */ + //! \brief Fill the pending event object with the transaction id + //! \param txnId - transaction id, normally obtained from + //! Connection::generateTxnId() void setTransactionId(const QString& txnId); - /** - * Sets event id for locally created events - * - * When a new event is created locally, it has no server id yet. - * This function allows to add the id once the confirmation from - * the server is received. There should be no id set previously - * in the event. It's the responsibility of the code calling addId() - * to notify clients that use Q_PROPERTY(id) about its change - */ + //! \brief Add an event id to locally created events after they are sent + //! + //! When a new event is created locally, it has no id; the homeserver + //! assigns it once the event is sent. This function allows to add the id + //! once the confirmation from the server is received. There should be no id + //! set previously in the event. It's the responsibility of the code calling + //! addId() to notify clients about the change; there's no signal or + //! callback for that in RoomEvent. void addId(const QString& newId); +#ifdef Quotient_E2EE_ENABLED + void setOriginalEvent(event_ptr_tt<RoomEvent>&& originalEvent); + const RoomEvent* originalEvent() const { return _originalEvent.get(); } + const QJsonObject encryptedJson() const; +#endif + +protected: + explicit RoomEvent(const QJsonObject& json); + void dumpTo(QDebug dbg) const override; + private: + // RedactionEvent is an incomplete type here so we cannot inline + // constructors using it and also destructors (with 'using', in particular). event_ptr_tt<RedactionEvent> _redactedBecause; + +#ifdef Quotient_E2EE_ENABLED + event_ptr_tt<RoomEvent> _originalEvent; +#endif }; using RoomEventPtr = event_ptr_tt<RoomEvent>; using RoomEvents = EventsArray<RoomEvent>; using RoomEventsRange = Range<RoomEvents>; -class CallEventBase : public RoomEvent { -public: - CallEventBase(Type type, event_mtype_t matrixType, const QString& callId, - int version, const QJsonObject& contentJson = {}); - CallEventBase(Type type, const QJsonObject& json); - ~CallEventBase() override = default; - bool isCallEvent() const override { return true; } - - QString callId() const { return content<QString>("call_id"_ls); } - int version() const { return content<int>("version"_ls); } -}; } // namespace Quotient Q_DECLARE_METATYPE(Quotient::RoomEvent*) Q_DECLARE_METATYPE(const Quotient::RoomEvent*) diff --git a/lib/events/roomkeyevent.cpp b/lib/events/roomkeyevent.cpp deleted file mode 100644 index 66580430..00000000 --- a/lib/events/roomkeyevent.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "roomkeyevent.h" - -using namespace Quotient; - -RoomKeyEvent::RoomKeyEvent(const QJsonObject &obj) : Event(typeId(), obj) -{ - if (roomId().isEmpty()) - qCWarning(E2EE) << "Room key event has empty room id"; -} diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index 679cbf7c..dad5df8b 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -1,19 +1,33 @@ +// SPDX-FileCopyrightText: 2019 Alexey Andreyev <aa13q@ya.ru> +// SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once #include "event.h" namespace Quotient { -class RoomKeyEvent : public Event +class QUOTIENT_API RoomKeyEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.room_key", RoomKeyEvent) + QUO_EVENT(RoomKeyEvent, "m.room_key") - RoomKeyEvent(const QJsonObject& obj); + using Event::Event; + explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, + const QString& sessionId, const QString& sessionKey) + : Event(basicJson(TypeId, { + { "algorithm", algorithm }, + { "room_id", roomId }, + { "session_id", sessionId }, + { "session_key", sessionKey }, + })) + {} - QString algorithm() const { return content<QString>("algorithm"_ls); } - QString roomId() const { return content<QString>("room_id"_ls); } - QString sessionId() const { return content<QString>("session_id"_ls); } - QString sessionKey() const { return content<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 3193a54d..4e7eae1b 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -1,47 +1,20 @@ -/****************************************************************************** - * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-FileCopyrightText: 2019 Karol Kosek <krkkx@protonmail.com> +// SPDX-License-Identifier: LGPL-2.1-or-later #include "roommemberevent.h" - -#include "converters.h" #include "logging.h" -#include <array> - -static const std::array<QString, 5> membershipStrings = { - { QStringLiteral("invite"), QStringLiteral("join"), QStringLiteral("knock"), - QStringLiteral("leave"), QStringLiteral("ban") } -}; - namespace Quotient { template <> -struct JsonConverter<MembershipType> { - static MembershipType load(const QJsonValue& jv) +struct JsonConverter<Membership> { + static Membership load(const QJsonValue& jv) { - const auto& membershipString = jv.toString(); - for (auto it = membershipStrings.begin(); it != membershipStrings.end(); - ++it) - if (membershipString == *it) - return MembershipType(it - membershipStrings.begin()); + if (const auto& ms = jv.toString(); !ms.isEmpty()) + return flagFromJsonString<Membership>(ms, MembershipStrings); - if (!membershipString.isEmpty()) - qCWarning(EVENTS) << "Unknown MembershipType: " << membershipString; - return MembershipType::Undefined; + qCWarning(EVENTS) << "Empty membership state"; + return Membership::Invalid; } }; } // namespace Quotient @@ -49,25 +22,29 @@ struct JsonConverter<MembershipType> { using namespace Quotient; MemberEventContent::MemberEventContent(const QJsonObject& json) - : membership(fromJson<MembershipType>(json["membership"_ls])) + : membership(fromJson<Membership>(json["membership"_ls])) , isDirect(json["is_direct"_ls].toBool()) - , displayName(sanitized(json["displayname"_ls].toString())) - , avatarUrl(json["avatar_url"_ls].toString()) + , displayName(fromJson<Omittable<QString>>(json["displayname"_ls])) + , avatarUrl(fromJson<Omittable<QString>>(json["avatar_url"_ls])) , reason(json["reason"_ls].toString()) -{} +{ + if (displayName) + displayName = sanitized(*displayName); +} -void MemberEventContent::fillJson(QJsonObject* o) const +QJsonObject MemberEventContent::toJson() const { - Q_ASSERT(o); - Q_ASSERT_X(membership != MembershipType::Undefined, __FUNCTION__, - "The key 'membership' must be explicit in MemberEventContent"); - if (membership != MembershipType::Undefined) - o->insert(QStringLiteral("membership"), membershipStrings[membership]); - o->insert(QStringLiteral("displayname"), displayName); - if (avatarUrl.isValid()) - o->insert(QStringLiteral("avatar_url"), avatarUrl.toString()); + QJsonObject o; + if (membership != Membership::Invalid) + o.insert(QStringLiteral("membership"), + flagToJsonString(membership, MembershipStrings)); + if (displayName) + o.insert(QStringLiteral("displayname"), *displayName); + if (avatarUrl && avatarUrl->isValid()) + 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 @@ -77,47 +54,49 @@ bool RoomMemberEvent::changesMembership() const bool RoomMemberEvent::isInvite() const { - return membership() == MembershipType::Invite && changesMembership(); + return membership() == Membership::Invite && changesMembership(); } bool RoomMemberEvent::isRejectedInvite() const { - return membership() == MembershipType::Leave && prevContent() - && prevContent()->membership == MembershipType::Invite; + return membership() == Membership::Leave && prevContent() + && prevContent()->membership == Membership::Invite; } bool RoomMemberEvent::isJoin() const { - return membership() == MembershipType::Join && changesMembership(); + return membership() == Membership::Join && changesMembership(); } bool RoomMemberEvent::isLeave() const { - return membership() == MembershipType::Leave && prevContent() + return membership() == Membership::Leave && prevContent() && prevContent()->membership != membership() - && prevContent()->membership != MembershipType::Ban - && prevContent()->membership != MembershipType::Invite; + && prevContent()->membership != Membership::Ban + && prevContent()->membership != Membership::Invite; } bool RoomMemberEvent::isBan() const { - return membership() == MembershipType::Ban && changesMembership(); + return membership() == Membership::Ban && changesMembership(); } bool RoomMemberEvent::isUnban() const { - return membership() == MembershipType::Leave && prevContent() - && prevContent()->membership == MembershipType::Ban; + return membership() == Membership::Leave && prevContent() + && prevContent()->membership == Membership::Ban; } bool RoomMemberEvent::isRename() const { - auto prevName = prevContent() ? prevContent()->displayName : QString(); - return displayName() != prevName; + return prevContent() && prevContent()->displayName + ? newDisplayName() != *prevContent()->displayName + : newDisplayName().has_value(); } bool RoomMemberEvent::isAvatarUpdate() const { - auto prevAvatarUrl = prevContent() ? prevContent()->avatarUrl : QUrl(); - return avatarUrl() != prevAvatarUrl; + return prevContent() && prevContent()->avatarUrl + ? newAvatarUrl() != *prevContent()->avatarUrl + : newAvatarUrl().has_value(); } diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index 783b8207..9f063136 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -1,91 +1,57 @@ -/****************************************************************************** - * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2015 Felix Rohrbach <kde@fxrh.de> +// SPDX-FileCopyrightText: 2017 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-FileCopyrightText: 2019 Karol Kosek <krkkx@protonmail.com> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once -#include "eventcontent.h" #include "stateevent.h" +#include "quotient_common.h" namespace Quotient { -class MemberEventContent : public EventContent::Base { +class QUOTIENT_API MemberEventContent { public: - enum MembershipType : size_t { - Invite = 0, - Join, - Knock, - Leave, - Ban, - Undefined - }; + using MembershipType + [[deprecated("Use Quotient::Membership instead")]] = Membership; - explicit MemberEventContent(MembershipType mt = Join) : membership(mt) {} + QUO_IMPLICIT MemberEventContent(Membership ms) : membership(ms) {} explicit MemberEventContent(const QJsonObject& json); + QJsonObject toJson() const; - MembershipType membership; + Membership membership; + /// (Only for invites) Whether the invite is to a direct chat bool isDirect = false; - QString displayName; - QUrl avatarUrl; + Omittable<QString> displayName; + Omittable<QUrl> avatarUrl; QString reason; - -protected: - void fillJson(QJsonObject* o) const override; }; -using MembershipType = MemberEventContent::MembershipType; +using MembershipType [[deprecated("Use Membership instead")]] = Membership; -class RoomMemberEvent : public StateEvent<MemberEventContent> { +class QUOTIENT_API RoomMemberEvent + : public KeyedStateEventBase<RoomMemberEvent, MemberEventContent> { Q_GADGET public: - DEFINE_EVENT_TYPEID("m.room.member", RoomMemberEvent) - - using MembershipType = MemberEventContent::MembershipType; + QUO_EVENT(RoomMemberEvent, "m.room.member") - explicit RoomMemberEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) - {} - [[deprecated("Use RoomMemberEvent(userId, contentArgs) instead")]] - RoomMemberEvent(MemberEventContent&& c) - : StateEvent(typeId(), matrixTypeId(), QString(), c) - {} - template <typename... ArgTs> - RoomMemberEvent(const QString& userId, ArgTs&&... contentArgs) - : StateEvent(typeId(), matrixTypeId(), userId, - std::forward<ArgTs>(contentArgs)...) - {} + using MembershipType + [[deprecated("Use Quotient::Membership instead")]] = Membership; - /// A special constructor to create unknown RoomMemberEvents - /** - * This is needed in order to use RoomMemberEvent as a "base event - * class" in cases like GetMembersByRoomJob when RoomMemberEvents - * (rather than RoomEvents or StateEvents) are resolved from JSON. - * For such cases loadEvent<> requires an underlying class to be - * constructible with unknownTypeId() instead of its genuine id. - * Don't use it directly. - * \sa GetMembersByRoomJob, loadEvent, unknownTypeId - */ - RoomMemberEvent(Type type, const QJsonObject& fullJson) - : StateEvent(type, fullJson) - {} + using KeyedStateEventBase::KeyedStateEventBase; - MembershipType membership() const { return content().membership; } - QString userId() const { return fullJson()[StateKeyKeyL].toString(); } + Membership membership() const { return content().membership; } + QString userId() const { return stateKey(); } bool isDirect() const { return content().isDirect; } - QString displayName() const { return content().displayName; } - QUrl avatarUrl() const { return content().avatarUrl; } + Omittable<QString> newDisplayName() const { return content().displayName; } + Omittable<QUrl> newAvatarUrl() const { return content().avatarUrl; } + [[deprecated("Use newDisplayName() instead")]] QString displayName() const + { + return newDisplayName().value_or(QString()); + } + [[deprecated("Use newAvatarUrl() instead")]] QUrl avatarUrl() const + { + return newAvatarUrl().value_or(QUrl()); + } QString reason() const { return content().reason; } bool changesMembership() const; bool isBan() const; @@ -96,20 +62,5 @@ public: bool isLeave() const; bool isRename() const; bool isAvatarUpdate() const; - -private: - Q_ENUM(MembershipType) }; - -template <> -class EventFactory<RoomMemberEvent> { -public: - static event_ptr_tt<RoomMemberEvent> make(const QJsonObject& json, - const QString&) - { - return makeEvent<RoomMemberEvent>(json); - } -}; - -REGISTER_EVENT_TYPE(RoomMemberEvent) } // namespace Quotient diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index de499e7c..df4840b3 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -1,44 +1,33 @@ -/****************************************************************************** - * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2015 Felix Rohrbach <kde@fxrh.de> +// SPDX-FileCopyrightText: 2016 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-FileCopyrightText: 2017 Roman Plášil <me@rplasil.name> +// SPDX-License-Identifier: LGPL-2.1-or-later #include "roommessageevent.h" #include "logging.h" +#include "events/eventrelation.h" #include <QtCore/QFileInfo> #include <QtCore/QMimeDatabase> #include <QtGui/QImageReader> -#include <QtMultimedia/QMediaResource> +#if QT_VERSION_MAJOR < 6 +# include <QtMultimedia/QMediaResource> +#endif using namespace Quotient; using namespace EventContent; using MsgType = RoomMessageEvent::MsgType; -static const auto RelatesToKeyL = "m.relates_to"_ls; -static const auto MsgTypeKeyL = "msgtype"_ls; -static const auto FormattedBodyKeyL = "formatted_body"_ls; - -static const auto TextTypeKey = "m.text"; -static const auto EmoteTypeKey = "m.emote"; -static const auto NoticeTypeKey = "m.notice"; - -static const auto HtmlContentTypeId = QStringLiteral("org.matrix.custom.html"); +namespace { // Supporting internal definitions +constexpr auto RelatesToKey = "m.relates_to"_ls; +constexpr auto MsgTypeKey = "msgtype"_ls; +constexpr auto FormattedBodyKey = "formatted_body"_ls; +constexpr auto TextTypeKey = "m.text"_ls; +constexpr auto EmoteTypeKey = "m.emote"_ls; +constexpr auto NoticeTypeKey = "m.notice"_ls; +constexpr auto HtmlContentTypeId = "org.matrix.custom.html"_ls; template <typename ContentT> TypedBase* make(const QJsonObject& json) @@ -49,13 +38,13 @@ TypedBase* make(const QJsonObject& json) template <> TypedBase* make<TextContent>(const QJsonObject& json) { - return json.contains(FormattedBodyKeyL) || json.contains(RelatesToKeyL) + return json.contains(FormattedBodyKey) || json.contains(RelatesToKey) ? new TextContent(json) : nullptr; } struct MsgTypeDesc { - QString matrixType; + QLatin1String matrixType; MsgType enumType; TypedBase* (*maker)(const QJsonObject&); }; @@ -64,11 +53,11 @@ const std::vector<MsgTypeDesc> msgTypes = { { TextTypeKey, MsgType::Text, make<TextContent> }, { EmoteTypeKey, MsgType::Emote, make<TextContent> }, { NoticeTypeKey, MsgType::Notice, make<TextContent> }, - { QStringLiteral("m.image"), MsgType::Image, make<ImageContent> }, - { QStringLiteral("m.file"), MsgType::File, make<FileContent> }, - { QStringLiteral("m.location"), MsgType::Location, make<LocationContent> }, - { QStringLiteral("m.video"), MsgType::Video, make<VideoContent> }, - { QStringLiteral("m.audio"), MsgType::Audio, make<AudioContent> } + { "m.image"_ls, MsgType::Image, make<ImageContent> }, + { "m.file"_ls, MsgType::File, make<FileContent> }, + { "m.location"_ls, MsgType::Location, make<LocationContent> }, + { "m.video"_ls, MsgType::Video, make<VideoContent> }, + { "m.audio"_ls, MsgType::Audio, make<AudioContent> } }; QString msgTypeToJson(MsgType enumType) @@ -95,49 +84,52 @@ MsgType jsonToMsgType(const QString& matrixType) return MsgType::Unknown; } -inline bool isReplacement(const Omittable<RelatesTo>& rel) +inline bool isReplacement(const Omittable<EventRelation>& rel) { - return rel && rel->type == RelatesTo::ReplacementTypeId(); + return rel && rel->type == EventRelation::ReplacementType; } +} // anonymous namespace + QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody, const QString& jsonMsgType, TypedBase* content) { - auto json = content ? content->toJson() : QJsonObject(); - if (json.contains(RelatesToKeyL)) { + QJsonObject json; + if (content) { + // TODO: replace with content->fillJson(json) when it starts working + json = content->toJson(); if (jsonMsgType != TextTypeKey && jsonMsgType != NoticeTypeKey && jsonMsgType != EmoteTypeKey) { - json.remove(RelatesToKeyL); - qCWarning(EVENTS) - << RelatesToKeyL << "cannot be used in" << jsonMsgType - << "messages; the relation has been stripped off"; - } else { - // After the above, we know for sure that the content is TextContent - // and that its RelatesTo structure is not omitted - auto* textContent = static_cast<const TextContent*>(content); - Q_ASSERT(textContent && textContent->relatesTo.has_value()); - if (textContent->relatesTo->type == RelatesTo::ReplacementTypeId()) { - auto newContentJson = json.take("m.new_content"_ls).toObject(); - newContentJson.insert(BodyKey, plainBody); - newContentJson.insert(MsgTypeKeyL, jsonMsgType); - json.insert(QStringLiteral("m.new_content"), newContentJson); - json[MsgTypeKeyL] = jsonMsgType; - json[BodyKeyL] = "* " + plainBody; - return json; + if (json.contains(RelatesToKey)) { + json.remove(RelatesToKey); + qCWarning(EVENTS) + << RelatesToKey << "cannot be used in" << jsonMsgType + << "messages; the relation has been stripped off"; } + } else if (auto* textContent = static_cast<const TextContent*>(content); + textContent->relatesTo + && textContent->relatesTo->type + == EventRelation::ReplacementType) { + auto newContentJson = json.take("m.new_content"_ls).toObject(); + newContentJson.insert(BodyKey, plainBody); + newContentJson.insert(MsgTypeKey, jsonMsgType); + json.insert(QStringLiteral("m.new_content"), newContentJson); + json[MsgTypeKey] = jsonMsgType; + json[BodyKeyL] = "* " + plainBody; + return json; } } - json.insert(QStringLiteral("msgtype"), jsonMsgType); - json.insert(QStringLiteral("body"), plainBody); + json.insert(MsgTypeKey, jsonMsgType); + json.insert(BodyKey, plainBody); return json; } RoomMessageEvent::RoomMessageEvent(const QString& plainBody, const QString& jsonMsgType, TypedBase* content) - : RoomEvent(typeId(), matrixTypeId(), - assembleContentJson(plainBody, jsonMsgType, content)) + : RoomEvent( + basicJson(TypeId, assembleContentJson(plainBody, jsonMsgType, content))) , _content(content) {} @@ -146,6 +138,7 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody, MsgType msgType, : RoomMessageEvent(plainBody, msgTypeToJson(msgType), content) {} +#if QT_VERSION_MAJOR < 6 TypedBase* contentFromFile(const QFileInfo& file, bool asGenericFile) { auto filePath = file.absoluteFilePath(); @@ -179,15 +172,16 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody, : rawMsgTypeForFile(file), contentFromFile(file, asGenericFile)) {} +#endif RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) - : RoomEvent(typeId(), obj), _content(nullptr) + : RoomEvent(obj), _content(nullptr) { if (isRedacted()) return; const QJsonObject content = contentJson(); - if (content.contains(MsgTypeKeyL) && content.contains(BodyKeyL)) { - auto msgtype = content[MsgTypeKeyL].toString(); + if (content.contains(MsgTypeKey) && content.contains(BodyKeyL)) { + auto msgtype = content[MsgTypeKey].toString(); bool msgTypeFound = false; for (const auto& mt : msgTypes) if (mt.matrixType == msgtype) { @@ -213,12 +207,12 @@ RoomMessageEvent::MsgType RoomMessageEvent::msgtype() const QString RoomMessageEvent::rawMsgtype() const { - return contentJson()[MsgTypeKeyL].toString(); + return contentPart<QString>(MsgTypeKey); } QString RoomMessageEvent::plainBody() const { - return contentJson()[BodyKeyL].toString(); + return contentPart<QString>(BodyKeyL); } QMimeType RoomMessageEvent::mimeType() const @@ -276,7 +270,7 @@ QString RoomMessageEvent::rawMsgTypeForFile(const QFileInfo& fi) } TextContent::TextContent(QString text, const QString& contentType, - Omittable<RelatesTo> relatesTo) + Omittable<EventRelation> relatesTo) : mimeType(QMimeDatabase().mimeTypeForName(contentType)) , body(std::move(text)) , relatesTo(std::move(relatesTo)) @@ -285,26 +279,8 @@ TextContent::TextContent(QString text, const QString& contentType, mimeType = QMimeDatabase().mimeTypeForName("text/html"); } -namespace Quotient { -// Overload the default fromJson<> logic that defined in converters.h -// as we want -template <> -Omittable<RelatesTo> fromJson(const QJsonValue& jv) -{ - const auto jo = jv.toObject(); - if (jo.isEmpty()) - return none; - const auto replyJson = jo.value(RelatesTo::ReplyTypeId()).toObject(); - if (!replyJson.isEmpty()) - return replyTo(fromJson<QString>(replyJson[EventIdKeyL])); - - return RelatesTo { jo.value("rel_type"_ls).toString(), - jo.value(EventIdKeyL).toString() }; -} -} // namespace Quotient - TextContent::TextContent(const QJsonObject& json) - : relatesTo(fromJson<Omittable<RelatesTo>>(json[RelatesToKeyL])) + : relatesTo(fromJson<Omittable<EventRelation>>(json[RelatesToKey])) { QMimeDatabase db; static const auto PlainTextMimeType = db.mimeTypeForName("text/plain"); @@ -317,7 +293,7 @@ TextContent::TextContent(const QJsonObject& json) // of sending HTML messages. if (actualJson["format"_ls].toString() == HtmlContentTypeId) { mimeType = HtmlMimeType; - body = actualJson[FormattedBodyKeyL].toString(); + body = actualJson[FormattedBodyKey].toString(); } else { // Falling back to plain text, as there's no standard way to describe // rich text in messages. @@ -326,29 +302,30 @@ TextContent::TextContent(const QJsonObject& json) } } -void TextContent::fillJson(QJsonObject* json) const +void TextContent::fillJson(QJsonObject &json) const { static const auto FormatKey = QStringLiteral("format"); - static const auto FormattedBodyKey = QStringLiteral("formatted_body"); - 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(QStringLiteral("m.relates_to"), - relatesTo->type == RelatesTo::ReplyTypeId() ? - QJsonObject { { relatesTo->type, QJsonObject{ { EventIdKey, relatesTo->eventId } } } } : - QJsonObject { { "rel_type", relatesTo->type }, { EventIdKey, relatesTo->eventId } } - ); - if (relatesTo->type == RelatesTo::ReplacementTypeId()) { + json.insert( + QStringLiteral("m.relates_to"), + relatesTo->type == EventRelation::ReplyType + ? QJsonObject { { relatesTo->type, + QJsonObject { + { EventIdKey, relatesTo->eventId } } } } + : QJsonObject { { RelTypeKey, relatesTo->type }, + { EventIdKey, relatesTo->eventId } }); + if (relatesTo->type == EventRelation::ReplacementType) { QJsonObject newContentJson; if (mimeType.inherits("text/html")) { newContentJson.insert(FormatKey, HtmlContentTypeId); newContentJson.insert(FormattedBodyKey, body); } - json->insert(QStringLiteral("m.new_content"), newContentJson); + json.insert(QStringLiteral("m.new_content"), newContentJson); } } } @@ -369,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 2501d097..889fc4dc 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -1,24 +1,12 @@ -/****************************************************************************** - * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2015 Felix Rohrbach <kde@fxrh.de> +// SPDX-FileCopyrightText: 2016 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-FileCopyrightText: 2017 Roman Plášil <me@rplasil.name> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include "eventcontent.h" +#include "eventrelation.h" #include "roomevent.h" class QFileInfo; @@ -29,14 +17,10 @@ namespace MessageEventContent = EventContent; // Back-compatibility /** * The event class corresponding to m.room.message events */ -class RoomMessageEvent : public RoomEvent { +class QUOTIENT_API RoomMessageEvent : public RoomEvent { Q_GADGET - Q_PROPERTY(QString msgType READ rawMsgtype CONSTANT) - Q_PROPERTY(QString plainBody READ plainBody CONSTANT) - Q_PROPERTY(QMimeType mimeType READ mimeType STORED false CONSTANT) - Q_PROPERTY(const EventContent::TypedBase* content READ content CONSTANT) public: - DEFINE_EVENT_TYPEID("m.room.message", RoomMessageEvent) + QUO_EVENT(RoomMessageEvent, "m.room.message") enum class MsgType { Text, @@ -55,8 +39,12 @@ public: explicit RoomMessageEvent(const QString& plainBody, MsgType msgType = MsgType::Text, EventContent::TypedBase* content = nullptr); +#if QT_VERSION_MAJOR < 6 + [[deprecated("Create an EventContent object on the client side" + " and pass it to other constructors")]] // explicit RoomMessageEvent(const QString& plainBody, const QFileInfo& file, bool asGenericFile = false); +#endif explicit RoomMessageEvent(const QJsonObject& obj); MsgType msgtype() const; @@ -71,9 +59,26 @@ public: _content.data()); } QMimeType mimeType() const; + //! \brief Determine whether the message has text content + //! + //! \return true, if the message type is one of m.text, m.notice, m.emote, + //! or the message type is unspecified (in which case plainBody() + //! can still be examined); false otherwise bool hasTextContent() const; + //! \brief Determine whether the message has a file/attachment + //! + //! \return true, if the message has a data structure corresponding to + //! a file (such as m.file or m.audio); false otherwise bool hasFileContent() const; + //! \brief Determine whether the message has a thumbnail + //! + //! \return true, if the message has a data structure corresponding to + //! a thumbnail (the message type may be one for visual content, + //! such as m.image, or generic binary content, i.e. m.file); + //! false otherwise bool hasThumbnail() const; + //! \brief Obtain id of an event replaced by the current one + //! \sa RoomEvent::isReplaced, RoomEvent::replacedBy QString replacedEvent() const; static QString rawMsgTypeForUrl(const QUrl& url); @@ -89,47 +94,49 @@ private: Q_ENUM(MsgType) }; -REGISTER_EVENT_TYPE(RoomMessageEvent) + using MessageEventType = RoomMessageEvent::MsgType; namespace EventContent { - // Additional event content types - struct RelatesTo { - static constexpr const char* ReplyTypeId() { return "m.in_reply_to"; } - static constexpr const char* ReplacementTypeId() { return "m.replace"; } - QString type; // The only supported relation so far - QString eventId; + struct [[deprecated("Use Quotient::EventRelation instead")]] RelatesTo + : EventRelation { + static constexpr auto ReplyTypeId() { return ReplyType; } + static constexpr auto ReplacementTypeId() { return ReplacementType; } }; - inline RelatesTo replyTo(QString eventId) + [[deprecated("Use EventRelation::replyTo() instead")]] + inline auto replyTo(QString eventId) { - return { RelatesTo::ReplyTypeId(), std::move(eventId) }; + return EventRelation::replyTo(std::move(eventId)); } - inline RelatesTo replacementOf(QString eventId) + [[deprecated("Use EventRelation::replace() instead")]] + inline auto replacementOf(QString eventId) { - return { RelatesTo::ReplacementTypeId(), std::move(eventId) }; + return EventRelation::replace(std::move(eventId)); } + // Additional event content types + /** * Rich text content for m.text, m.emote, m.notice * * Available fields: mimeType, body. The body can be either rich text * or plain text, depending on what mimeType specifies. */ - class TextContent : public TypedBase { + class QUOTIENT_API TextContent : public TypedBase { public: TextContent(QString text, const QString& contentType, - Omittable<RelatesTo> relatesTo = none); + Omittable<EventRelation> relatesTo = none); explicit TextContent(const QJsonObject& json); QMimeType type() const override { return mimeType; } QMimeType mimeType; QString body; - Omittable<RelatesTo> relatesTo; + Omittable<EventRelation> relatesTo; protected: - void fillJson(QJsonObject* json) const override; + void fillJson(QJsonObject& json) const override; }; /** @@ -145,7 +152,7 @@ namespace EventContent { * - thumbnail.mimeType * - thumbnail.imageSize */ - class LocationContent : public TypedBase { + class QUOTIENT_API LocationContent : public TypedBase { public: LocationContent(const QString& geoUri, const Thumbnail& thumbnail = {}); explicit LocationContent(const QJsonObject& json); @@ -157,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 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: @@ -204,7 +208,7 @@ namespace EventContent { * - mimeType * - imageSize */ - using VideoContent = PlayableContent<UrlWithThumbnailContent<ImageInfo>>; + using VideoContent = PlayableContent<ImageInfo>; /** * Content class for m.audio @@ -217,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 0a401752..d9bd010b 100644 --- a/lib/events/roompowerlevelsevent.cpp +++ b/lib/events/roompowerlevelsevent.cpp @@ -1,9 +1,12 @@ -#include "roompowerlevelsevent.h" +// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org> +// SPDX-License-Identifier: LGPL-2.1-or-later -#include <QJsonDocument> +#include "roompowerlevelsevent.h" 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)), @@ -15,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 f0f7207f..6150980a 100644 --- a/lib/events/roompowerlevelsevent.h +++ b/lib/events/roompowerlevelsevent.h @@ -1,16 +1,18 @@ +// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org> +// SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once -#include "eventcontent.h" #include "stateevent.h" namespace Quotient { -class 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; @@ -26,19 +28,14 @@ public: int usersDefault; Notifications notifications; - -protected: - void fillJson(QJsonObject* o) const override; }; -class RoomPowerLevelsEvent : public StateEvent<PowerLevelsEventContent> { - Q_GADGET +class QUOTIENT_API RoomPowerLevelsEvent + : public KeylessStateEventBase<RoomPowerLevelsEvent, PowerLevelsEventContent> { public: - DEFINE_EVENT_TYPEID("m.room.power_levels", RoomPowerLevelsEvent) + QUO_EVENT(RoomPowerLevelsEvent, "m.room.power_levels") - explicit RoomPowerLevelsEvent(const QJsonObject& obj) - : StateEvent(typeId(), obj) - {} + using KeylessStateEventBase::KeylessStateEventBase; int invite() const { return content().invite; } int kick() const { return content().kick; } @@ -58,19 +55,5 @@ public: int powerLevelForEvent(const QString& eventId) const; int powerLevelForState(const QString& eventId) const; int powerLevelForUser(const QString& userId) const; - -private: }; - -template <> -class EventFactory<RoomPowerLevelsEvent> { -public: - static event_ptr_tt<RoomPowerLevelsEvent> make(const QJsonObject& json, - const QString&) - { - return makeEvent<RoomPowerLevelsEvent>(json); - } -}; - -REGISTER_EVENT_TYPE(RoomPowerLevelsEvent) } // namespace Quotient diff --git a/lib/events/roomtombstoneevent.cpp b/lib/events/roomtombstoneevent.cpp index f93eb60d..2c3492d6 100644 --- a/lib/events/roomtombstoneevent.cpp +++ b/lib/events/roomtombstoneevent.cpp @@ -1,20 +1,5 @@ -/****************************************************************************** - * Copyright (C) 2019 QMatrixClient project - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2019 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #include "roomtombstoneevent.h" @@ -22,10 +7,10 @@ using namespace Quotient; QString RoomTombstoneEvent::serverMessage() const { - return fromJson<QString>(contentJson()["body"_ls]); + return contentPart<QString>("body"_ls); } QString RoomTombstoneEvent::successorRoomId() const { - return fromJson<QString>(contentJson()["replacement_room"_ls]); + return contentPart<QString>("replacement_room"_ls); } diff --git a/lib/events/roomtombstoneevent.h b/lib/events/roomtombstoneevent.h index 2c2f0663..c85b4dfd 100644 --- a/lib/events/roomtombstoneevent.h +++ b/lib/events/roomtombstoneevent.h @@ -1,37 +1,18 @@ -/****************************************************************************** - * Copyright (C) 2019 QMatrixClient project - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2019 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include "stateevent.h" namespace Quotient { -class RoomTombstoneEvent : public StateEventBase { +class QUOTIENT_API RoomTombstoneEvent : public StateEvent { public: - DEFINE_EVENT_TYPEID("m.room.tombstone", RoomTombstoneEvent) + QUO_EVENT(RoomTombstoneEvent, "m.room.tombstone") - explicit RoomTombstoneEvent() : StateEventBase(typeId(), matrixTypeId()) {} - explicit RoomTombstoneEvent(const QJsonObject& obj) - : StateEventBase(typeId(), obj) - {} + using StateEvent::StateEvent; QString serverMessage() const; QString successorRoomId() const; }; -REGISTER_EVENT_TYPE(RoomTombstoneEvent) } // namespace Quotient diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index cde5b0fd..2a0d3817 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -1,89 +1,47 @@ -/****************************************************************************** - * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include "stateevent.h" +#include "single_key_value.h" namespace Quotient { -namespace EventContent { - template <typename T> - class SimpleContent { - public: - 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) } }; - } - - public: - T value; - - protected: - QString key; - }; -} // namespace EventContent - -#define DEFINE_SIMPLE_STATE_EVENT(_Name, _TypeId, _ValueType, _ContentKey) \ - class _Name : public StateEvent<EventContent::SimpleContent<_ValueType>> { \ - public: \ - using value_type = content_type::value_type; \ - DEFINE_EVENT_TYPEID(_TypeId, _Name) \ - explicit _Name() : _Name(value_type()) {} \ - 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) \ - // End of macro +#define DEFINE_SIMPLE_STATE_EVENT(Name_, TypeId_, ValueType_, ContentKey_) \ + constexpr auto Name_##Key = #ContentKey_##_ls; \ + class QUOTIENT_API Name_ \ + : public KeylessStateEventBase< \ + Name_, EventContent::SingleKeyValue<ValueType_, Name_##Key>> { \ + public: \ + using value_type = ValueType_; \ + QUO_EVENT(Name_, TypeId_) \ + using KeylessStateEventBase::KeylessStateEventBase; \ + auto ContentKey_() const { return content().value; } \ + }; \ +// End of macro DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name) DEFINE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic", QString, topic) - -class RoomAliasesEvent - : public StateEvent<EventContent::SimpleContent<QStringList>> { +DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEvent, "m.room.pinned_messages", + QStringList, pinnedEvents) + +constexpr auto RoomAliasesEventKey = "aliases"_ls; +class QUOTIENT_API RoomAliasesEvent + : public KeyedStateEventBase< + RoomAliasesEvent, + EventContent::SingleKeyValue<QStringList, RoomAliasesEventKey>> +{ public: - DEFINE_EVENT_TYPEID("m.room.aliases", RoomAliasesEvent) - explicit RoomAliasesEvent(const QJsonObject& obj) - : StateEvent(typeId(), obj, QStringLiteral("aliases")) - {} - RoomAliasesEvent(const QString& server, const QStringList& aliases) - : StateEvent(typeId(), matrixTypeId(), server, - QStringLiteral("aliases"), aliases) - {} + QUO_EVENT(RoomAliasesEvent, "m.room.aliases") + using KeyedStateEventBase::KeyedStateEventBase; + + 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; } }; -REGISTER_EVENT_TYPE(RoomAliasesEvent) } // namespace Quotient diff --git a/lib/events/single_key_value.h b/lib/events/single_key_value.h new file mode 100644 index 00000000..ca2bd331 --- /dev/null +++ b/lib/events/single_key_value.h @@ -0,0 +1,36 @@ +#pragma once + +#include "converters.h" + +namespace Quotient { + +namespace EventContent { + template <typename T, const QLatin1String& KeyStr> + struct SingleKeyValue { + // NOLINTBEGIN(google-explicit-constructor): that check should learn + // about explicit(false) + QUO_IMPLICIT SingleKeyValue(const T& v = {}) + : value { v } + {} + QUO_IMPLICIT SingleKeyValue(T&& v) + : value { std::move(v) } + {} + // NOLINTEND(google-explicit-constructor) + 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 5909e8a6..72ecd5ad 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -1,64 +1,40 @@ -/****************************************************************************** - * Copyright (C) 2018 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #include "stateevent.h" +#include "logging.h" using namespace Quotient; -// Aside from the normal factory to instantiate StateEventBase inheritors -// StateEventBase itself can be instantiated if there's a state_key JSON key -// but the event type is unknown. -[[maybe_unused]] static auto stateEventTypeInitialised = - RoomEvent::factory_t::addMethod( - [](const QJsonObject& json, const QString& matrixType) -> StateEventPtr { - if (!json.contains(StateKeyKeyL)) - return nullptr; - - if (auto e = StateEventBase::factory_t::make(json, matrixType)) - return e; - - return makeEvent<StateEventBase>(unknownEventTypeId(), json); - }); +StateEvent::StateEvent(const QJsonObject& json) + : RoomEvent(json) +{ + Q_ASSERT_X(json.contains(StateKeyKeyL), __FUNCTION__, + "Attempt to create a state event without state key"); +} -StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, - const QString& stateKey, +StateEvent::StateEvent(Event::Type type, const QString& stateKey, const QJsonObject& contentJson) - : RoomEvent(type, basicStateEventJson(matrixType, contentJson, stateKey)) + : RoomEvent(basicJson(type, stateKey, contentJson)) {} -bool StateEventBase::repeatsState() const +bool StateEvent::repeatsState() const { - const auto prevContentJson = unsignedJson().value(PrevContentKeyL); - return fullJson().value(ContentKeyL) == prevContentJson; + return contentJson() == unsignedPart<QJsonObject>(PrevContentKeyL); } -QString StateEventBase::replacedState() const +QString StateEvent::replacedState() const { - return unsignedJson().value("replaces_state"_ls).toString(); + return unsignedPart<QString>("replaces_state"_ls); } -void StateEventBase::dumpTo(QDebug dbg) const +void StateEvent::dumpTo(QDebug dbg) const { if (!stateKey().isEmpty()) dbg << '<' << stateKey() << "> "; - if (unsignedJson().contains(PrevContentKeyL)) - dbg << QJsonDocument(unsignedJson()[PrevContentKeyL].toObject()) - .toJson(QJsonDocument::Compact) + if (const auto prevContentJson = unsignedPart<QJsonObject>(PrevContentKeyL); + !prevContentJson.isEmpty()) + dbg << QJsonDocument(prevContentJson).toJson(QJsonDocument::Compact) << " -> "; RoomEvent::dumpTo(dbg); } diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 710b4271..992ec2e2 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -1,20 +1,5 @@ -/****************************************************************************** - * Copyright (C) 2018 Kitsune Ral <kitsune-ral@users.sf.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once @@ -22,41 +7,54 @@ namespace Quotient { -/// Make a minimal correct Matrix state event JSON -template <typename StrT> -inline QJsonObject basicStateEventJson(StrT matrixType, - const QJsonObject& content, - const QString& stateKey = {}) -{ - return { { TypeKey, std::forward<StrT>(matrixType) }, - { StateKeyKey, stateKey }, - { ContentKey, content } }; -} - -class StateEventBase : public RoomEvent { +class QUOTIENT_API StateEvent : public RoomEvent { public: - using factory_t = EventFactory<StateEventBase>; + QUO_BASE_EVENT(StateEvent, "json.contains('state_key')"_ls, + RoomEvent::BaseMetaType) + static bool isValid(const QJsonObject& fullJson) + { + return fullJson.contains(StateKeyKeyL); + } - StateEventBase(Type type, const QJsonObject& json) : RoomEvent(type, json) - {} - StateEventBase(Type type, event_mtype_t matrixType, - const QString& stateKey = {}, - const QJsonObject& contentJson = {}); - ~StateEventBase() override = default; + //! \brief Static setting of whether a given even type uses state keys + //! + //! Most event types don't use a state key; overriding this to `true` + //! for a given type changes the calls across Quotient to include state key + //! in their signatures; otherwise, state key is still accessible but + //! constructors and calls in, e.g., RoomStateView don't include it. + static constexpr auto needsStateKey = false; + + explicit StateEvent(Type type, const QString& stateKey = {}, + const QJsonObject& contentJson = {}); + + //! 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; - virtual bool repeatsState() const; + +protected: + explicit StateEvent(const QJsonObject& json); + void dumpTo(QDebug dbg) const override; }; -using StateEventPtr = event_ptr_tt<StateEventBase>; -using StateEvents = EventsArray<StateEventBase>; +using StateEventBase + [[deprecated("StateEventBase is StateEvent now")]] = StateEvent; +using StateEventPtr = event_ptr_tt<StateEvent>; +using StateEvents = EventsArray<StateEvent>; -template <> -inline bool is<StateEventBase>(const Event& e) +[[deprecated("Use StateEvent::basicJson() instead")]] +inline QJsonObject basicStateEventJson(const QString& matrixTypeId, + const QJsonObject& content, + const QString& stateKey = {}) { - return e.isStateEvent(); + return StateEvent::basicJson(matrixTypeId, stateKey, content); } /** @@ -65,67 +63,89 @@ 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>; - -template <typename ContentT> -struct Prev { - template <typename... ContentParamTs> - explicit Prev(const QJsonObject& unsignedJson, - ContentParamTs&&... contentParams) - : senderId(unsignedJson.value("prev_sender"_ls).toString()) - , content(unsignedJson.value(PrevContentKeyL).toObject(), - std::forward<ContentParamTs>(contentParams)...) - {} - - QString senderId; - ContentT content; -}; +using StateEventKey = std::pair<QString, QString>; -template <typename ContentT> -class StateEvent : public StateEventBase { +template <typename EventT, typename ContentT> +class EventTemplate<EventT, StateEvent, ContentT> + : public StateEvent { public: using content_type = ContentT; + struct Prev { + explicit Prev() = default; + explicit Prev(const QJsonObject& unsignedJson) + : senderId(fromJson<QString>(unsignedJson["prev_sender"_ls])) + , content( + fromJson<Omittable<ContentT>>(unsignedJson[PrevContentKeyL])) + {} + + QString senderId; + Omittable<ContentT> content; + }; + + explicit EventTemplate(const QJsonObject& fullJson) + : StateEvent(fullJson) + , _content(fromJson<ContentT>(Event::contentJson())) + , _prev(unsignedJson()) + {} template <typename... ContentParamTs> - explicit StateEvent(Type type, const QJsonObject& fullJson, - ContentParamTs&&... contentParams) - : StateEventBase(type, fullJson) - , _content(contentJson(), std::forward<ContentParamTs>(contentParams)...) - { - const auto& unsignedData = unsignedJson(); - if (unsignedData.contains(PrevContentKeyL)) - _prev = std::make_unique<Prev<ContentT>>( - unsignedData, std::forward<ContentParamTs>(contentParams)...); - } - template <typename... ContentParamTs> - explicit StateEvent(Type type, event_mtype_t matrixType, - const QString& stateKey, - ContentParamTs&&... contentParams) - : StateEventBase(type, matrixType, stateKey) - , _content(std::forward<ContentParamTs>(contentParams)...) + explicit EventTemplate(const QString& stateKey, + ContentParamTs&&... contentParams) + : StateEvent(EventT::TypeId, stateKey) + , _content { std::forward<ContentParamTs>(contentParams)... } { - editJson().insert(ContentKey, _content.toJson()); + editJson().insert(ContentKey, toJson(_content)); } const ContentT& content() const { return _content; } + template <typename VisitorT> void editContent(VisitorT&& visitor) { visitor(_content); - editJson()[ContentKeyL] = _content.toJson(); - } - [[deprecated("Use prevContent instead")]] const ContentT* prev_content() const - { - return prevContent(); - } - const ContentT* prevContent() const - { - return _prev ? &_prev->content : nullptr; + editJson()[ContentKeyL] = toJson(_content); } - QString prevSenderId() const { return _prev ? _prev->senderId : QString(); } + const Omittable<ContentT>& prevContent() const { return _prev.content; } + QString prevSenderId() const { return _prev.senderId; } private: ContentT _content; - std::unique_ptr<Prev<ContentT>> _prev; + Prev _prev; +}; + +template <typename EventT, typename ContentT> +class KeyedStateEventBase + : public EventTemplate<EventT, StateEvent, ContentT> { +public: + static constexpr auto needsStateKey = true; + + using EventTemplate<EventT, StateEvent, ContentT>::EventTemplate; }; + +template <typename EvT> +concept Keyed_State_Event = EvT::needsStateKey; + +template <typename EventT, typename ContentT> +class KeylessStateEventBase + : public EventTemplate<EventT, StateEvent, ContentT> { +private: + using base_type = EventTemplate<EventT, StateEvent, ContentT>; + +public: + template <typename... ContentParamTs> + explicit KeylessStateEventBase(ContentParamTs&&... contentParams) + : base_type(QString(), std::forward<ContentParamTs>(contentParams)...) + {} + +protected: + explicit KeylessStateEventBase(const QJsonObject& fullJson) + : base_type(fullJson) + {} +}; + +template <typename EvT> +concept Keyless_State_Event = !EvT::needsStateKey; + } // namespace Quotient +Q_DECLARE_METATYPE(Quotient::StateEvent*) +Q_DECLARE_METATYPE(const Quotient::StateEvent*) diff --git a/lib/events/stickerevent.h b/lib/events/stickerevent.h new file mode 100644 index 00000000..67905481 --- /dev/null +++ b/lib/events/stickerevent.h @@ -0,0 +1,48 @@ +// SDPX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org> +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "roomevent.h" +#include "eventcontent.h" + +namespace Quotient { + +/// Sticker messages are specialised image messages that are displayed without +/// controls (e.g. no "download" link, or light-box view on click, as would be +/// displayed for for m.image events). +class QUOTIENT_API StickerEvent : public RoomEvent +{ +public: + QUO_EVENT(StickerEvent, "m.sticker") + + 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. + QUO_CONTENT_GETTER(QString, body) + + /// \brief Metadata about the image referred to in url including a + /// thumbnail representation. + 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 + { + return m_imageContent.url(); + } + +private: + EventContent::ImageContent m_imageContent; +}; +} // namespace Quotient diff --git a/lib/events/typingevent.cpp b/lib/events/typingevent.cpp deleted file mode 100644 index a95d2f0d..00000000 --- a/lib/events/typingevent.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "typingevent.h" - -#include <QtCore/QJsonArray> - -using namespace Quotient; - -TypingEvent::TypingEvent(const QJsonObject& obj) : Event(typeId(), obj) -{ - const auto& array = contentJson()["user_ids"_ls].toArray(); - _users.reserve(array.size()); - for (const auto& user : array) - _users.push_back(user.toString()); -} diff --git a/lib/events/typingevent.h b/lib/events/typingevent.h index 1cf4e69d..b56475af 100644 --- a/lib/events/typingevent.h +++ b/lib/events/typingevent.h @@ -1,36 +1,10 @@ -/****************************************************************************** - * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include "event.h" namespace Quotient { -class TypingEvent : public Event { -public: - DEFINE_EVENT_TYPEID("m.typing", TypingEvent) - - TypingEvent(const QJsonObject& obj); - - const QStringList& users() const { return _users; } - -private: - QStringList _users; -}; -REGISTER_EVENT_TYPE(TypingEvent) +DEFINE_SIMPLE_EVENT(TypingEvent, Event, "m.typing", QStringList, users, "user_ids") } // namespace Quotient |