├── logo.png ├── gtad ├── preamble.mustache ├── data.h.mustache └── define_models.mustache ├── source_file.template ├── libquotientemojis.qrc ├── .gitmodules ├── quotest.pro ├── Quotient ├── events │ ├── typingevent.h │ ├── roomtombstoneevent.cpp │ ├── directchatevent.h │ ├── roomtombstoneevent.h │ ├── redactionevent.h │ ├── roomcreateevent.h │ ├── directchatevent.cpp │ ├── roomavatarevent.h │ ├── receiptevent.h │ ├── single_key_value.h │ ├── roomcreateevent.cpp │ ├── reactionevent.h │ ├── roomkeyevent.h │ ├── stateevent.cpp │ ├── encryptionevent.cpp │ ├── encryptionevent.h │ ├── accountdataevents.h │ ├── roomcanonicalaliasevent.h │ ├── stickerevent.h │ ├── roommemberevent.h │ ├── eventrelation.cpp │ ├── roompowerlevelsevent.cpp │ ├── simplestateevents.h │ └── eventrelation.h ├── csapi │ ├── openid.cpp │ ├── wellknown.cpp │ ├── refresh.cpp │ ├── voip.cpp │ ├── versions.cpp │ ├── support.cpp │ ├── admin.cpp │ ├── whoami.cpp │ ├── kicking.cpp │ ├── list_joined_rooms.cpp │ ├── room_send.cpp │ ├── room_upgrades.cpp │ ├── capabilities.cpp │ ├── inviting.cpp │ ├── login_token.cpp │ ├── redaction.cpp │ ├── to_device.cpp │ ├── room_state.cpp │ ├── typing.cpp │ ├── receipts.cpp │ ├── users.cpp │ ├── report_content.cpp │ ├── appservice_room_directory.cpp │ ├── search.cpp │ ├── definitions │ │ ├── tag.h │ │ ├── wellknown │ │ │ ├── homeserver.h │ │ │ ├── identity_server.h │ │ │ └── full.h │ │ ├── room_key_backup.h │ │ ├── user_identifier.h │ │ ├── push_ruleset.h │ │ ├── auth_data.h │ │ ├── third_party_signed.h │ │ ├── client_device.h │ │ ├── openid_token.h │ │ ├── cross_signing_key.h │ │ ├── request_token_response.h │ │ ├── key_backup_data.h │ │ ├── push_rule.h │ │ └── device_keys.h │ ├── read_markers.cpp │ ├── logout.cpp │ ├── third_party_membership.cpp │ ├── room_upgrades.h │ ├── knocking.cpp │ ├── banning.cpp │ ├── leaving.cpp │ ├── voip.h │ ├── filter.cpp │ ├── list_joined_rooms.h │ ├── peeking_events.cpp │ ├── registration_tokens.cpp │ ├── presence.cpp │ ├── typing.h │ ├── to_device.h │ ├── notifications.cpp │ ├── room_event_by_timestamp.cpp │ ├── kicking.h │ ├── cross_signing.cpp │ ├── event_context.cpp │ ├── threads_list.cpp │ ├── read_markers.h │ ├── report_content.h │ ├── wellknown.h │ ├── receipts.h │ ├── inviting.h │ ├── login.cpp │ ├── registration_tokens.h │ ├── pusher.cpp │ ├── appservice_room_directory.h │ ├── room_send.h │ ├── message_pagination.cpp │ ├── joining.cpp │ ├── tags.cpp │ ├── sso_login_redirect.cpp │ ├── space_hierarchy.cpp │ ├── create_room.cpp │ ├── openid.h │ ├── redaction.h │ ├── directory.cpp │ ├── banning.h │ ├── device_management.cpp │ ├── room_state.h │ ├── logout.h │ ├── knocking.h │ ├── leaving.h │ └── filter.h ├── thread.h ├── networksettings.cpp ├── thread.cpp ├── expected.h ├── e2ee │ ├── qolmmessage.cpp │ ├── qolmmessage.h │ ├── qolmutility.h │ ├── qolmutility.cpp │ ├── sssshandler.h │ ├── qolmoutboundsession.h │ └── qolmsession.h ├── networksettings.h ├── mxcreply.h ├── eventitem.cpp ├── converters.cpp ├── jobs │ ├── requestdata.h │ ├── syncjob.h │ ├── downloadfilejob.h │ ├── requestdata.cpp │ ├── mediathumbnailjob.h │ └── syncjob.cpp ├── quotient_export.h ├── application-service │ └── definitions │ │ ├── location.h │ │ └── user.h ├── roomstateview.cpp ├── logging_categories_p.h ├── keyimport.h ├── avatar.h ├── connectiondata.h ├── networkaccessmanager.h └── ssosession.h ├── Quotient.pc.in ├── autotests ├── testgroupsession.h ├── testolmutility.h ├── testolmsession.h ├── data │ ├── test-threadroot-event.json │ ├── test-thread1-event.json │ └── test-thread2-event.json ├── register-users.sh ├── testolmaccount.h ├── adjust-config.sh ├── CMakeLists.txt ├── key-export.data ├── testutils.h ├── testutils.cpp ├── testkeyimport.cpp ├── callcandidateseventtest.cpp ├── setup-tests.sh └── testgroupsession.cpp ├── sonar-project.properties ├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── change-request.md │ └── bug_report.md └── workflows │ └── static.yml ├── cmake └── QuotientConfig.cmake.in ├── quotest ├── .valgrind.supp └── CMakeLists.txt └── SECURITY.md /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quotient-im/libQuotient/HEAD/logo.png -------------------------------------------------------------------------------- /gtad/preamble.mustache: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | -------------------------------------------------------------------------------- /source_file.template: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: The Quotient Project Contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | -------------------------------------------------------------------------------- /libquotientemojis.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | sas-emoji.json 4 | 5 | 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "gtad/gtad"] 2 | path = gtad/gtad 3 | url = https://github.com/quotient-im/gtad.git 4 | [submodule "doxygen-awesome-css"] 5 | path = doxygen-awesome-css 6 | url = https://github.com/jothepro/doxygen-awesome-css.git 7 | -------------------------------------------------------------------------------- /quotest.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | include(libquotient.pri) 4 | 5 | QT += testlib 6 | 7 | CONFIG *= c++1z warn_on object_parallel_to_source 8 | 9 | windows { CONFIG *= console } 10 | 11 | SOURCES += tests/quotest.cpp 12 | 13 | DISTFILES += \ 14 | .valgrind.supp 15 | -------------------------------------------------------------------------------- /Quotient/events/typingevent.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2017 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "event.h" 7 | 8 | namespace Quotient { 9 | DEFINE_SIMPLE_EVENT(TypingEvent, Event, "m.typing", QStringList, users, "user_ids") 10 | } // namespace Quotient 11 | -------------------------------------------------------------------------------- /Quotient.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=${prefix} 3 | includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ 4 | libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ 5 | 6 | Name: Quotient 7 | Description: A Qt library to write cross-platfrom clients for Matrix 8 | Version: @API_VERSION@ 9 | Cflags: -I${includedir} 10 | Libs: -L${libdir} -lQuotient 11 | -------------------------------------------------------------------------------- /gtad/data.h.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | SPDX-FileCopyrightText: 2020 Kitsune Ral 3 | SPDX-License-Identifier: LGPL-2.1-or-later 4 | }}{{>preamble}} 5 | #pragma once 6 | 7 | #include 8 | {{#imports}} 9 | #include {{_}}{{/imports}} 10 | 11 | namespace Quotient { 12 | {{>define_models}} 13 | } // namespace Quotient 14 | -------------------------------------------------------------------------------- /autotests/testgroupsession.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Carl Schwan 2 | // 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #include 6 | 7 | class TestGroupSession : public QObject 8 | { 9 | Q_OBJECT 10 | 11 | private Q_SLOTS: 12 | void groupSessionPicklingValid(); 13 | void groupSessionCryptoValid(); 14 | }; 15 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=quotient-im_libQuotient 2 | sonar.organization=quotient-im 3 | 4 | # This is the name and version displayed in the SonarCloud UI. 5 | sonar.projectName=libQuotient 6 | sonar.projectVersion=0.8 7 | 8 | sonar.sources=Quotient 9 | 10 | # Encoding of the source code. Default is default system encoding 11 | #sonar.sourceEncoding=UTF-8 12 | -------------------------------------------------------------------------------- /autotests/testolmutility.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Carl Schwan 2 | // 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #include 6 | 7 | class TestOlmUtility : public QObject 8 | { 9 | Q_OBJECT 10 | 11 | private Q_SLOTS: 12 | void canonicalJSON(); 13 | void verifySignedOneTimeKey(); 14 | void validUploadKeysRequest(); 15 | }; 16 | -------------------------------------------------------------------------------- /autotests/testolmsession.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Carl Schwan 2 | // 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #include 6 | 7 | class TestOlmSession : public QObject 8 | { 9 | Q_OBJECT 10 | private Q_SLOTS: 11 | void olmOutboundSessionCreation(); 12 | void olmEncryptDecrypt(); 13 | void correctSessionOrdering(); 14 | }; 15 | -------------------------------------------------------------------------------- /autotests/data/test-threadroot-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "content": { 3 | "body": "Thread root event", 4 | "msgtype": "m.text" 5 | }, 6 | "event_id": "$threadroot:example.org", 7 | "origin_server_ts": 1432735824654, 8 | "room_id": "!test:example.org", 9 | "sender": "@example:example.org", 10 | "type": "m.room.message", 11 | "unsigned": { 12 | "age": 1234 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Quotient/csapi/openid.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "openid.h" 4 | 5 | using namespace Quotient; 6 | 7 | RequestOpenIdTokenJob::RequestOpenIdTokenJob(const QString& userId, const QJsonObject& dontUse) 8 | : BaseJob(HttpVerb::Post, u"RequestOpenIdTokenJob"_s, 9 | makePath("/_matrix/client/v3", "/user/", userId, "/openid/request_token")) 10 | { 11 | setRequestData({ toJson(dontUse) }); 12 | } 13 | -------------------------------------------------------------------------------- /Quotient/events/roomtombstoneevent.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include "roomtombstoneevent.h" 5 | 6 | using namespace Quotient; 7 | 8 | QString RoomTombstoneEvent::serverMessage() const 9 | { 10 | return contentPart("body"_L1); 11 | } 12 | 13 | QString RoomTombstoneEvent::successorRoomId() const 14 | { 15 | return contentPart("replacement_room"_L1); 16 | } 17 | -------------------------------------------------------------------------------- /Quotient/csapi/wellknown.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "wellknown.h" 4 | 5 | using namespace Quotient; 6 | 7 | QUrl GetWellknownJob::makeRequestUrl(const HomeserverData& hsData) 8 | { 9 | return BaseJob::makeRequestUrl(hsData, makePath("/.well-known", "/matrix/client")); 10 | } 11 | 12 | GetWellknownJob::GetWellknownJob() 13 | : BaseJob(HttpVerb::Get, u"GetWellknownJob"_s, makePath("/.well-known", "/matrix/client"), false) 14 | {} 15 | -------------------------------------------------------------------------------- /Quotient/events/directchatevent.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "event.h" 7 | 8 | namespace Quotient { 9 | class QUOTIENT_API DirectChatEvent : public Event { 10 | public: 11 | QUO_EVENT(DirectChatEvent, "m.direct") 12 | 13 | using Event::Event; 14 | 15 | QMultiHash usersToDirectChats() const; 16 | }; 17 | } // namespace Quotient 18 | -------------------------------------------------------------------------------- /Quotient/csapi/refresh.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "refresh.h" 4 | 5 | using namespace Quotient; 6 | 7 | RefreshJob::RefreshJob(const QString& refreshToken) 8 | : BaseJob(HttpVerb::Post, u"RefreshJob"_s, makePath("/_matrix/client/v3", "/refresh"), false) 9 | { 10 | QJsonObject _dataJson; 11 | addParam(_dataJson, "refresh_token"_L1, refreshToken); 12 | setRequestData({ _dataJson }); 13 | addExpectedKey(u"access_token"_s); 14 | } 15 | -------------------------------------------------------------------------------- /Quotient/csapi/voip.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "voip.h" 4 | 5 | using namespace Quotient; 6 | 7 | QUrl GetTurnServerJob::makeRequestUrl(const HomeserverData& hsData) 8 | { 9 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/voip/turnServer")); 10 | } 11 | 12 | GetTurnServerJob::GetTurnServerJob() 13 | : BaseJob(HttpVerb::Get, u"GetTurnServerJob"_s, 14 | makePath("/_matrix/client/v3", "/voip/turnServer")) 15 | {} 16 | -------------------------------------------------------------------------------- /Quotient/csapi/versions.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "versions.h" 4 | 5 | using namespace Quotient; 6 | 7 | QUrl GetVersionsJob::makeRequestUrl(const HomeserverData& hsData) 8 | { 9 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client", "/versions")); 10 | } 11 | 12 | GetVersionsJob::GetVersionsJob() 13 | : BaseJob(HttpVerb::Get, u"GetVersionsJob"_s, makePath("/_matrix/client", "/versions")) 14 | { 15 | addExpectedKey(u"versions"_s); 16 | } 17 | -------------------------------------------------------------------------------- /Quotient/csapi/support.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "support.h" 4 | 5 | using namespace Quotient; 6 | 7 | QUrl GetWellknownSupportJob::makeRequestUrl(const HomeserverData& hsData) 8 | { 9 | return BaseJob::makeRequestUrl(hsData, makePath("/.well-known", "/matrix/support")); 10 | } 11 | 12 | GetWellknownSupportJob::GetWellknownSupportJob() 13 | : BaseJob(HttpVerb::Get, u"GetWellknownSupportJob"_s, 14 | makePath("/.well-known", "/matrix/support"), false) 15 | {} 16 | -------------------------------------------------------------------------------- /Quotient/events/roomtombstoneevent.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "stateevent.h" 7 | 8 | namespace Quotient { 9 | class QUOTIENT_API RoomTombstoneEvent : public StateEvent { 10 | public: 11 | QUO_EVENT(RoomTombstoneEvent, "m.room.tombstone") 12 | 13 | using StateEvent::StateEvent; 14 | 15 | QString serverMessage() const; 16 | QString successorRoomId() const; 17 | }; 18 | } // namespace Quotient 19 | -------------------------------------------------------------------------------- /Quotient/csapi/admin.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "admin.h" 4 | 5 | using namespace Quotient; 6 | 7 | QUrl GetWhoIsJob::makeRequestUrl(const HomeserverData& hsData, const QString& userId) 8 | { 9 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/admin/whois/", userId)); 10 | } 11 | 12 | GetWhoIsJob::GetWhoIsJob(const QString& userId) 13 | : BaseJob(HttpVerb::Get, u"GetWhoIsJob"_s, 14 | makePath("/_matrix/client/v3", "/admin/whois/", userId)) 15 | {} 16 | -------------------------------------------------------------------------------- /Quotient/csapi/whoami.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "whoami.h" 4 | 5 | using namespace Quotient; 6 | 7 | QUrl GetTokenOwnerJob::makeRequestUrl(const HomeserverData& hsData) 8 | { 9 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/account/whoami")); 10 | } 11 | 12 | GetTokenOwnerJob::GetTokenOwnerJob() 13 | : BaseJob(HttpVerb::Get, u"GetTokenOwnerJob"_s, 14 | makePath("/_matrix/client/v3", "/account/whoami")) 15 | { 16 | addExpectedKey(u"user_id"_s); 17 | } 18 | -------------------------------------------------------------------------------- /autotests/data/test-thread1-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "content": { 3 | "body": "Thread reply 1 event", 4 | "msgtype": "m.text", 5 | "m.relates_to": { 6 | "rel_type": "m.thread", 7 | "event_id": "$threadroot:example.org" 8 | } 9 | }, 10 | "event_id": "$thread1:example.org", 11 | "origin_server_ts": 1432735824654, 12 | "room_id": "!test:example.org", 13 | "sender": "@example:example.org", 14 | "type": "m.room.message", 15 | "unsigned": { 16 | "age": 1234 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /autotests/data/test-thread2-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "content": { 3 | "body": "Thread reply 2 event", 4 | "msgtype": "m.text", 5 | "m.relates_to": { 6 | "rel_type": "m.thread", 7 | "event_id": "$threadroot:example.org" 8 | } 9 | }, 10 | "event_id": "$thread2:example.org", 11 | "origin_server_ts": 1432735824654, 12 | "room_id": "!test:example.org", 13 | "sender": "@example:example.org", 14 | "type": "m.room.message", 15 | "unsigned": { 16 | "age": 1234 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Quotient/csapi/kicking.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "kicking.h" 4 | 5 | using namespace Quotient; 6 | 7 | KickJob::KickJob(const QString& roomId, const QString& userId, const QString& reason) 8 | : BaseJob(HttpVerb::Post, u"KickJob"_s, 9 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/kick")) 10 | { 11 | QJsonObject _dataJson; 12 | addParam(_dataJson, "user_id"_L1, userId); 13 | addParam(_dataJson, "reason"_L1, reason); 14 | setRequestData({ _dataJson }); 15 | } 16 | -------------------------------------------------------------------------------- /Quotient/csapi/list_joined_rooms.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "list_joined_rooms.h" 4 | 5 | using namespace Quotient; 6 | 7 | QUrl GetJoinedRoomsJob::makeRequestUrl(const HomeserverData& hsData) 8 | { 9 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/joined_rooms")); 10 | } 11 | 12 | GetJoinedRoomsJob::GetJoinedRoomsJob() 13 | : BaseJob(HttpVerb::Get, u"GetJoinedRoomsJob"_s, makePath("/_matrix/client/v3", "/joined_rooms")) 14 | { 15 | addExpectedKey(u"joined_rooms"_s); 16 | } 17 | -------------------------------------------------------------------------------- /Quotient/csapi/room_send.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "room_send.h" 4 | 5 | using namespace Quotient; 6 | 7 | SendMessageJob::SendMessageJob(const QString& roomId, const QString& eventType, 8 | const QString& txnId, const QJsonObject& content) 9 | : BaseJob(HttpVerb::Put, u"SendMessageJob"_s, 10 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/send/", eventType, "/", txnId)) 11 | { 12 | setRequestData({ toJson(content) }); 13 | addExpectedKey(u"event_id"_s); 14 | } 15 | -------------------------------------------------------------------------------- /Quotient/csapi/room_upgrades.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "room_upgrades.h" 4 | 5 | using namespace Quotient; 6 | 7 | UpgradeRoomJob::UpgradeRoomJob(const QString& roomId, const QString& newVersion) 8 | : BaseJob(HttpVerb::Post, u"UpgradeRoomJob"_s, 9 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/upgrade")) 10 | { 11 | QJsonObject _dataJson; 12 | addParam(_dataJson, "new_version"_L1, newVersion); 13 | setRequestData({ _dataJson }); 14 | addExpectedKey(u"replacement_room"_s); 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # usual build directory names 2 | build 3 | build_dir 4 | 5 | # IDE project files/directories 6 | *.kdev4 7 | .directory 8 | *.user* 9 | .idea 10 | 11 | # qmake derivatives 12 | Makefile* 13 | object_script.* 14 | .qmake* 15 | debug/ 16 | release/ 17 | 18 | # CMake derivatives and user data 19 | CMakeCache.txt 20 | cmake_install.cmake 21 | Makefile 22 | Quotient_autogen/ 23 | .cmake/ 24 | tests/.cmake/ 25 | autotests/synapse-data 26 | CMakeUserPresets.json 27 | 28 | # clangd 29 | .cache/ 30 | compile_commands.json 31 | 32 | # Created by doxygen 33 | html/ 34 | latex/ 35 | -------------------------------------------------------------------------------- /Quotient/csapi/capabilities.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "capabilities.h" 4 | 5 | using namespace Quotient; 6 | 7 | QUrl GetCapabilitiesJob::makeRequestUrl(const HomeserverData& hsData) 8 | { 9 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/capabilities")); 10 | } 11 | 12 | GetCapabilitiesJob::GetCapabilitiesJob() 13 | : BaseJob(HttpVerb::Get, u"GetCapabilitiesJob"_s, 14 | makePath("/_matrix/client/v3", "/capabilities")) 15 | { 16 | addExpectedKey(u"capabilities"_s); 17 | } 18 | -------------------------------------------------------------------------------- /Quotient/csapi/inviting.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "inviting.h" 4 | 5 | using namespace Quotient; 6 | 7 | InviteUserJob::InviteUserJob(const QString& roomId, const QString& userId, const QString& reason) 8 | : BaseJob(HttpVerb::Post, u"InviteUserJob"_s, 9 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/invite")) 10 | { 11 | QJsonObject _dataJson; 12 | addParam(_dataJson, "user_id"_L1, userId); 13 | addParam(_dataJson, "reason"_L1, reason); 14 | setRequestData({ _dataJson }); 15 | } 16 | -------------------------------------------------------------------------------- /Quotient/events/redactionevent.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2017 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "roomevent.h" 7 | 8 | namespace Quotient { 9 | class QUOTIENT_API RedactionEvent : public RoomEvent { 10 | public: 11 | QUO_EVENT(RedactionEvent, "m.room.redaction") 12 | 13 | using RoomEvent::RoomEvent; 14 | 15 | QString redactedEvent() const 16 | { 17 | return fullJson()["redacts"_L1].toString(); 18 | } 19 | QUO_CONTENT_GETTER(QString, reason) 20 | }; 21 | } // namespace Quotient 22 | -------------------------------------------------------------------------------- /Quotient/thread.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include "quotient_export.h" 9 | 10 | namespace Quotient { 11 | 12 | class RoomEvent; 13 | 14 | class QUOTIENT_API Thread { 15 | public: 16 | QString threadRootId = {}; 17 | QString latestEventId = {}; 18 | int size = 0; 19 | bool localUserParticipated = {}; 20 | 21 | bool addEvent(const RoomEvent* event, bool isLatest, bool isLocalUser); 22 | }; 23 | 24 | } // namespace Quotient 25 | -------------------------------------------------------------------------------- /Quotient/csapi/login_token.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "login_token.h" 4 | 5 | using namespace Quotient; 6 | 7 | GenerateLoginTokenJob::GenerateLoginTokenJob(const std::optional& auth) 8 | : BaseJob(HttpVerb::Post, u"GenerateLoginTokenJob"_s, 9 | makePath("/_matrix/client/v1", "/login/get_token")) 10 | { 11 | QJsonObject _dataJson; 12 | addParam(_dataJson, "auth"_L1, auth); 13 | setRequestData({ _dataJson }); 14 | addExpectedKey(u"login_token"_s); 15 | addExpectedKey(u"expires_in_ms"_s); 16 | } 17 | -------------------------------------------------------------------------------- /Quotient/csapi/redaction.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "redaction.h" 4 | 5 | using namespace Quotient; 6 | 7 | RedactEventJob::RedactEventJob(const QString& roomId, const QString& eventId, const QString& txnId, 8 | const QString& reason) 9 | : BaseJob(HttpVerb::Put, u"RedactEventJob"_s, 10 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/redact/", eventId, "/", txnId)) 11 | { 12 | QJsonObject _dataJson; 13 | addParam(_dataJson, "reason"_L1, reason); 14 | setRequestData({ _dataJson }); 15 | } 16 | -------------------------------------------------------------------------------- /Quotient/csapi/to_device.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "to_device.h" 4 | 5 | using namespace Quotient; 6 | 7 | SendToDeviceJob::SendToDeviceJob(const QString& eventType, const QString& txnId, 8 | const QHash>& messages) 9 | : BaseJob(HttpVerb::Put, u"SendToDeviceJob"_s, 10 | makePath("/_matrix/client/v3", "/sendToDevice/", eventType, "/", txnId)) 11 | { 12 | QJsonObject _dataJson; 13 | addParam(_dataJson, "messages"_L1, messages); 14 | setRequestData({ _dataJson }); 15 | } 16 | -------------------------------------------------------------------------------- /Quotient/csapi/room_state.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "room_state.h" 4 | 5 | using namespace Quotient; 6 | 7 | SetRoomStateWithKeyJob::SetRoomStateWithKeyJob(const QString& roomId, const QString& eventType, 8 | const QString& stateKey, const QJsonObject& content) 9 | : BaseJob(HttpVerb::Put, u"SetRoomStateWithKeyJob"_s, 10 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/state/", eventType, "/", stateKey)) 11 | { 12 | setRequestData({ toJson(content) }); 13 | addExpectedKey(u"event_id"_s); 14 | } 15 | -------------------------------------------------------------------------------- /Quotient/csapi/typing.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "typing.h" 4 | 5 | using namespace Quotient; 6 | 7 | SetTypingJob::SetTypingJob(const QString& userId, const QString& roomId, bool typing, 8 | std::optional timeout) 9 | : BaseJob(HttpVerb::Put, u"SetTypingJob"_s, 10 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/typing/", userId)) 11 | { 12 | QJsonObject _dataJson; 13 | addParam(_dataJson, "typing"_L1, typing); 14 | addParam(_dataJson, "timeout"_L1, timeout); 15 | setRequestData({ _dataJson }); 16 | } 17 | -------------------------------------------------------------------------------- /Quotient/csapi/receipts.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "receipts.h" 4 | 5 | using namespace Quotient; 6 | 7 | PostReceiptJob::PostReceiptJob(const QString& roomId, const QString& receiptType, 8 | const QString& eventId, const QString& threadId) 9 | : BaseJob(HttpVerb::Post, u"PostReceiptJob"_s, 10 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/receipt/", receiptType, "/", 11 | eventId)) 12 | { 13 | QJsonObject _dataJson; 14 | addParam(_dataJson, "thread_id"_L1, threadId); 15 | setRequestData({ _dataJson }); 16 | } 17 | -------------------------------------------------------------------------------- /autotests/register-users.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ ! -x register_new_matrix_user ]; then 6 | echo "This script is meant to be executed in a Synapse container" 7 | fi 8 | 9 | echo Register alice 10 | for i in 1 2 3 4 5 6 7 8 9; do 11 | register_new_matrix_user --admin -u alice$i -p secret -c /data/homeserver.yaml https://localhost:8008 12 | done 13 | echo Register bob 14 | for i in 1 2 3; do 15 | register_new_matrix_user --admin -u bob$i -p secret -c /data/homeserver.yaml https://localhost:8008 16 | done 17 | echo Register carl 18 | register_new_matrix_user --admin -u carl -p secret -c /data/homeserver.yaml https://localhost:8008 19 | 20 | -------------------------------------------------------------------------------- /Quotient/csapi/users.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "users.h" 4 | 5 | using namespace Quotient; 6 | 7 | SearchUserDirectoryJob::SearchUserDirectoryJob(const QString& searchTerm, std::optional limit) 8 | : BaseJob(HttpVerb::Post, u"SearchUserDirectoryJob"_s, 9 | makePath("/_matrix/client/v3", "/user_directory/search")) 10 | { 11 | QJsonObject _dataJson; 12 | addParam(_dataJson, "search_term"_L1, searchTerm); 13 | addParam(_dataJson, "limit"_L1, limit); 14 | setRequestData({ _dataJson }); 15 | addExpectedKey(u"results"_s); 16 | addExpectedKey(u"limited"_s); 17 | } 18 | -------------------------------------------------------------------------------- /Quotient/csapi/report_content.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "report_content.h" 4 | 5 | using namespace Quotient; 6 | 7 | ReportContentJob::ReportContentJob(const QString& roomId, const QString& eventId, 8 | std::optional score, const QString& reason) 9 | : BaseJob(HttpVerb::Post, u"ReportContentJob"_s, 10 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/report/", eventId)) 11 | { 12 | QJsonObject _dataJson; 13 | addParam(_dataJson, "score"_L1, score); 14 | addParam(_dataJson, "reason"_L1, reason); 15 | setRequestData({ _dataJson }); 16 | } 17 | -------------------------------------------------------------------------------- /Quotient/csapi/appservice_room_directory.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "appservice_room_directory.h" 4 | 5 | using namespace Quotient; 6 | 7 | UpdateAppserviceRoomDirectoryVisibilityJob::UpdateAppserviceRoomDirectoryVisibilityJob( 8 | const QString& networkId, const QString& roomId, const QString& visibility) 9 | : BaseJob(HttpVerb::Put, u"UpdateAppserviceRoomDirectoryVisibilityJob"_s, 10 | makePath("/_matrix/client/v3", "/directory/list/appservice/", networkId, "/", roomId), 11 | false) 12 | { 13 | QJsonObject _dataJson; 14 | addParam(_dataJson, "visibility"_L1, visibility); 15 | setRequestData({ _dataJson }); 16 | } 17 | -------------------------------------------------------------------------------- /Quotient/csapi/search.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "search.h" 4 | 5 | using namespace Quotient; 6 | 7 | auto queryToSearch(const QString& nextBatch) 8 | { 9 | QUrlQuery _q; 10 | addParam(_q, u"next_batch"_s, nextBatch); 11 | return _q; 12 | } 13 | 14 | SearchJob::SearchJob(const Categories& searchCategories, const QString& nextBatch) 15 | : BaseJob(HttpVerb::Post, u"SearchJob"_s, makePath("/_matrix/client/v3", "/search"), 16 | queryToSearch(nextBatch)) 17 | { 18 | QJsonObject _dataJson; 19 | addParam(_dataJson, "search_categories"_L1, searchCategories); 20 | setRequestData({ _dataJson }); 21 | addExpectedKey(u"search_categories"_s); 22 | } 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/change-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Change request 3 | about: Suggest an idea or improvement for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your request related to a problem?** 11 | 12 | 13 | **Describe the suggested change/improvement you'd like** 14 | 15 | 16 | **Describe alternatives you've considered** 17 | 18 | 19 | **Additional context** 20 | 21 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/tag.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | struct QUOTIENT_API Tag { 10 | //! A number in a range `[0,1]` describing a relative 11 | //! position of the room under the given tag. 12 | std::optional order{}; 13 | }; 14 | 15 | template <> 16 | struct JsonObjectConverter { 17 | static void dumpTo(QJsonObject& jo, const Tag& pod) 18 | { 19 | addParam(jo, "order"_L1, pod.order); 20 | } 21 | static void fillFrom(const QJsonObject& jo, Tag& pod) 22 | { 23 | fillFromJson(jo.value("order"_L1), pod.order); 24 | } 25 | }; 26 | 27 | } // namespace Quotient 28 | -------------------------------------------------------------------------------- /Quotient/csapi/read_markers.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "read_markers.h" 4 | 5 | using namespace Quotient; 6 | 7 | SetReadMarkerJob::SetReadMarkerJob(const QString& roomId, const QString& fullyRead, 8 | const QString& read, const QString& readPrivate) 9 | : BaseJob(HttpVerb::Post, u"SetReadMarkerJob"_s, 10 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/read_markers")) 11 | { 12 | QJsonObject _dataJson; 13 | addParam(_dataJson, "m.fully_read"_L1, fullyRead); 14 | addParam(_dataJson, "m.read"_L1, read); 15 | addParam(_dataJson, "m.read.private"_L1, readPrivate); 16 | setRequestData({ _dataJson }); 17 | } 18 | -------------------------------------------------------------------------------- /Quotient/csapi/logout.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "logout.h" 4 | 5 | using namespace Quotient; 6 | 7 | QUrl LogoutJob::makeRequestUrl(const HomeserverData& hsData) 8 | { 9 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/logout")); 10 | } 11 | 12 | LogoutJob::LogoutJob() 13 | : BaseJob(HttpVerb::Post, u"LogoutJob"_s, makePath("/_matrix/client/v3", "/logout")) 14 | {} 15 | 16 | QUrl LogoutAllJob::makeRequestUrl(const HomeserverData& hsData) 17 | { 18 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/logout/all")); 19 | } 20 | 21 | LogoutAllJob::LogoutAllJob() 22 | : BaseJob(HttpVerb::Post, u"LogoutAllJob"_s, makePath("/_matrix/client/v3", "/logout/all")) 23 | {} 24 | -------------------------------------------------------------------------------- /Quotient/networksettings.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2017 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include "networksettings.h" 5 | 6 | using namespace Quotient; 7 | 8 | void NetworkSettings::setupApplicationProxy() const 9 | { 10 | QNetworkProxy::setApplicationProxy( 11 | { proxyType(), proxyHostName(), proxyPort() }); 12 | } 13 | 14 | QUO_DEFINE_SETTING(NetworkSettings, QNetworkProxy::ProxyType, proxyType, 15 | "proxy_type", QNetworkProxy::DefaultProxy, setProxyType) 16 | QUO_DEFINE_SETTING(NetworkSettings, QString, proxyHostName, "proxy_hostname", 17 | {}, setProxyHostName) 18 | QUO_DEFINE_SETTING(NetworkSettings, quint16, proxyPort, "proxy_port", -1, 19 | setProxyPort) 20 | -------------------------------------------------------------------------------- /Quotient/thread.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include "thread.h" 5 | 6 | #include "events/roomevent.h" 7 | 8 | using namespace Quotient; 9 | 10 | bool Thread::addEvent(const RoomEvent* event, bool isLatest, bool isLocalUser) 11 | { 12 | // Note: the root event may not have the thread aggregation in its unsigned on creation 13 | // hence checking the event id. 14 | if (event->threadRootEventId() != threadRootId && event->id() != threadRootId) { 15 | return false; 16 | } 17 | if (isLatest || latestEventId.isEmpty()) { 18 | latestEventId = event->id(); 19 | } 20 | ++size; 21 | localUserParticipated |= isLocalUser; 22 | 23 | return true; 24 | } 25 | -------------------------------------------------------------------------------- /Quotient/events/roomcreateevent.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "stateevent.h" 7 | #include 8 | 9 | namespace Quotient { 10 | class QUOTIENT_API RoomCreateEvent : public StateEvent { 11 | public: 12 | QUO_EVENT(RoomCreateEvent, "m.room.create") 13 | 14 | using StateEvent::StateEvent; 15 | 16 | struct Predecessor { 17 | QString roomId; 18 | QString eventId; 19 | }; 20 | 21 | bool isFederated() const; 22 | QString version() const; 23 | Predecessor predecessor() const; 24 | bool isUpgrade() const; 25 | RoomType roomType() const; 26 | QStringList additionalCreators() const; 27 | }; 28 | } // namespace Quotient 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behaviour, and the description of the actual result: 15 | 1. 16 | 2. 17 | 3. 18 | 19 | **Expected behavior** 20 | 21 | 22 | **Is it environment-specific?** 23 | 24 | - OS: [e.g. Windows 11] 25 | - Version of the library [e.g. 0.6.10] 26 | - Linkage: static, dynamic, any 27 | 28 | **Additional context** 29 | 30 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/wellknown/homeserver.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | //! Used by clients to discover homeserver information. 9 | struct QUOTIENT_API HomeserverInformation { 10 | //! The base URL for the homeserver for client-server connections. 11 | QUrl baseUrl; 12 | }; 13 | 14 | template <> 15 | struct JsonObjectConverter { 16 | static void dumpTo(QJsonObject& jo, const HomeserverInformation& pod) 17 | { 18 | addParam(jo, "base_url"_L1, pod.baseUrl); 19 | } 20 | static void fillFrom(const QJsonObject& jo, HomeserverInformation& pod) 21 | { 22 | fillFromJson(jo.value("base_url"_L1), pod.baseUrl); 23 | } 24 | }; 25 | 26 | } // namespace Quotient 27 | -------------------------------------------------------------------------------- /Quotient/expected.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | namespace Quotient { 9 | 10 | template 11 | requires (!std::is_same_v) 12 | class [[deprecated("Use std::expected instead")]] Expected : public std::expected { 13 | public: 14 | using std::expected::expected; 15 | 16 | template X> 17 | explicit(false) Expected(X&& x) : std::expected(std::unexpect, std::forward(x)) 18 | {} 19 | 20 | T&& move_value_or(T&& fallback) 21 | { 22 | if (this->has_value()) 23 | return std::move(this->value()); 24 | return std::move(fallback); 25 | } 26 | }; 27 | 28 | } // namespace Quotient 29 | -------------------------------------------------------------------------------- /Quotient/e2ee/qolmmessage.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Alexey Andreyev 2 | // 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #include "qolmmessage.h" 6 | 7 | #include 8 | 9 | using namespace Quotient; 10 | 11 | QOlmMessage::QOlmMessage(QByteArray ciphertext, QOlmMessage::Type type) 12 | : QByteArray(std::move(ciphertext)) 13 | , m_messageType(type) 14 | { 15 | Q_ASSERT_X(!isEmpty(), "olm message", "Ciphertext is empty"); 16 | } 17 | 18 | QOlmMessage::Type QOlmMessage::type() const 19 | { 20 | return m_messageType; 21 | } 22 | 23 | QByteArray QOlmMessage::toCiphertext() const 24 | { 25 | return SLICE(*this, QByteArray); 26 | } 27 | 28 | QOlmMessage QOlmMessage::fromCiphertext(const QByteArray &ciphertext) 29 | { 30 | return QOlmMessage(ciphertext, QOlmMessage::General); 31 | } 32 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/room_key_backup.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace Quotient { 10 | //! The backed up keys for a room. 11 | struct QUOTIENT_API RoomKeyBackup { 12 | //! A map of session IDs to key data. 13 | QHash sessions; 14 | }; 15 | 16 | template <> 17 | struct JsonObjectConverter { 18 | static void dumpTo(QJsonObject& jo, const RoomKeyBackup& pod) 19 | { 20 | addParam(jo, "sessions"_L1, pod.sessions); 21 | } 22 | static void fillFrom(const QJsonObject& jo, RoomKeyBackup& pod) 23 | { 24 | fillFromJson(jo.value("sessions"_L1), pod.sessions); 25 | } 26 | }; 27 | 28 | } // namespace Quotient 29 | -------------------------------------------------------------------------------- /Quotient/csapi/third_party_membership.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "third_party_membership.h" 4 | 5 | using namespace Quotient; 6 | 7 | InviteBy3PIDJob::InviteBy3PIDJob(const QString& roomId, const QString& idServer, 8 | const QString& idAccessToken, const QString& medium, 9 | const QString& address) 10 | : BaseJob(HttpVerb::Post, u"InviteBy3PIDJob"_s, 11 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/invite")) 12 | { 13 | QJsonObject _dataJson; 14 | addParam(_dataJson, "id_server"_L1, idServer); 15 | addParam(_dataJson, "id_access_token"_L1, idAccessToken); 16 | addParam(_dataJson, "medium"_L1, medium); 17 | addParam(_dataJson, "address"_L1, address); 18 | setRequestData({ _dataJson }); 19 | } 20 | -------------------------------------------------------------------------------- /autotests/testolmaccount.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Carl Schwan 2 | // 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #include 6 | #include 7 | 8 | namespace Quotient { 9 | class Connection; 10 | } 11 | 12 | class TestOlmAccount : public QObject 13 | { 14 | Q_OBJECT 15 | 16 | private Q_SLOTS: 17 | void pickleUnpickledTest(); 18 | void identityKeysValid(); 19 | void signatureValid(); 20 | void oneTimeKeysValid(); 21 | //void removeOneTimeKeys(); 22 | void deviceKeys(); 23 | void encryptedFile(); 24 | void uploadIdentityKey(); 25 | void uploadOneTimeKeys(); 26 | void uploadSignedOneTimeKeys(); 27 | void uploadKeys(); 28 | void queryTest(); 29 | void claimKeys(); 30 | void claimMultipleKeys(); 31 | void enableEncryption(); 32 | }; 33 | -------------------------------------------------------------------------------- /Quotient/events/directchatevent.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include "directchatevent.h" 5 | 6 | using namespace Quotient; 7 | 8 | QMultiHash DirectChatEvent::usersToDirectChats() const 9 | { 10 | QMultiHash result; 11 | const auto& json = contentJson(); 12 | for (auto it = json.begin(); it != json.end(); ++it) { 13 | // Beware of range-for's over temporary returned from temporary 14 | // (see the bottom of 15 | // http://en.cppreference.com/w/cpp/language/range-for#Explanation) 16 | const auto roomIds = it.value().toArray(); 17 | for (const auto& roomIdValue : roomIds) 18 | result.insert(it.key(), roomIdValue.toString()); 19 | } 20 | return result; 21 | } 22 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/wellknown/identity_server.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | //! Used by clients to discover identity server information. 9 | struct QUOTIENT_API IdentityServerInformation { 10 | //! The base URL for the identity server for client-server connections. 11 | QUrl baseUrl; 12 | }; 13 | 14 | template <> 15 | struct JsonObjectConverter { 16 | static void dumpTo(QJsonObject& jo, const IdentityServerInformation& pod) 17 | { 18 | addParam(jo, "base_url"_L1, pod.baseUrl); 19 | } 20 | static void fillFrom(const QJsonObject& jo, IdentityServerInformation& pod) 21 | { 22 | fillFromJson(jo.value("base_url"_L1), pod.baseUrl); 23 | } 24 | }; 25 | 26 | } // namespace Quotient 27 | -------------------------------------------------------------------------------- /Quotient/networksettings.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2017 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "settings.h" 7 | 8 | #include 9 | 10 | Q_DECLARE_METATYPE(QNetworkProxy::ProxyType) 11 | 12 | namespace Quotient { 13 | class QUOTIENT_API NetworkSettings : public SettingsGroup { 14 | Q_OBJECT 15 | QUO_DECLARE_SETTING(QNetworkProxy::ProxyType, proxyType, setProxyType) 16 | QUO_DECLARE_SETTING(QString, proxyHostName, setProxyHostName) 17 | QUO_DECLARE_SETTING(quint16, proxyPort, setProxyPort) 18 | Q_PROPERTY(QString proxyHost READ proxyHostName WRITE setProxyHostName) 19 | public: 20 | explicit NetworkSettings() : SettingsGroup(u"Network"_s) {} 21 | 22 | Q_INVOKABLE void setupApplicationProxy() const; 23 | }; 24 | } // namespace Quotient 25 | -------------------------------------------------------------------------------- /Quotient/mxcreply.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "util.h" 7 | 8 | #include "events/filesourceinfo.h" 9 | 10 | #include 11 | 12 | namespace Quotient { 13 | class QUOTIENT_API MxcReply : public QNetworkReply 14 | { 15 | Q_OBJECT 16 | public: 17 | explicit MxcReply(); 18 | explicit MxcReply(QNetworkReply* reply, 19 | const EncryptedFileMetadata& fileMetadata); 20 | 21 | qint64 bytesAvailable() const override; 22 | 23 | public Q_SLOTS: 24 | void abort() override; 25 | 26 | protected: 27 | qint64 readData(char* data, qint64 maxlen) override; 28 | void ignoreSslErrorsImplementation(const QList &) override; 29 | 30 | private: 31 | class Private; 32 | ImplPtr d; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /cmake/QuotientConfig.cmake.in: -------------------------------------------------------------------------------- 1 | include(CMakeFindDependencyMacro) 2 | 3 | find_dependency(@Qt@Core) 4 | if (@Qt@Core_VERSION VERSION_GREATER_EQUAL 6.10) 5 | find_dependency(@Qt@CorePrivate) 6 | endif() 7 | find_dependency(@Qt@Gui) 8 | find_dependency(@Qt@Network) 9 | find_dependency(@Qt@Keychain) 10 | find_dependency(Olm) 11 | find_dependency(OpenSSL) 12 | find_dependency(@Qt@Sql) 13 | 14 | if(NOT @BUILD_SHARED_LIBS@ AND @Qt@Core_VERSION VERSION_GREATER_EQUAL 6.10) 15 | find_dependency(@Qt@CorePrivate) 16 | endif() 17 | 18 | include("${CMAKE_CURRENT_LIST_DIR}/@QUOTIENT_LIB_NAME@Targets.cmake") 19 | 20 | if (NOT QUOTIENT_FORCE_NAMESPACED_INCLUDES) 21 | get_target_property(_include_dir @QUOTIENT_LIB_NAME@ INTERFACE_INCLUDE_DIRECTORIES) 22 | list(APPEND _include_dir "${_include_dir}/Quotient") 23 | set_target_properties(@QUOTIENT_LIB_NAME@ PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${_include_dir}") 24 | endif() 25 | -------------------------------------------------------------------------------- /Quotient/csapi/room_upgrades.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Upgrades a room to a new room version. 10 | //! 11 | //! Upgrades the given room to a particular room version. 12 | class QUOTIENT_API UpgradeRoomJob : public BaseJob { 13 | public: 14 | //! \param roomId 15 | //! The ID of the room to upgrade. 16 | //! 17 | //! \param newVersion 18 | //! The new version for the room. 19 | explicit UpgradeRoomJob(const QString& roomId, const QString& newVersion); 20 | 21 | // Result properties 22 | 23 | //! The ID of the new room. 24 | QString replacementRoom() const { return loadFromJson("replacement_room"_L1); } 25 | }; 26 | 27 | inline auto collectResponse(const UpgradeRoomJob* job) { return job->replacementRoom(); } 28 | 29 | } // namespace Quotient 30 | -------------------------------------------------------------------------------- /Quotient/csapi/knocking.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "knocking.h" 4 | 5 | using namespace Quotient; 6 | 7 | auto queryToKnockRoom(const QStringList& serverName, const QStringList& via) 8 | { 9 | QUrlQuery _q; 10 | addParam(_q, u"server_name"_s, serverName); 11 | addParam(_q, u"via"_s, via); 12 | return _q; 13 | } 14 | 15 | KnockRoomJob::KnockRoomJob(const QString& roomIdOrAlias, const QStringList& serverName, 16 | const QStringList& via, const QString& reason) 17 | : BaseJob(HttpVerb::Post, u"KnockRoomJob"_s, 18 | makePath("/_matrix/client/v3", "/knock/", roomIdOrAlias), 19 | queryToKnockRoom(serverName, via)) 20 | { 21 | QJsonObject _dataJson; 22 | addParam(_dataJson, "reason"_L1, reason); 23 | setRequestData({ _dataJson }); 24 | addExpectedKey(u"room_id"_s); 25 | } 26 | -------------------------------------------------------------------------------- /Quotient/events/roomavatarevent.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2017 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "eventcontent.h" 7 | #include "stateevent.h" 8 | 9 | namespace Quotient { 10 | class QUOTIENT_API RoomAvatarEvent 11 | : public KeylessStateEventBase { 13 | // It's a bit of an overkill to use a full-fledged ImageContent 14 | // because in reality m.room.avatar usually only has a single URL, 15 | // without a thumbnail. But The Spec says there be thumbnails, and 16 | // we follow The Spec (and ImageContent is very convenient to reuse here). 17 | public: 18 | QUO_EVENT(RoomAvatarEvent, "m.room.avatar") 19 | using KeylessStateEventBase::KeylessStateEventBase; 20 | 21 | QUrl url() const { return content().url(); } 22 | }; 23 | } // namespace Quotient 24 | -------------------------------------------------------------------------------- /Quotient/csapi/banning.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "banning.h" 4 | 5 | using namespace Quotient; 6 | 7 | BanJob::BanJob(const QString& roomId, const QString& userId, const QString& reason) 8 | : BaseJob(HttpVerb::Post, u"BanJob"_s, makePath("/_matrix/client/v3", "/rooms/", roomId, "/ban")) 9 | { 10 | QJsonObject _dataJson; 11 | addParam(_dataJson, "user_id"_L1, userId); 12 | addParam(_dataJson, "reason"_L1, reason); 13 | setRequestData({ _dataJson }); 14 | } 15 | 16 | UnbanJob::UnbanJob(const QString& roomId, const QString& userId, const QString& reason) 17 | : BaseJob(HttpVerb::Post, u"UnbanJob"_s, 18 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/unban")) 19 | { 20 | QJsonObject _dataJson; 21 | addParam(_dataJson, "user_id"_L1, userId); 22 | addParam(_dataJson, "reason"_L1, reason); 23 | setRequestData({ _dataJson }); 24 | } 25 | -------------------------------------------------------------------------------- /Quotient/events/receiptevent.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "event.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace Quotient { 12 | struct UserTimestamp { 13 | QString userId; 14 | QDateTime timestamp; 15 | }; 16 | struct ReceiptsForEvent { 17 | QString evtId; 18 | QVector receipts; 19 | }; 20 | using EventsWithReceipts = QVector; 21 | 22 | template <> 23 | QUOTIENT_API EventsWithReceipts fromJson(const QJsonObject& json); 24 | QUOTIENT_API QJsonObject toJson(const EventsWithReceipts& ewrs); 25 | 26 | class QUOTIENT_API ReceiptEvent 27 | : public EventTemplate { 28 | public: 29 | QUO_EVENT(ReceiptEvent, "m.receipt") 30 | using EventTemplate::EventTemplate; 31 | }; 32 | } // namespace Quotient 33 | -------------------------------------------------------------------------------- /Quotient/csapi/leaving.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "leaving.h" 4 | 5 | using namespace Quotient; 6 | 7 | LeaveRoomJob::LeaveRoomJob(const QString& roomId, const QString& reason) 8 | : BaseJob(HttpVerb::Post, u"LeaveRoomJob"_s, 9 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/leave")) 10 | { 11 | QJsonObject _dataJson; 12 | addParam(_dataJson, "reason"_L1, reason); 13 | setRequestData({ _dataJson }); 14 | } 15 | 16 | QUrl ForgetRoomJob::makeRequestUrl(const HomeserverData& hsData, const QString& roomId) 17 | { 18 | return BaseJob::makeRequestUrl(hsData, 19 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/forget")); 20 | } 21 | 22 | ForgetRoomJob::ForgetRoomJob(const QString& roomId) 23 | : BaseJob(HttpVerb::Post, u"ForgetRoomJob"_s, 24 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/forget")) 25 | {} 26 | -------------------------------------------------------------------------------- /Quotient/eventitem.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include "eventitem.h" 5 | 6 | #include "events/roomavatarevent.h" 7 | #include "events/roommessageevent.h" 8 | 9 | using namespace Quotient; 10 | 11 | void PendingEventItem::setFileUploaded(const FileSourceInfo& uploadedFileData) 12 | { 13 | if (auto* rme = getAs()) 14 | rme->updateFileSourceInfo(uploadedFileData); 15 | 16 | if (auto* rae = getAs()) { 17 | rae->editContent([&uploadedFileData](EventContent::FileInfo& fi) { 18 | fi.source = uploadedFileData; 19 | }); 20 | } 21 | setStatus(EventStatus::FileUploaded); 22 | } 23 | 24 | // Not exactly sure why but this helps with the linker not finding 25 | // Quotient::EventStatus::staticMetaObject when building Quaternion 26 | #include "moc_eventitem.cpp" // NOLINT(bugprone-suspicious-include) 27 | -------------------------------------------------------------------------------- /Quotient/converters.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include "converters.h" 5 | 6 | #include "logging_categories_p.h" 7 | 8 | #include 9 | 10 | void Quotient::_impl::reportEnumOutOfBounds(uint32_t v, const char* enumTypeName) 11 | { 12 | qCritical(MAIN).noquote() 13 | << "Value" << v << "is out of bounds for enumeration" << enumTypeName; 14 | } 15 | 16 | QJsonValue Quotient::JsonConverter::dump(const QVariant& v) 17 | { 18 | return QJsonValue::fromVariant(v); 19 | } 20 | 21 | QVariant Quotient::JsonConverter::load(const QJsonValue& jv) 22 | { 23 | return jv.toVariant(); 24 | } 25 | 26 | QJsonObject Quotient::toJson(const QVariantHash& vh) 27 | { 28 | return QJsonObject::fromVariantHash(vh); 29 | } 30 | 31 | template<> 32 | QVariantHash Quotient::fromJson(const QJsonValue& jv) 33 | { 34 | return jv.toObject().toVariantHash(); 35 | } 36 | -------------------------------------------------------------------------------- /Quotient/csapi/voip.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Obtain TURN server credentials. 10 | //! 11 | //! This API provides credentials for the client to use when initiating 12 | //! calls. 13 | class QUOTIENT_API GetTurnServerJob : public BaseJob { 14 | public: 15 | explicit GetTurnServerJob(); 16 | 17 | //! \brief Construct a URL without creating a full-fledged job object 18 | //! 19 | //! This function can be used when a URL for GetTurnServerJob 20 | //! is necessary but the job itself isn't. 21 | static QUrl makeRequestUrl(const HomeserverData& hsData); 22 | 23 | // Result properties 24 | 25 | //! The TURN server credentials. 26 | QJsonObject data() const { return fromJson(jsonData()); } 27 | }; 28 | 29 | inline auto collectResponse(const GetTurnServerJob* job) { return job->data(); } 30 | 31 | } // namespace Quotient 32 | -------------------------------------------------------------------------------- /Quotient/events/single_key_value.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Quotient { 6 | 7 | namespace EventContent { 8 | template 9 | struct SingleKeyValue { 10 | Q_IMPLICIT SingleKeyValue(const T& v = {}) : value(v) {} 11 | Q_IMPLICIT SingleKeyValue(T&& v) : value(std::move(v)) {} 12 | T value; 13 | }; 14 | } // namespace EventContent 15 | 16 | template 17 | struct JsonConverter> { 18 | using content_type = EventContent::SingleKeyValue; 19 | static content_type load(const QJsonValue& jv) 20 | { 21 | return fromJson(jv.toObject().value(JsonKey)); 22 | } 23 | static QJsonObject dump(const content_type& c) 24 | { 25 | return { { JsonKey, toJson(c.value) } }; 26 | } 27 | static inline const auto JsonKey = toSnakeCase(KeyStr); 28 | }; 29 | } // namespace Quotient 30 | -------------------------------------------------------------------------------- /Quotient/csapi/filter.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "filter.h" 4 | 5 | using namespace Quotient; 6 | 7 | DefineFilterJob::DefineFilterJob(const QString& userId, const Filter& filter) 8 | : BaseJob(HttpVerb::Post, u"DefineFilterJob"_s, 9 | makePath("/_matrix/client/v3", "/user/", userId, "/filter")) 10 | { 11 | setRequestData({ toJson(filter) }); 12 | addExpectedKey(u"filter_id"_s); 13 | } 14 | 15 | QUrl GetFilterJob::makeRequestUrl(const HomeserverData& hsData, const QString& userId, 16 | const QString& filterId) 17 | { 18 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/user/", userId, 19 | "/filter/", filterId)); 20 | } 21 | 22 | GetFilterJob::GetFilterJob(const QString& userId, const QString& filterId) 23 | : BaseJob(HttpVerb::Get, u"GetFilterJob"_s, 24 | makePath("/_matrix/client/v3", "/user/", userId, "/filter/", filterId)) 25 | {} 26 | -------------------------------------------------------------------------------- /Quotient/jobs/requestdata.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | class QJsonObject; 9 | class QJsonArray; 10 | class QJsonDocument; 11 | class QIODevice; 12 | 13 | namespace Quotient { 14 | /** 15 | * A simple wrapper that represents the request body. 16 | * Provides a unified interface to dump an unstructured byte stream 17 | * as well as JSON (and possibly other structures in the future) to 18 | * a QByteArray consumed by QNetworkAccessManager request methods. 19 | */ 20 | class QUOTIENT_API RequestData { 21 | public: 22 | Q_IMPLICIT RequestData(const QByteArray& a = {}); 23 | Q_IMPLICIT RequestData(const QJsonObject& jo); 24 | Q_IMPLICIT RequestData(const QJsonArray& ja); 25 | Q_IMPLICIT RequestData(QIODevice* source); 26 | 27 | QIODevice* source() const { return _source.get(); } 28 | 29 | private: 30 | ImplPtr _source; 31 | }; 32 | } // namespace Quotient 33 | -------------------------------------------------------------------------------- /Quotient/quotient_export.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #ifdef QUOTIENT_STATIC 9 | #define QUOTIENT_API 10 | #define QUOTIENT_HIDDEN 11 | #else 12 | #ifndef QUOTIENT_API 13 | #ifdef BUILDING_SHARED_QUOTIENT // Building this library 14 | #ifdef Q_OS_WIN 15 | #define QUOTIENT_API Q_DECL_EXPORT 16 | #else 17 | // On non-Windows, Q_DECL_EXPORT can apply protected visibility and the current 18 | // code for event types is incompatible with it (see #692). 19 | #define QUOTIENT_API __attribute__((visibility("default"))) 20 | #endif 21 | #else // Using this library 22 | #define QUOTIENT_API Q_DECL_IMPORT 23 | #endif 24 | #endif 25 | 26 | #ifndef QUOTIENT_HIDDEN 27 | #define QUOTIENT_HIDDEN Q_DECL_HIDDEN 28 | #endif 29 | #endif 30 | -------------------------------------------------------------------------------- /Quotient/csapi/list_joined_rooms.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Lists the user's current rooms. 10 | //! 11 | //! This API returns a list of the user's current rooms. 12 | class QUOTIENT_API GetJoinedRoomsJob : public BaseJob { 13 | public: 14 | explicit GetJoinedRoomsJob(); 15 | 16 | //! \brief Construct a URL without creating a full-fledged job object 17 | //! 18 | //! This function can be used when a URL for GetJoinedRoomsJob 19 | //! is necessary but the job itself isn't. 20 | static QUrl makeRequestUrl(const HomeserverData& hsData); 21 | 22 | // Result properties 23 | 24 | //! The ID of each room in which the user has `joined` membership. 25 | QStringList joinedRooms() const { return loadFromJson("joined_rooms"_L1); } 26 | }; 27 | 28 | inline auto collectResponse(const GetJoinedRoomsJob* job) { return job->joinedRooms(); } 29 | 30 | } // namespace Quotient 31 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/user_identifier.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | //! Identification information for a user 9 | struct QUOTIENT_API UserIdentifier { 10 | //! The type of identification. See [Identifier types](/client-server-api/#identifier-types) 11 | //! for supported values and additional property descriptions. 12 | QString type; 13 | 14 | //! Keys dependent on the identification type. 15 | QVariantHash additionalProperties{}; 16 | }; 17 | 18 | template <> 19 | struct JsonObjectConverter { 20 | static void dumpTo(QJsonObject& jo, const UserIdentifier& pod) 21 | { 22 | fillJson(jo, pod.additionalProperties); 23 | addParam(jo, "type"_L1, pod.type); 24 | } 25 | static void fillFrom(QJsonObject jo, UserIdentifier& pod) 26 | { 27 | fillFromJson(jo.take("type"_L1), pod.type); 28 | fromJson(jo, pod.additionalProperties); 29 | } 30 | }; 31 | 32 | } // namespace Quotient 33 | -------------------------------------------------------------------------------- /Quotient/jobs/syncjob.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2016 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "../csapi/definitions/sync_filter.h" 7 | #include "../syncdata.h" 8 | #include "basejob.h" 9 | 10 | namespace Quotient { 11 | class QUOTIENT_API SyncJob : public BaseJob { 12 | public: 13 | static constexpr auto defaultTimeout = std::chrono::seconds(30); 14 | static constexpr auto defaultTimeoutMillis = 15 | std::chrono::milliseconds(defaultTimeout).count(); 16 | 17 | explicit SyncJob(const QString& since = {}, const QString& filter = {}, 18 | int timeout = defaultTimeoutMillis, const QString& presence = {}); 19 | explicit SyncJob(const QString& since, const Filter& filter, int timeout = defaultTimeoutMillis, 20 | const QString& presence = {}); 21 | 22 | SyncData takeData() { return std::move(d); } 23 | 24 | protected: 25 | Status prepareResult() override; 26 | 27 | private: 28 | SyncData d; 29 | }; 30 | } // namespace Quotient 31 | -------------------------------------------------------------------------------- /.github/workflows/static.yml: -------------------------------------------------------------------------------- 1 | name: Deploy docs to Github pages 2 | 3 | on: 4 | push: 5 | branches: ["dev"] 6 | 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | pages: write 12 | id-token: write 13 | 14 | concurrency: 15 | group: "pages" 16 | cancel-in-progress: false 17 | 18 | jobs: 19 | deploy: 20 | environment: 21 | name: github-pages 22 | url: ${{ steps.deployment.outputs.page_url }} 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Install doxygen 26 | run: sudo apt-get -qq install doxygen graphviz 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | - name: Update submodules 30 | run: git submodule update --init 31 | - name: Build docs 32 | run: doxygen 33 | - name: Setup Pages 34 | uses: actions/configure-pages@v5 35 | - name: Upload artifact 36 | uses: actions/upload-pages-artifact@v3 37 | with: 38 | path: 'html' 39 | - name: Deploy to GitHub Pages 40 | id: deployment 41 | uses: actions/deploy-pages@v4 42 | -------------------------------------------------------------------------------- /Quotient/csapi/peeking_events.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "peeking_events.h" 4 | 5 | using namespace Quotient; 6 | 7 | auto queryToPeekEvents(const QString& from, std::optional timeout, const QString& roomId) 8 | { 9 | QUrlQuery _q; 10 | addParam(_q, u"from"_s, from); 11 | addParam(_q, u"timeout"_s, timeout); 12 | addParam(_q, u"room_id"_s, roomId); 13 | return _q; 14 | } 15 | 16 | QUrl PeekEventsJob::makeRequestUrl(const HomeserverData& hsData, const QString& from, 17 | std::optional timeout, const QString& roomId) 18 | { 19 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/events"), 20 | queryToPeekEvents(from, timeout, roomId)); 21 | } 22 | 23 | PeekEventsJob::PeekEventsJob(const QString& from, std::optional timeout, const QString& roomId) 24 | : BaseJob(HttpVerb::Get, u"PeekEventsJob"_s, makePath("/_matrix/client/v3", "/events"), 25 | queryToPeekEvents(from, timeout, roomId)) 26 | {} 27 | -------------------------------------------------------------------------------- /Quotient/events/roomcreateevent.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include "roomcreateevent.h" 5 | 6 | using namespace Quotient; 7 | 8 | bool RoomCreateEvent::isFederated() const 9 | { 10 | return contentPart("m.federate"_L1); 11 | } 12 | 13 | QString RoomCreateEvent::version() const 14 | { 15 | return contentPart("room_version"_L1); 16 | } 17 | 18 | RoomCreateEvent::Predecessor RoomCreateEvent::predecessor() const 19 | { 20 | const auto predJson = contentPart("predecessor"_L1); 21 | return { fromJson(predJson[RoomIdKey]), 22 | fromJson(predJson[EventIdKey]) }; 23 | } 24 | 25 | bool RoomCreateEvent::isUpgrade() const 26 | { 27 | return contentJson().contains("predecessor"_L1); 28 | } 29 | 30 | RoomType RoomCreateEvent::roomType() const 31 | { 32 | return contentPart("type"_L1); 33 | } 34 | 35 | QStringList RoomCreateEvent::additionalCreators() const 36 | { 37 | return contentPart("additional_creators"_L1); 38 | } 39 | -------------------------------------------------------------------------------- /Quotient/csapi/registration_tokens.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "registration_tokens.h" 4 | 5 | using namespace Quotient; 6 | 7 | auto queryToRegistrationTokenValidity(const QString& token) 8 | { 9 | QUrlQuery _q; 10 | addParam(_q, u"token"_s, token); 11 | return _q; 12 | } 13 | 14 | QUrl RegistrationTokenValidityJob::makeRequestUrl(const HomeserverData& hsData, const QString& token) 15 | { 16 | return BaseJob::makeRequestUrl(hsData, 17 | makePath("/_matrix/client/v1", 18 | "/register/m.login.registration_token/validity"), 19 | queryToRegistrationTokenValidity(token)); 20 | } 21 | 22 | RegistrationTokenValidityJob::RegistrationTokenValidityJob(const QString& token) 23 | : BaseJob(HttpVerb::Get, u"RegistrationTokenValidityJob"_s, 24 | makePath("/_matrix/client/v1", "/register/m.login.registration_token/validity"), 25 | queryToRegistrationTokenValidity(token), {}, false) 26 | { 27 | addExpectedKey(u"valid"_s); 28 | } 29 | -------------------------------------------------------------------------------- /Quotient/csapi/presence.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "presence.h" 4 | 5 | using namespace Quotient; 6 | 7 | SetPresenceJob::SetPresenceJob(const QString& userId, const QString& presence, 8 | const QString& statusMsg) 9 | : BaseJob(HttpVerb::Put, u"SetPresenceJob"_s, 10 | makePath("/_matrix/client/v3", "/presence/", userId, "/status")) 11 | { 12 | QJsonObject _dataJson; 13 | addParam(_dataJson, "presence"_L1, presence); 14 | addParam(_dataJson, "status_msg"_L1, statusMsg); 15 | setRequestData({ _dataJson }); 16 | } 17 | 18 | QUrl GetPresenceJob::makeRequestUrl(const HomeserverData& hsData, const QString& userId) 19 | { 20 | return BaseJob::makeRequestUrl(hsData, 21 | makePath("/_matrix/client/v3", "/presence/", userId, "/status")); 22 | } 23 | 24 | GetPresenceJob::GetPresenceJob(const QString& userId) 25 | : BaseJob(HttpVerb::Get, u"GetPresenceJob"_s, 26 | makePath("/_matrix/client/v3", "/presence/", userId, "/status")) 27 | { 28 | addExpectedKey(u"presence"_s); 29 | } 30 | -------------------------------------------------------------------------------- /autotests/adjust-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CMD="" 4 | 5 | $CMD sed -i 's/tls: false/tls: true/g' homeserver.yaml 6 | 7 | ( 8 | cat < 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "roomevent.h" 7 | #include "eventrelation.h" 8 | 9 | namespace Quotient { 10 | 11 | class QUOTIENT_API ReactionEvent 12 | : public EventTemplate< 13 | ReactionEvent, RoomEvent, 14 | EventContent::SingleKeyValue> { 15 | public: 16 | QUO_EVENT(ReactionEvent, "m.reaction") 17 | static bool isValid(const QJsonObject& fullJson) 18 | { 19 | return fullJson[ContentKey][RelatesToKey][RelTypeKey].toString() 20 | == EventRelation::AnnotationType; 21 | } 22 | 23 | ReactionEvent(const QString& eventId, const QString& reactionKey) 24 | : EventTemplate(EventRelation::annotate(eventId, reactionKey)) 25 | {} 26 | 27 | QString eventId() const { return content().value.eventId; } 28 | QString key() const { return content().value.key; } 29 | 30 | private: 31 | explicit ReactionEvent(const QJsonObject& json) : EventTemplate(json) {} 32 | }; 33 | 34 | } // namespace Quotient 35 | -------------------------------------------------------------------------------- /Quotient/application-service/definitions/location.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | struct QUOTIENT_API ThirdPartyLocation { 10 | //! An alias for a matrix room. 11 | QString alias; 12 | 13 | //! The protocol ID that the third-party location is a part of. 14 | QString protocol; 15 | 16 | //! Information used to identify this third-party location. 17 | QJsonObject fields; 18 | }; 19 | 20 | template <> 21 | struct JsonObjectConverter { 22 | static void dumpTo(QJsonObject& jo, const ThirdPartyLocation& pod) 23 | { 24 | addParam(jo, "alias"_L1, pod.alias); 25 | addParam(jo, "protocol"_L1, pod.protocol); 26 | addParam(jo, "fields"_L1, pod.fields); 27 | } 28 | static void fillFrom(const QJsonObject& jo, ThirdPartyLocation& pod) 29 | { 30 | fillFromJson(jo.value("alias"_L1), pod.alias); 31 | fillFromJson(jo.value("protocol"_L1), pod.protocol); 32 | fillFromJson(jo.value("fields"_L1), pod.fields); 33 | } 34 | }; 35 | 36 | } // namespace Quotient 37 | -------------------------------------------------------------------------------- /Quotient/roomstateview.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include "roomstateview.h" 5 | 6 | using namespace Quotient; 7 | 8 | const StateEvent* RoomStateView::get(const QString& evtType, 9 | const QString& stateKey) const 10 | { 11 | return value({ evtType, stateKey }); 12 | } 13 | 14 | bool RoomStateView::contains(const QString& evtType, 15 | const QString& stateKey) const 16 | { 17 | return contains({ evtType, stateKey }); 18 | } 19 | 20 | QJsonObject RoomStateView::contentJson(const QString& evtType, 21 | const QString& stateKey) const 22 | { 23 | return queryOr(evtType, stateKey, &Event::contentJson, QJsonObject()); 24 | } 25 | 26 | const QVector RoomStateView::eventsOfType( 27 | const QString& evtType) const 28 | { 29 | auto vals = QVector(); 30 | for (auto it = cbegin(); it != cend(); ++it) 31 | if (it.key().first == evtType) 32 | vals.append(it.value()); 33 | 34 | return vals; 35 | } 36 | -------------------------------------------------------------------------------- /gtad/define_models.mustache: -------------------------------------------------------------------------------- 1 | {{#models}} 2 | {{#model}} 3 | {{>structDefinition}} 4 | 5 | template <> struct JsonObjectConverter<{{name}}> 6 | { 7 | {{#in?}} 8 | static void dumpTo(QJsonObject& jo, const {{name}}& pod) 9 | { {{#propertyMap}} 10 | fillJson(jo, pod.{{nameCamelCase}}); 11 | {{/propertyMap}}{{#parents}} 12 | fillJson<{{name}}>(jo, pod); 13 | {{/parents}}{{#vars}} 14 | addParam{{^required?}}{{/required?}}(jo, "{{baseName}}"_L1, 15 | pod.{{nameCamelCase}}); 16 | {{/vars}} 17 | } 18 | {{/in?}} 19 | {{#out?}} 20 | static void fillFrom({{>maybeCrefJsonObject}} jo, {{name}}& pod) 21 | { {{#parents}} 22 | fillFromJson<{{qualifiedName}}>(jo, pod); 23 | {{/parents}}{{#vars}} 24 | fillFromJson(jo.{{>takeOrValue}}("{{baseName}}"_L1), pod.{{nameCamelCase}}); 25 | {{/vars}}{{#propertyMap}} 26 | fromJson(jo, pod.{{nameCamelCase}}); 27 | {{/propertyMap}} 28 | } 29 | {{/out?}} 30 | }; 31 | 32 | {{/model}} 33 | {{/models}} 34 | -------------------------------------------------------------------------------- /Quotient/application-service/definitions/user.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | struct QUOTIENT_API ThirdPartyUser { 10 | //! A Matrix User ID representing a third-party user. 11 | QString userid; 12 | 13 | //! The protocol ID that the third-party location is a part of. 14 | QString protocol; 15 | 16 | //! Information used to identify this third-party location. 17 | QJsonObject fields; 18 | }; 19 | 20 | template <> 21 | struct JsonObjectConverter { 22 | static void dumpTo(QJsonObject& jo, const ThirdPartyUser& pod) 23 | { 24 | addParam(jo, "userid"_L1, pod.userid); 25 | addParam(jo, "protocol"_L1, pod.protocol); 26 | addParam(jo, "fields"_L1, pod.fields); 27 | } 28 | static void fillFrom(const QJsonObject& jo, ThirdPartyUser& pod) 29 | { 30 | fillFromJson(jo.value("userid"_L1), pod.userid); 31 | fillFromJson(jo.value("protocol"_L1), pod.protocol); 32 | fillFromJson(jo.value("fields"_L1), pod.fields); 33 | } 34 | }; 35 | 36 | } // namespace Quotient 37 | -------------------------------------------------------------------------------- /Quotient/jobs/downloadfilejob.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "basejob.h" 7 | 8 | namespace Quotient { 9 | struct EncryptedFileMetadata; 10 | 11 | class QUOTIENT_API DownloadFileJob : public BaseJob { 12 | public: 13 | static QUrl makeRequestUrl(const HomeserverData& hsData, const QUrl& mxcUri); 14 | static QUrl makeRequestUrl(const HomeserverData& hsData, const QString& serverName, 15 | const QString& mediaId); 16 | 17 | DownloadFileJob(QString serverName, QString mediaId, const QString& localFilename = {}); 18 | 19 | DownloadFileJob(QString serverName, QString mediaId, const EncryptedFileMetadata& file, 20 | const QString& localFilename = {}); 21 | QString targetFileName() const; 22 | 23 | private: 24 | class Private; 25 | ImplPtr d; 26 | 27 | void doPrepare(const ConnectionData* connectionData) override; 28 | void onSentRequest(QNetworkReply* reply) override; 29 | void beforeAbandon() override; 30 | Status prepareResult() override; 31 | }; 32 | } // namespace Quotient 33 | -------------------------------------------------------------------------------- /Quotient/logging_categories_p.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2017 Elvis Angelaccio 2 | // SPDX-FileCopyrightText: 2017 Kitsune Ral 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #define QUO_LOGGING_CATEGORY(Name, Id) \ 10 | inline Q_LOGGING_CATEGORY((Name), (Id), QtInfoMsg) 11 | 12 | namespace Quotient { 13 | 14 | QUO_LOGGING_CATEGORY(MAIN, "quotient.main") 15 | QUO_LOGGING_CATEGORY(EVENTS, "quotient.events") 16 | QUO_LOGGING_CATEGORY(STATE, "quotient.events.state") 17 | QUO_LOGGING_CATEGORY(MEMBERS, "quotient.events.members") 18 | QUO_LOGGING_CATEGORY(MESSAGES, "quotient.events.messages") 19 | QUO_LOGGING_CATEGORY(EPHEMERAL, "quotient.events.ephemeral") 20 | QUO_LOGGING_CATEGORY(E2EE, "quotient.e2ee") 21 | QUO_LOGGING_CATEGORY(JOBS, "quotient.jobs") 22 | QUO_LOGGING_CATEGORY(SYNCJOB, "quotient.jobs.sync") 23 | QUO_LOGGING_CATEGORY(THUMBNAILJOB, "quotient.jobs.thumbnail") 24 | QUO_LOGGING_CATEGORY(NETWORK, "quotient.network") 25 | QUO_LOGGING_CATEGORY(PROFILER, "quotient.profiler") 26 | QUO_LOGGING_CATEGORY(DATABASE, "quotient.database") 27 | 28 | } // namespace Quotient 29 | 30 | -------------------------------------------------------------------------------- /Quotient/csapi/typing.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Informs the server that the user has started or stopped typing. 10 | //! 11 | //! This tells the server that the user is typing for the next N 12 | //! milliseconds where N is the value specified in the `timeout` key. 13 | //! Alternatively, if `typing` is `false`, it tells the server that the 14 | //! user has stopped typing. 15 | class QUOTIENT_API SetTypingJob : public BaseJob { 16 | public: 17 | //! \param userId 18 | //! The user who has started to type. 19 | //! 20 | //! \param roomId 21 | //! The room in which the user is typing. 22 | //! 23 | //! \param typing 24 | //! Whether the user is typing or not. If `false`, the `timeout` 25 | //! key can be omitted. 26 | //! 27 | //! \param timeout 28 | //! The length of time in milliseconds to mark this user as typing. 29 | explicit SetTypingJob(const QString& userId, const QString& roomId, bool typing, 30 | std::optional timeout = std::nullopt); 31 | }; 32 | 33 | } // namespace Quotient 34 | -------------------------------------------------------------------------------- /Quotient/e2ee/qolmmessage.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Alexey Andreyev 2 | // 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace Quotient { 14 | 15 | /*! \brief A wrapper around an olm encrypted message 16 | * 17 | * This class encapsulates a Matrix olm encrypted message, 18 | * passed in either of 2 forms: a general message or a pre-key message. 19 | * 20 | * The class provides functions to get a type and the ciphertext. 21 | */ 22 | class QUOTIENT_API QOlmMessage : public QByteArray { 23 | Q_GADGET 24 | public: 25 | enum Type { 26 | PreKey = OLM_MESSAGE_TYPE_PRE_KEY, 27 | General = OLM_MESSAGE_TYPE_MESSAGE, 28 | }; 29 | Q_ENUM(Type) 30 | 31 | explicit QOlmMessage(QByteArray ciphertext, Type type = General); 32 | 33 | static QOlmMessage fromCiphertext(const QByteArray &ciphertext); 34 | 35 | Q_INVOKABLE Type type() const; 36 | Q_INVOKABLE QByteArray toCiphertext() const; 37 | 38 | private: 39 | Type m_messageType = General; 40 | }; 41 | 42 | } //namespace Quotient 43 | -------------------------------------------------------------------------------- /Quotient/events/roomkeyevent.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Alexey Andreyev 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "event.h" 7 | 8 | namespace Quotient { 9 | class QUOTIENT_API RoomKeyEvent : public Event 10 | { 11 | public: 12 | QUO_EVENT(RoomKeyEvent, "m.room_key") 13 | 14 | using Event::Event; 15 | explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, 16 | const QString& sessionId, const QString& sessionKey) 17 | : Event(basicJson(TypeId, { 18 | { "algorithm"_L1, algorithm }, 19 | { "room_id"_L1, roomId }, 20 | { "session_id"_L1, sessionId }, 21 | { "session_key"_L1, sessionKey }, 22 | })) 23 | {} 24 | 25 | QUO_CONTENT_GETTER(QString, algorithm) 26 | QUO_CONTENT_GETTER(QString, roomId) 27 | QUO_CONTENT_GETTER(QString, sessionId) 28 | QByteArray sessionKey() const 29 | { 30 | return contentPart("session_key"_L1).toLatin1(); 31 | } 32 | }; 33 | } // namespace Quotient 34 | -------------------------------------------------------------------------------- /Quotient/csapi/to_device.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Send an event to a given set of devices. 10 | //! 11 | //! This endpoint is used to send send-to-device events to a set of 12 | //! client devices. 13 | class QUOTIENT_API SendToDeviceJob : public BaseJob { 14 | public: 15 | //! \param eventType 16 | //! The type of event to send. 17 | //! 18 | //! \param txnId 19 | //! The [transaction ID](/client-server-api/#transaction-identifiers) for this event. Clients 20 | //! should generate an ID unique across requests with the same access token; it will be used 21 | //! by the server to ensure idempotency of requests. 22 | //! 23 | //! \param messages 24 | //! The messages to send. A map from user ID, to a map from 25 | //! device ID to message body. The device ID may also be `*`, 26 | //! meaning all known devices for the user. 27 | explicit SendToDeviceJob(const QString& eventType, const QString& txnId, 28 | const QHash>& messages); 29 | }; 30 | 31 | } // namespace Quotient 32 | -------------------------------------------------------------------------------- /Quotient/jobs/requestdata.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include "requestdata.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace Quotient; 14 | 15 | auto bufferFromData(const QByteArray& data) 16 | { 17 | auto source = makeImpl(); 18 | source->setData(data); 19 | source->open(QIODevice::ReadOnly); 20 | return source; 21 | } 22 | 23 | template 24 | requires std::constructible_from 25 | inline auto bufferFromJson(const JsonDataT& jdata) 26 | { 27 | return bufferFromData(QJsonDocument(jdata).toJson(QJsonDocument::Compact)); 28 | } 29 | 30 | RequestData::RequestData(const QByteArray& a) : _source(bufferFromData(a)) {} 31 | 32 | RequestData::RequestData(const QJsonObject& jo) : _source(bufferFromJson(jo)) {} 33 | 34 | RequestData::RequestData(const QJsonArray& ja) : _source(bufferFromJson(ja)) {} 35 | 36 | RequestData::RequestData(QIODevice* source) 37 | : _source(acquireImpl(source)) 38 | {} 39 | -------------------------------------------------------------------------------- /Quotient/csapi/notifications.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "notifications.h" 4 | 5 | using namespace Quotient; 6 | 7 | auto queryToGetNotifications(const QString& from, std::optional limit, const QString& only) 8 | { 9 | QUrlQuery _q; 10 | addParam(_q, u"from"_s, from); 11 | addParam(_q, u"limit"_s, limit); 12 | addParam(_q, u"only"_s, only); 13 | return _q; 14 | } 15 | 16 | QUrl GetNotificationsJob::makeRequestUrl(const HomeserverData& hsData, const QString& from, 17 | std::optional limit, const QString& only) 18 | { 19 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/notifications"), 20 | queryToGetNotifications(from, limit, only)); 21 | } 22 | 23 | GetNotificationsJob::GetNotificationsJob(const QString& from, std::optional limit, 24 | const QString& only) 25 | : BaseJob(HttpVerb::Get, u"GetNotificationsJob"_s, 26 | makePath("/_matrix/client/v3", "/notifications"), 27 | queryToGetNotifications(from, limit, only)) 28 | { 29 | addExpectedKey(u"notifications"_s); 30 | } 31 | -------------------------------------------------------------------------------- /Quotient/csapi/room_event_by_timestamp.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "room_event_by_timestamp.h" 4 | 5 | using namespace Quotient; 6 | 7 | auto queryToGetEventByTimestamp(int ts, const QString& dir) 8 | { 9 | QUrlQuery _q; 10 | addParam(_q, u"ts"_s, ts); 11 | addParam(_q, u"dir"_s, dir); 12 | return _q; 13 | } 14 | 15 | QUrl GetEventByTimestampJob::makeRequestUrl(const HomeserverData& hsData, const QString& roomId, 16 | int ts, const QString& dir) 17 | { 18 | return BaseJob::makeRequestUrl(hsData, 19 | makePath("/_matrix/client/v1", "/rooms/", roomId, 20 | "/timestamp_to_event"), 21 | queryToGetEventByTimestamp(ts, dir)); 22 | } 23 | 24 | GetEventByTimestampJob::GetEventByTimestampJob(const QString& roomId, int ts, const QString& dir) 25 | : BaseJob(HttpVerb::Get, u"GetEventByTimestampJob"_s, 26 | makePath("/_matrix/client/v1", "/rooms/", roomId, "/timestamp_to_event"), 27 | queryToGetEventByTimestamp(ts, dir)) 28 | { 29 | addExpectedKey(u"event_id"_s); 30 | addExpectedKey(u"origin_server_ts"_s); 31 | } 32 | -------------------------------------------------------------------------------- /Quotient/csapi/kicking.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Kick a user from the room. 10 | //! 11 | //! Kick a user from the room. 12 | //! 13 | //! The caller must have the required power level in order to perform this operation. 14 | //! 15 | //! Kicking a user adjusts the target member's membership state to be `leave` with an 16 | //! optional `reason`. Like with other membership changes, a user can directly adjust 17 | //! the target member's state by making a request to `/rooms//state/m.room.member/`. 19 | class QUOTIENT_API KickJob : public BaseJob { 20 | public: 21 | //! \param roomId 22 | //! The room identifier (not alias) from which the user should be kicked. 23 | //! 24 | //! \param userId 25 | //! The fully qualified user ID of the user being kicked. 26 | //! 27 | //! \param reason 28 | //! The reason the user has been kicked. This will be supplied as the 29 | //! `reason` on the target's updated [`m.room.member`](/client-server-api/#mroommember) event. 30 | explicit KickJob(const QString& roomId, const QString& userId, const QString& reason = {}); 31 | }; 32 | 33 | } // namespace Quotient 34 | -------------------------------------------------------------------------------- /Quotient/events/stateevent.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include "stateevent.h" 5 | 6 | using namespace Quotient; 7 | 8 | StateEvent::StateEvent(const QJsonObject& json) 9 | : RoomEvent(json) 10 | { 11 | Q_ASSERT_X(json.contains(StateKeyKey), __FUNCTION__, 12 | "Attempt to create a state event without state key"); 13 | } 14 | 15 | StateEvent::StateEvent(event_type_t type, const QString& stateKey, 16 | const QJsonObject& contentJson) 17 | : RoomEvent(basicJson(type, stateKey, contentJson)) 18 | {} 19 | 20 | bool StateEvent::repeatsState() const 21 | { 22 | return contentJson() == unsignedPart(PrevContentKey); 23 | } 24 | 25 | QString StateEvent::replacedState() const 26 | { 27 | return unsignedPart("replaces_state"_L1); 28 | } 29 | 30 | void StateEvent::dumpTo(QDebug dbg) const 31 | { 32 | if (!stateKey().isEmpty()) 33 | dbg << '<' << stateKey() << "> "; 34 | if (const auto prevContentJson = unsignedPart(PrevContentKey); 35 | !prevContentJson.isEmpty()) 36 | dbg << QJsonDocument(prevContentJson).toJson(QJsonDocument::Compact) 37 | << " -> "; 38 | RoomEvent::dumpTo(dbg); 39 | } 40 | -------------------------------------------------------------------------------- /Quotient/csapi/cross_signing.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "cross_signing.h" 4 | 5 | using namespace Quotient; 6 | 7 | UploadCrossSigningKeysJob::UploadCrossSigningKeysJob( 8 | const std::optional& masterKey, 9 | const std::optional& selfSigningKey, 10 | const std::optional& userSigningKey, 11 | const std::optional& auth) 12 | : BaseJob(HttpVerb::Post, u"UploadCrossSigningKeysJob"_s, 13 | makePath("/_matrix/client/v3", "/keys/device_signing/upload")) 14 | { 15 | QJsonObject _dataJson; 16 | addParam(_dataJson, "master_key"_L1, masterKey); 17 | addParam(_dataJson, "self_signing_key"_L1, selfSigningKey); 18 | addParam(_dataJson, "user_signing_key"_L1, userSigningKey); 19 | addParam(_dataJson, "auth"_L1, auth); 20 | setRequestData({ _dataJson }); 21 | } 22 | 23 | UploadCrossSigningSignaturesJob::UploadCrossSigningSignaturesJob( 24 | const QHash>& signatures) 25 | : BaseJob(HttpVerb::Post, u"UploadCrossSigningSignaturesJob"_s, 26 | makePath("/_matrix/client/v3", "/keys/signatures/upload")) 27 | { 28 | setRequestData({ toJson(signatures) }); 29 | } 30 | -------------------------------------------------------------------------------- /Quotient/csapi/event_context.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "event_context.h" 4 | 5 | using namespace Quotient; 6 | 7 | auto queryToGetEventContext(std::optional limit, const QString& filter) 8 | { 9 | QUrlQuery _q; 10 | addParam(_q, u"limit"_s, limit); 11 | addParam(_q, u"filter"_s, filter); 12 | return _q; 13 | } 14 | 15 | QUrl GetEventContextJob::makeRequestUrl(const HomeserverData& hsData, const QString& roomId, 16 | const QString& eventId, std::optional limit, 17 | const QString& filter) 18 | { 19 | return BaseJob::makeRequestUrl(hsData, 20 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/context/", 21 | eventId), 22 | queryToGetEventContext(limit, filter)); 23 | } 24 | 25 | GetEventContextJob::GetEventContextJob(const QString& roomId, const QString& eventId, 26 | std::optional limit, const QString& filter) 27 | : BaseJob(HttpVerb::Get, u"GetEventContextJob"_s, 28 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/context/", eventId), 29 | queryToGetEventContext(limit, filter)) 30 | {} 31 | -------------------------------------------------------------------------------- /Quotient/keyimport.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include "expected.h" // Only here to not break client code still using Expected 7 | #include "quotient_export.h" 8 | 9 | #include 10 | 11 | class TestKeyImport; 12 | 13 | namespace Quotient 14 | { 15 | class Connection; 16 | } 17 | 18 | namespace Quotient 19 | { 20 | 21 | class QUOTIENT_API KeyImport : public QObject 22 | { 23 | Q_OBJECT 24 | 25 | public: 26 | enum Error { 27 | Success, 28 | InvalidPassphrase, 29 | InvalidData, 30 | OtherError, 31 | }; 32 | Q_ENUM(Error) 33 | 34 | using QObject::QObject; 35 | 36 | Q_INVOKABLE Error importKeys(QString data, const QString& passphrase, 37 | const Quotient::Connection* connection); 38 | Q_INVOKABLE std::expected exportKeys(const QString& passphrase, 39 | const Quotient::Connection* connection); 40 | 41 | friend class ::TestKeyImport; 42 | 43 | private: 44 | std::expected decrypt(QString data, const QString& passphrase); 45 | std::expected encrypt(QJsonArray sessions, const QString& passphrase); 46 | }; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Quotient/events/encryptionevent.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2017 Kitsune Ral 2 | // SPDX-FileCopyrightText: 2019 Alexey Andreyev 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #include "encryptionevent.h" 6 | 7 | #include "../e2ee/e2ee_common.h" 8 | 9 | using namespace Quotient; 10 | 11 | EncryptionEventContent::EncryptionEventContent(const QJsonObject& json) 12 | : encryption(fromJson(json[AlgorithmKeyL])) 13 | , algorithm(sanitized(json[AlgorithmKeyL].toString())) 14 | { 15 | // NB: fillFromJson only fills the variable if the JSON key exists 16 | fillFromJson(json[RotationPeriodMsKeyL], rotationPeriodMs); 17 | fillFromJson(json[RotationPeriodMsgsKeyL], rotationPeriodMsgs); 18 | } 19 | 20 | EncryptionEventContent::EncryptionEventContent(Quotient::EncryptionType et) 21 | : encryption(et) 22 | { 23 | if(encryption != Quotient::EncryptionType::Undefined) { 24 | algorithm = Quotient::toJson(encryption); 25 | } 26 | } 27 | 28 | QJsonObject EncryptionEventContent::toJson() const 29 | { 30 | QJsonObject o; 31 | if (encryption != Quotient::EncryptionType::Undefined) 32 | o.insert(AlgorithmKey, algorithm); 33 | o.insert(RotationPeriodMsKey, rotationPeriodMs); 34 | o.insert(RotationPeriodMsgsKey, rotationPeriodMsgs); 35 | return o; 36 | } 37 | -------------------------------------------------------------------------------- /Quotient/avatar.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2017 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "util.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | namespace Quotient { 15 | class Connection; 16 | 17 | class QUOTIENT_API Avatar { 18 | public: 19 | explicit Avatar(Connection* parent, const QUrl& url = {}); 20 | 21 | #ifdef __cpp_lib_move_only_function // AppleClang 15 doesn't have it 22 | using get_callback_t = std::move_only_function; 23 | using upload_callback_t = std::move_only_function; 24 | #else 25 | using get_callback_t = std::function; 26 | using upload_callback_t = std::function; 27 | #endif 28 | 29 | 30 | QImage get(int dimension, get_callback_t callback) const; 31 | QImage get(int width, int height, get_callback_t callback) const; 32 | 33 | QFuture upload(const QString& fileName) const; 34 | QFuture upload(QIODevice* source) const; 35 | 36 | bool isEmpty() const; 37 | QString mediaId() const; 38 | QUrl url() const; 39 | bool updateUrl(const QUrl& newUrl); 40 | 41 | static bool isUrlValid(const QUrl& u); 42 | 43 | private: 44 | class Private; 45 | ImplPtr d; 46 | }; 47 | } // namespace Quotient 48 | -------------------------------------------------------------------------------- /quotest/.valgrind.supp: -------------------------------------------------------------------------------- 1 | { 2 | libc_dirty_free_on_exit 3 | Memcheck:Free 4 | fun:free 5 | fun:__libc_freeres 6 | fun:_vgnU_freeres 7 | fun:__run_exit_handlers 8 | fun:exit 9 | } 10 | 11 | { 12 | QAuthenticator 13 | Memcheck:Leak 14 | match-leak-kinds: possible 15 | ... 16 | fun:_ZN14QAuthenticator6detachEv 17 | } 18 | 19 | { 20 | QTimer 21 | Memcheck:Leak 22 | match-leak-kinds: possible 23 | fun:_Znwm 24 | fun:_ZN7QObjectC1EPS_ 25 | fun:_ZN6QTimerC1EP7QObject 26 | } 27 | 28 | { 29 | QSslConfiguration 30 | Memcheck:Leak 31 | match-leak-kinds: possible 32 | fun:_Znwm 33 | ... 34 | fun:_ZN17QSslConfigurationC1Ev 35 | } 36 | 37 | { 38 | libcrypto_ASN1 39 | Memcheck:Leak 40 | match-leak-kinds: definite 41 | fun:malloc 42 | ... 43 | fun:ASN1_item_ex_d2i 44 | } 45 | 46 | { 47 | malloc_from_libcrypto 48 | Memcheck:Leak 49 | match-leak-kinds: possible 50 | fun:malloc 51 | fun:CRYPTO_malloc 52 | ... 53 | obj:/lib/x86_64-linux-gnu/libcrypto.so.* 54 | } 55 | 56 | { 57 | Slot_activation_from_QtNetwork 58 | Memcheck:Leak 59 | match-leak-kinds: definite 60 | fun:malloc 61 | fun:inflateInit2_ 62 | obj:/*/*/*/libQt5Network.so.* 63 | ... 64 | fun:_ZN11QMetaObject8activateEP7QObjectiiPPv 65 | ... 66 | fun:_ZN11QMetaObject8activateEP7QObjectiiPPv 67 | obj:/*/*/*/libQt5Network.so.* 68 | } -------------------------------------------------------------------------------- /Quotient/csapi/threads_list.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "threads_list.h" 4 | 5 | using namespace Quotient; 6 | 7 | auto queryToGetThreadRoots(const QString& include, std::optional limit, const QString& from) 8 | { 9 | QUrlQuery _q; 10 | addParam(_q, u"include"_s, include); 11 | addParam(_q, u"limit"_s, limit); 12 | addParam(_q, u"from"_s, from); 13 | return _q; 14 | } 15 | 16 | QUrl GetThreadRootsJob::makeRequestUrl(const HomeserverData& hsData, const QString& roomId, 17 | const QString& include, std::optional limit, 18 | const QString& from) 19 | { 20 | return BaseJob::makeRequestUrl(hsData, 21 | makePath("/_matrix/client/v1", "/rooms/", roomId, "/threads"), 22 | queryToGetThreadRoots(include, limit, from)); 23 | } 24 | 25 | GetThreadRootsJob::GetThreadRootsJob(const QString& roomId, const QString& include, 26 | std::optional limit, const QString& from) 27 | : BaseJob(HttpVerb::Get, u"GetThreadRootsJob"_s, 28 | makePath("/_matrix/client/v1", "/rooms/", roomId, "/threads"), 29 | queryToGetThreadRoots(include, limit, from)) 30 | { 31 | addExpectedKey(u"chunk"_s); 32 | } 33 | -------------------------------------------------------------------------------- /Quotient/e2ee/qolmutility.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Carl Schwan 2 | // 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include 10 | 11 | struct OlmUtility; 12 | 13 | namespace Quotient { 14 | 15 | //! Allows you to make use of crytographic hashing via SHA-2 and 16 | //! verifying ed25519 signatures. 17 | class QUOTIENT_API QOlmUtility 18 | { 19 | public: 20 | QOlmUtility(); 21 | 22 | //! Returns a sha256 of the supplied byte slice. 23 | QString sha256Bytes(const QByteArray& inputBuf) const; 24 | 25 | //! Convenience function that converts the UTF-8 message 26 | //! to bytes and then calls `sha256Bytes()`, returning its output. 27 | QString sha256Utf8Msg(const QString& message) const; 28 | 29 | //! Verify a ed25519 signature. 30 | //! \param key QByteArray The public part of the ed25519 key that signed the message. 31 | //! \param message QByteArray The message that was signed. 32 | //! \param signature QByteArray The signature of the message. 33 | bool ed25519Verify(const QByteArray& key, const QByteArray& message, 34 | QByteArray signature) const; 35 | 36 | OlmErrorCode lastErrorCode() const; 37 | const char* lastError() const; 38 | 39 | private: 40 | CStructPtr olmDataHolder; 41 | }; 42 | } // namespace Quotient 43 | -------------------------------------------------------------------------------- /Quotient/csapi/read_markers.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Set the position of the read marker for a room. 10 | //! 11 | //! Sets the position of the read marker for a given room, and optionally 12 | //! the read receipt's location. 13 | class QUOTIENT_API SetReadMarkerJob : public BaseJob { 14 | public: 15 | //! \param roomId 16 | //! The room ID to set the read marker in for the user. 17 | //! 18 | //! \param fullyRead 19 | //! The event ID the read marker should be located at. The 20 | //! event MUST belong to the room. 21 | //! 22 | //! \param read 23 | //! The event ID to set the read receipt location at. This is 24 | //! equivalent to calling `/receipt/m.read/$elsewhere:example.org` 25 | //! and is provided here to save that extra call. 26 | //! 27 | //! \param readPrivate 28 | //! The event ID to set the *private* read receipt location at. This 29 | //! equivalent to calling `/receipt/m.read.private/$elsewhere:example.org` 30 | //! and is provided here to save that extra call. 31 | explicit SetReadMarkerJob(const QString& roomId, const QString& fullyRead = {}, 32 | const QString& read = {}, const QString& readPrivate = {}); 33 | }; 34 | 35 | } // namespace Quotient 36 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/push_ruleset.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace Quotient { 10 | 11 | struct QUOTIENT_API PushRuleset { 12 | QVector content{}; 13 | 14 | QVector override{}; 15 | 16 | QVector room{}; 17 | 18 | QVector sender{}; 19 | 20 | QVector underride{}; 21 | }; 22 | 23 | template <> 24 | struct JsonObjectConverter { 25 | static void dumpTo(QJsonObject& jo, const PushRuleset& pod) 26 | { 27 | addParam(jo, "content"_L1, pod.content); 28 | addParam(jo, "override"_L1, pod.override); 29 | addParam(jo, "room"_L1, pod.room); 30 | addParam(jo, "sender"_L1, pod.sender); 31 | addParam(jo, "underride"_L1, pod.underride); 32 | } 33 | static void fillFrom(const QJsonObject& jo, PushRuleset& pod) 34 | { 35 | fillFromJson(jo.value("content"_L1), pod.content); 36 | fillFromJson(jo.value("override"_L1), pod.override); 37 | fillFromJson(jo.value("room"_L1), pod.room); 38 | fillFromJson(jo.value("sender"_L1), pod.sender); 39 | fillFromJson(jo.value("underride"_L1), pod.underride); 40 | } 41 | }; 42 | 43 | } // namespace Quotient 44 | -------------------------------------------------------------------------------- /Quotient/events/encryptionevent.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2017 Kitsune Ral 2 | // SPDX-FileCopyrightText: 2019 Alexey Andreyev 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #pragma once 6 | 7 | #include 8 | #include "stateevent.h" 9 | 10 | namespace Quotient { 11 | class QUOTIENT_API EncryptionEventContent { 12 | public: 13 | Q_IMPLICIT EncryptionEventContent(Quotient::EncryptionType et); 14 | explicit EncryptionEventContent(const QJsonObject& json); 15 | 16 | QJsonObject toJson() const; 17 | 18 | Quotient::EncryptionType encryption; 19 | QString algorithm {}; 20 | int rotationPeriodMs = 604'800'000; 21 | int rotationPeriodMsgs = 100; 22 | }; 23 | 24 | class QUOTIENT_API EncryptionEvent 25 | : public KeylessStateEventBase { 26 | public: 27 | QUO_EVENT(EncryptionEvent, "m.room.encryption") 28 | 29 | using KeylessStateEventBase::KeylessStateEventBase; 30 | 31 | Quotient::EncryptionType encryption() const { return content().encryption; } 32 | QString algorithm() const { return content().algorithm; } 33 | int rotationPeriodMs() const { return content().rotationPeriodMs; } 34 | int rotationPeriodMsgs() const { return content().rotationPeriodMsgs; } 35 | 36 | bool useEncryption() const { return !algorithm().isEmpty(); } 37 | }; 38 | } // namespace Quotient 39 | -------------------------------------------------------------------------------- /Quotient/connectiondata.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2015 Felix Rohrbach 2 | // SPDX-FileCopyrightText: 2016 Kitsune Ral 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #pragma once 6 | 7 | #include "util.h" 8 | 9 | #include 10 | 11 | #include 12 | 13 | namespace Quotient { 14 | 15 | class NetworkAccessManager; 16 | class BaseJob; 17 | 18 | class QUOTIENT_API ConnectionData { 19 | public: 20 | explicit ConnectionData(QUrl baseUrl); 21 | Q_DISABLE_COPY_MOVE(ConnectionData) 22 | virtual ~ConnectionData(); 23 | 24 | void submit(BaseJob* job); 25 | void limitRate(std::chrono::milliseconds nextCallAfter); 26 | 27 | QByteArray accessToken() const; 28 | QUrl baseUrl() const; 29 | const QString& deviceId() const; 30 | const QString& userId() const; 31 | HomeserverData homeserverData() const; 32 | Quotient::NetworkAccessManager *nam() const; 33 | 34 | void setBaseUrl(QUrl baseUrl); 35 | void setIdentity(const QString& userId, const QString& deviceId, QByteArray accessToken = {}); 36 | void setAccessToken(QByteArray accessToken); 37 | void setSupportedSpecVersions(QStringList versions); 38 | 39 | QString lastEvent() const; 40 | void setLastEvent(QString identifier); 41 | 42 | QString generateTxnId() const; 43 | 44 | private: 45 | class Private; 46 | ImplPtr d; 47 | }; 48 | } // namespace Quotient 49 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/auth_data.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | //! Used by clients to submit authentication information to the interactive-authentication API 9 | struct QUOTIENT_API AuthenticationData { 10 | //! The authentication type that the client is attempting to complete. 11 | //! May be omitted if `session` is given, and the client is reissuing a 12 | //! request which it believes has been completed out-of-band (for example, 13 | //! via the [fallback mechanism](#fallback)). 14 | QString type{}; 15 | 16 | //! The value of the session key given by the homeserver. 17 | QString session{}; 18 | 19 | //! Keys dependent on the login type 20 | QVariantHash authInfo{}; 21 | }; 22 | 23 | template <> 24 | struct JsonObjectConverter { 25 | static void dumpTo(QJsonObject& jo, const AuthenticationData& pod) 26 | { 27 | fillJson(jo, pod.authInfo); 28 | addParam(jo, "type"_L1, pod.type); 29 | addParam(jo, "session"_L1, pod.session); 30 | } 31 | static void fillFrom(QJsonObject jo, AuthenticationData& pod) 32 | { 33 | fillFromJson(jo.take("type"_L1), pod.type); 34 | fillFromJson(jo.take("session"_L1), pod.session); 35 | fromJson(jo, pod.authInfo); 36 | } 37 | }; 38 | 39 | } // namespace Quotient 40 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/wellknown/full.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace Quotient { 11 | //! Used by clients to determine the homeserver, identity server, and other 12 | //! optional components they should be interacting with. 13 | struct QUOTIENT_API DiscoveryInformation { 14 | HomeserverInformation homeserver; 15 | 16 | std::optional identityServer{}; 17 | 18 | //! Application-dependent keys using Java package naming convention. 19 | QVariantHash additionalProperties{}; 20 | }; 21 | 22 | template <> 23 | struct JsonObjectConverter { 24 | static void dumpTo(QJsonObject& jo, const DiscoveryInformation& pod) 25 | { 26 | fillJson(jo, pod.additionalProperties); 27 | addParam(jo, "m.homeserver"_L1, pod.homeserver); 28 | addParam(jo, "m.identity_server"_L1, pod.identityServer); 29 | } 30 | static void fillFrom(QJsonObject jo, DiscoveryInformation& pod) 31 | { 32 | fillFromJson(jo.take("m.homeserver"_L1), pod.homeserver); 33 | fillFromJson(jo.take("m.identity_server"_L1), pod.identityServer); 34 | fromJson(jo, pod.additionalProperties); 35 | } 36 | }; 37 | 38 | } // namespace Quotient 39 | -------------------------------------------------------------------------------- /Quotient/networkaccessmanager.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "Quotient/quotient_export.h" 7 | 8 | #include 9 | 10 | namespace Quotient { 11 | 12 | class QUOTIENT_API NetworkAccessManager : public QNetworkAccessManager { 13 | Q_OBJECT 14 | public: 15 | using QNetworkAccessManager::QNetworkAccessManager; 16 | 17 | static void addAccount(const QString& accountId, const QUrl& homeserver, 18 | const QByteArray& accessToken = {}); 19 | static void updateAccountSpecVersions(QStringView accountId, const QStringList &versions); 20 | static void dropAccount(QStringView accountId); 21 | 22 | static QList ignoredSslErrors(); 23 | static void addIgnoredSslError(const QSslError& error); 24 | static void clearIgnoredSslErrors(); 25 | 26 | static void setAccessToken(const QString& userId, const QByteArray& token); 27 | 28 | //! Get a NAM instance for the current thread 29 | static NetworkAccessManager* instance(); 30 | 31 | private Q_SLOTS: 32 | QStringList supportedSchemesImplementation() const; // clazy:exclude=const-signal-or-slot 33 | 34 | private: 35 | QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, 36 | QIODevice* outgoingData = nullptr) override; 37 | }; 38 | } // namespace Quotient 39 | -------------------------------------------------------------------------------- /autotests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Carl Schwan 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | include(CMakeParseArguments) 6 | 7 | add_custom_target(autotests ALL) 8 | 9 | add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" ) 10 | 11 | function(QUOTIENT_ADD_TEST) 12 | cmake_parse_arguments(ARG "" "NAME" "" ${ARGN}) 13 | add_executable(${ARG_NAME} ${ARG_NAME}.cpp testutils.h testutils.cpp) 14 | target_link_libraries(${ARG_NAME} ${Qt}::Core ${Qt}::Test ${QUOTIENT_LIB_NAME}) 15 | add_test(NAME ${ARG_NAME} COMMAND ${ARG_NAME}) 16 | add_dependencies(autotests ${ARG_NAME}) 17 | set_target_properties(${ARG_NAME} PROPERTIES 18 | CXX_STANDARD 23 19 | CXX_EXTENSIONS OFF 20 | VISIBILITY_INLINES_HIDDEN ON 21 | CXX_VISIBILITY_PRESET hidden 22 | ) 23 | endfunction() 24 | 25 | quotient_add_test(NAME callcandidateseventtest) 26 | quotient_add_test(NAME utiltests) 27 | quotient_add_test(NAME testolmaccount) 28 | quotient_add_test(NAME testgroupsession) 29 | quotient_add_test(NAME testolmsession) 30 | if (NOT MSVC) 31 | target_compile_options(testolmsession PRIVATE -fexceptions) 32 | endif() 33 | quotient_add_test(NAME testolmutility) 34 | quotient_add_test(NAME testcryptoutils) 35 | quotient_add_test(NAME testkeyverification) 36 | quotient_add_test(NAME testcrosssigning) 37 | quotient_add_test(NAME testkeyimport) 38 | quotient_add_test(NAME testsettings) 39 | quotient_add_test(NAME testthread) 40 | -------------------------------------------------------------------------------- /Quotient/csapi/report_content.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Report an event in a joined room as inappropriate. 10 | //! 11 | //! Reports an event as inappropriate to the server, which may then notify 12 | //! the appropriate people. The caller must be joined to the room to report 13 | //! it. 14 | //! 15 | //! It might be possible for clients to deduce whether an event exists by 16 | //! timing the response, as only a report for an event that does exist 17 | //! will require the homeserver to check whether a user is joined to 18 | //! the room. To combat this, homeserver implementations should add 19 | //! a random delay when generating a response. 20 | class QUOTIENT_API ReportContentJob : public BaseJob { 21 | public: 22 | //! \param roomId 23 | //! The room in which the event being reported is located. 24 | //! 25 | //! \param eventId 26 | //! The event to report. 27 | //! 28 | //! \param score 29 | //! The score to rate this content as where -100 is most offensive 30 | //! and 0 is inoffensive. 31 | //! 32 | //! \param reason 33 | //! The reason the content is being reported. May be blank. 34 | explicit ReportContentJob(const QString& roomId, const QString& eventId, 35 | std::optional score = std::nullopt, const QString& reason = {}); 36 | }; 37 | 38 | } // namespace Quotient 39 | -------------------------------------------------------------------------------- /Quotient/csapi/wellknown.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace Quotient { 10 | 11 | //! \brief Gets Matrix server discovery information about the domain. 12 | //! 13 | //! Gets discovery information about the domain. The file may include 14 | //! additional keys, which MUST follow the Java package naming convention, 15 | //! e.g. `com.example.myapp.property`. This ensures property names are 16 | //! suitably namespaced for each application and reduces the risk of 17 | //! clashes. 18 | //! 19 | //! Note that this endpoint is not necessarily handled by the homeserver, 20 | //! but by another webserver, to be used for discovering the homeserver URL. 21 | class QUOTIENT_API GetWellknownJob : public BaseJob { 22 | public: 23 | explicit GetWellknownJob(); 24 | 25 | //! \brief Construct a URL without creating a full-fledged job object 26 | //! 27 | //! This function can be used when a URL for GetWellknownJob 28 | //! is necessary but the job itself isn't. 29 | static QUrl makeRequestUrl(const HomeserverData& hsData); 30 | 31 | // Result properties 32 | 33 | //! Server discovery information. 34 | DiscoveryInformation data() const { return fromJson(jsonData()); } 35 | }; 36 | 37 | inline auto collectResponse(const GetWellknownJob* job) { return job->data(); } 38 | 39 | } // namespace Quotient 40 | -------------------------------------------------------------------------------- /Quotient/csapi/receipts.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Send a receipt for the given event ID. 10 | //! 11 | //! This API updates the marker for the given receipt type to the event ID 12 | //! specified. 13 | class QUOTIENT_API PostReceiptJob : public BaseJob { 14 | public: 15 | //! \param roomId 16 | //! The room in which to send the event. 17 | //! 18 | //! \param receiptType 19 | //! The type of receipt to send. This can also be `m.fully_read` as an 20 | //! alternative to 21 | //! [`/read_markers`](/client-server-api/#post_matrixclientv3roomsroomidread_markers). 22 | //! 23 | //! Note that `m.fully_read` does not appear under `m.receipt`: this endpoint 24 | //! effectively calls `/read_markers` internally when presented with a receipt 25 | //! type of `m.fully_read`. 26 | //! 27 | //! \param eventId 28 | //! The event ID to acknowledge up to. 29 | //! 30 | //! \param threadId 31 | //! The root thread event's ID (or `main`) for which 32 | //! thread this receipt is intended to be under. If 33 | //! not specified, the read receipt is *unthreaded* 34 | //! (default). 35 | explicit PostReceiptJob(const QString& roomId, const QString& receiptType, 36 | const QString& eventId, const QString& threadId = {}); 37 | }; 38 | 39 | } // namespace Quotient 40 | -------------------------------------------------------------------------------- /Quotient/csapi/inviting.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Invite a user to participate in a particular room. 10 | //! 11 | //! *Note that there are two forms of this API, which are documented separately. 12 | //! This version of the API requires that the inviter knows the Matrix 13 | //! identifier of the invitee. The other is documented in the 14 | //! [third-party invites](/client-server-api/#third-party-invites) section.* 15 | //! 16 | //! This API invites a user to participate in a particular room. 17 | //! They do not start participating in the room until they actually join the 18 | //! room. 19 | //! 20 | //! Only users currently in a particular room can invite other users to 21 | //! join that room. 22 | //! 23 | //! If the user was invited to the room, the homeserver will append a 24 | //! `m.room.member` event to the room. 25 | class QUOTIENT_API InviteUserJob : public BaseJob { 26 | public: 27 | //! \param roomId 28 | //! The room identifier (not alias) to which to invite the user. 29 | //! 30 | //! \param userId 31 | //! The fully qualified user ID of the invitee. 32 | //! 33 | //! \param reason 34 | //! Optional reason to be included as the `reason` on the subsequent 35 | //! membership event. 36 | explicit InviteUserJob(const QString& roomId, const QString& userId, const QString& reason = {}); 37 | }; 38 | 39 | } // namespace Quotient 40 | -------------------------------------------------------------------------------- /Quotient/jobs/mediathumbnailjob.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "basejob.h" 7 | 8 | #include 9 | 10 | namespace Quotient { 11 | class QUOTIENT_API MediaThumbnailJob : public BaseJob { 12 | public: 13 | static QUrl makeRequestUrl(const HomeserverData& hsData, const QUrl& mxcUri, 14 | QSize requestedSize, std::optional animated = std::nullopt); 15 | static QUrl makeRequestUrl(const HomeserverData& hsData, const QString& serverName, 16 | const QString& mediaId, QSize requestedSize, 17 | std::optional animated = std::nullopt); 18 | 19 | MediaThumbnailJob(QString serverName, QString mediaId, QSize requestedSize, 20 | std::optional animated = std::nullopt); 21 | MediaThumbnailJob(const QUrl& mxcUri, QSize requestedSize, 22 | std::optional animated = std::nullopt); 23 | 24 | QImage thumbnail() const; 25 | 26 | private: 27 | QString serverName; 28 | QString mediaId; 29 | QSize requestedSize; 30 | std::optional animated; 31 | QImage _thumbnail; 32 | 33 | void doPrepare(const ConnectionData* connectionData) override; 34 | Status prepareResult() override; 35 | }; 36 | 37 | inline auto collectResponse(const MediaThumbnailJob* j) { return j->thumbnail(); } 38 | 39 | } // namespace Quotient 40 | -------------------------------------------------------------------------------- /Quotient/events/accountdataevents.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "event.h" 7 | 8 | #include "../csapi/definitions/tag.h" 9 | 10 | namespace Quotient { 11 | constexpr inline auto FavouriteTag = "m.favourite"_L1; 12 | constexpr inline auto LowPriorityTag = "m.lowpriority"_L1; 13 | constexpr inline auto ServerNoticeTag = "m.server_notice"_L1; 14 | 15 | inline std::partial_ordering operator<=>( 16 | const Tag& lhs, const Tag& rhs) // clazy:exclude=function-args-by-value 17 | { 18 | // Per The Spec, rooms with no order should be after those with order, 19 | // against std::optional<>::operator<=>() convention. 20 | return (lhs.order && !rhs.order) ? std::partial_ordering::less 21 | : (!lhs.order && rhs.order) ? std::partial_ordering::greater 22 | : *lhs.order <=> *rhs.order; 23 | } 24 | 25 | inline bool operator==(const Tag& lhs, const Tag& rhs) // clazy:exclude=function-args-by-value 26 | { 27 | return std::is_eq(lhs <=> rhs); 28 | } 29 | 30 | using TagsMap = QHash; 31 | 32 | DEFINE_SIMPLE_EVENT(TagEvent, Event, "m.tag", TagsMap, tags, "tags") 33 | DEFINE_SIMPLE_EVENT(ReadMarkerEvent, Event, "m.fully_read", QString, 34 | eventId, "event_id") 35 | DEFINE_SIMPLE_EVENT(IgnoredUsersEvent, Event, "m.ignored_user_list", 36 | QSet, ignoredUsers, "ignored_users") 37 | } // namespace Quotient 38 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/third_party_signed.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | //! A signature of an `m.third_party_invite` token to prove that this user 9 | //! owns a third-party identity which has been invited to the room. 10 | struct QUOTIENT_API ThirdPartySigned { 11 | //! The Matrix ID of the user who issued the invite. 12 | QString sender; 13 | 14 | //! The Matrix ID of the invitee. 15 | QString mxid; 16 | 17 | //! The state key of the m.third_party_invite event. 18 | QString token; 19 | 20 | //! A signatures object containing a signature of the entire signed object. 21 | QHash> signatures; 22 | }; 23 | 24 | template <> 25 | struct JsonObjectConverter { 26 | static void dumpTo(QJsonObject& jo, const ThirdPartySigned& pod) 27 | { 28 | addParam(jo, "sender"_L1, pod.sender); 29 | addParam(jo, "mxid"_L1, pod.mxid); 30 | addParam(jo, "token"_L1, pod.token); 31 | addParam(jo, "signatures"_L1, pod.signatures); 32 | } 33 | static void fillFrom(const QJsonObject& jo, ThirdPartySigned& pod) 34 | { 35 | fillFromJson(jo.value("sender"_L1), pod.sender); 36 | fillFromJson(jo.value("mxid"_L1), pod.mxid); 37 | fillFromJson(jo.value("token"_L1), pod.token); 38 | fillFromJson(jo.value("signatures"_L1), pod.signatures); 39 | } 40 | }; 41 | 42 | } // namespace Quotient 43 | -------------------------------------------------------------------------------- /Quotient/csapi/login.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "login.h" 4 | 5 | using namespace Quotient; 6 | 7 | QUrl GetLoginFlowsJob::makeRequestUrl(const HomeserverData& hsData) 8 | { 9 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/login")); 10 | } 11 | 12 | GetLoginFlowsJob::GetLoginFlowsJob() 13 | : BaseJob(HttpVerb::Get, u"GetLoginFlowsJob"_s, makePath("/_matrix/client/v3", "/login"), false) 14 | {} 15 | 16 | LoginJob::LoginJob(const QString& type, const std::optional& identifier, 17 | const QString& password, const QString& token, const QString& deviceId, 18 | const QString& initialDeviceDisplayName, std::optional refreshToken) 19 | : BaseJob(HttpVerb::Post, u"LoginJob"_s, makePath("/_matrix/client/v3", "/login"), false) 20 | { 21 | QJsonObject _dataJson; 22 | addParam(_dataJson, "type"_L1, type); 23 | addParam(_dataJson, "identifier"_L1, identifier); 24 | addParam(_dataJson, "password"_L1, password); 25 | addParam(_dataJson, "token"_L1, token); 26 | addParam(_dataJson, "device_id"_L1, deviceId); 27 | addParam(_dataJson, "initial_device_display_name"_L1, initialDeviceDisplayName); 28 | addParam(_dataJson, "refresh_token"_L1, refreshToken); 29 | setRequestData({ _dataJson }); 30 | addExpectedKey(u"user_id"_s); 31 | addExpectedKey(u"access_token"_s); 32 | addExpectedKey(u"device_id"_s); 33 | } 34 | -------------------------------------------------------------------------------- /Quotient/events/roomcanonicalaliasevent.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Ram Nad 2 | // SPDX-FileCopyrightText: 2020 Kitsune Ral 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #pragma once 6 | 7 | #include "stateevent.h" 8 | 9 | namespace Quotient { 10 | namespace EventContent { 11 | struct AliasesEventContent { 12 | QString canonicalAlias; 13 | QStringList altAliases; 14 | }; 15 | } // namespace EventContent 16 | 17 | template<> 18 | inline EventContent::AliasesEventContent fromJson(const QJsonObject& jo) 19 | { 20 | return EventContent::AliasesEventContent { 21 | fromJson(jo["alias"_L1]), 22 | fromJson(jo["alt_aliases"_L1]) 23 | }; 24 | } 25 | template<> 26 | inline auto toJson(const EventContent::AliasesEventContent& c) 27 | { 28 | QJsonObject jo; 29 | addParam(jo, "alias"_L1, c.canonicalAlias); 30 | addParam(jo, "alt_aliases"_L1, c.altAliases); 31 | return jo; 32 | } 33 | 34 | class QUOTIENT_API RoomCanonicalAliasEvent 35 | : public KeylessStateEventBase { 37 | public: 38 | QUO_EVENT(RoomCanonicalAliasEvent, "m.room.canonical_alias") 39 | using KeylessStateEventBase::KeylessStateEventBase; 40 | 41 | QString alias() const { return content().canonicalAlias; } 42 | QStringList altAliases() const { return content().altAliases; } 43 | }; 44 | } // namespace Quotient 45 | -------------------------------------------------------------------------------- /Quotient/csapi/registration_tokens.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Query if a given registration token is still valid. 10 | //! 11 | //! Queries the server to determine if a given registration token is still 12 | //! valid at the time of request. This is a point-in-time check where the 13 | //! token might still expire by the time it is used. 14 | //! 15 | //! Servers should be sure to rate limit this endpoint to avoid brute force 16 | //! attacks. 17 | class QUOTIENT_API RegistrationTokenValidityJob : public BaseJob { 18 | public: 19 | //! \param token 20 | //! The token to check validity of. 21 | explicit RegistrationTokenValidityJob(const QString& token); 22 | 23 | //! \brief Construct a URL without creating a full-fledged job object 24 | //! 25 | //! This function can be used when a URL for RegistrationTokenValidityJob 26 | //! is necessary but the job itself isn't. 27 | static QUrl makeRequestUrl(const HomeserverData& hsData, const QString& token); 28 | 29 | // Result properties 30 | 31 | //! True if the token is still valid, false otherwise. This should 32 | //! additionally be false if the token is not a recognised token by 33 | //! the server. 34 | bool valid() const { return loadFromJson("valid"_L1); } 35 | }; 36 | 37 | inline auto collectResponse(const RegistrationTokenValidityJob* job) { return job->valid(); } 38 | 39 | } // namespace Quotient 40 | -------------------------------------------------------------------------------- /Quotient/ssosession.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "util.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace Quotient { 12 | class Connection; 13 | 14 | /*! Single sign-on (SSO) session encapsulation 15 | * 16 | * This class is responsible for setting up of a new SSO session, providing 17 | * a URL to be opened (usually, in a web browser) and handling the callback 18 | * response after completing the single sign-on, all the way to actually 19 | * logging the user in. It does NOT open and render the SSO URL, it only does 20 | * the necessary backstage work. 21 | * 22 | * Clients only need to open the URL; the rest is done for them. 23 | * Client code can look something like: 24 | * \code 25 | * QDesktopServices::openUrl( 26 | * connection->prepareForSso(initialDeviceName)->ssoUrl()); 27 | * \endcode 28 | */ 29 | class QUOTIENT_API SsoSession : public QObject { 30 | Q_OBJECT 31 | Q_PROPERTY(QUrl ssoUrl READ ssoUrl CONSTANT) 32 | Q_PROPERTY(QUrl callbackUrl READ callbackUrl CONSTANT) 33 | public: 34 | SsoSession(Connection* connection, const QString& initialDeviceName, 35 | const QString& deviceId = {}); 36 | ~SsoSession() override = default; 37 | 38 | QUrl ssoUrl() const; 39 | QUrl ssoUrlForRegistration() const; 40 | QUrl callbackUrl() const; 41 | 42 | private: 43 | class Private; 44 | ImplPtr d; 45 | }; 46 | } // namespace Quotient 47 | -------------------------------------------------------------------------------- /Quotient/csapi/pusher.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "pusher.h" 4 | 5 | using namespace Quotient; 6 | 7 | QUrl GetPushersJob::makeRequestUrl(const HomeserverData& hsData) 8 | { 9 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/pushers")); 10 | } 11 | 12 | GetPushersJob::GetPushersJob() 13 | : BaseJob(HttpVerb::Get, u"GetPushersJob"_s, makePath("/_matrix/client/v3", "/pushers")) 14 | {} 15 | 16 | PostPusherJob::PostPusherJob(const QString& pushkey, const QString& kind, const QString& appId, 17 | const QString& appDisplayName, const QString& deviceDisplayName, 18 | const QString& profileTag, const QString& lang, 19 | const std::optional& data, std::optional append) 20 | : BaseJob(HttpVerb::Post, u"PostPusherJob"_s, makePath("/_matrix/client/v3", "/pushers/set")) 21 | { 22 | QJsonObject _dataJson; 23 | addParam(_dataJson, "pushkey"_L1, pushkey); 24 | addParam(_dataJson, "kind"_L1, kind); 25 | addParam(_dataJson, "app_id"_L1, appId); 26 | addParam(_dataJson, "app_display_name"_L1, appDisplayName); 27 | addParam(_dataJson, "device_display_name"_L1, deviceDisplayName); 28 | addParam(_dataJson, "profile_tag"_L1, profileTag); 29 | addParam(_dataJson, "lang"_L1, lang); 30 | addParam(_dataJson, "data"_L1, data); 31 | addParam(_dataJson, "append"_L1, append); 32 | setRequestData({ _dataJson }); 33 | } 34 | -------------------------------------------------------------------------------- /quotest/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Carl Schwan 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | set(quotest_SRCS quotest.cpp) 6 | 7 | find_package(${Qt} COMPONENTS Concurrent) 8 | add_executable(quotest ${quotest_SRCS}) 9 | target_link_libraries(quotest PRIVATE ${Qt}::Core ${Qt}::Test ${Qt}::Concurrent ${QUOTIENT_LIB_NAME}) 10 | set_target_properties(quotest PROPERTIES 11 | CXX_STANDARD 23 12 | CXX_EXTENSIONS OFF 13 | VISIBILITY_INLINES_HIDDEN ON 14 | CXX_VISIBILITY_PRESET hidden 15 | ) 16 | 17 | if (MSVC) 18 | target_compile_options(quotest PUBLIC /EHsc /W4 19 | /wd4100 /wd4127 /wd4242 /wd4244 /wd4245 /wd4267 /wd4365 /wd4456 /wd4459 20 | /wd4464 /wd4505 /wd4514 /wd4571 /wd4619 /wd4623 /wd4625 /wd4626 /wd4706 21 | /wd4710 /wd4774 /wd4820 /wd4946 /wd5026 /wd5027) 22 | else() 23 | foreach (FLAG W Wall Wpedantic Wextra Wno-unused-parameter Werror=return-type) 24 | CHECK_CXX_COMPILER_FLAG("-${FLAG}" COMPILER_${FLAG}_SUPPORTED) 25 | if (COMPILER_${FLAG}_SUPPORTED AND 26 | NOT CMAKE_CXX_FLAGS MATCHES "(^| )-?${FLAG}($| )") 27 | target_compile_options(quotest PUBLIC -${FLAG}) 28 | endif () 29 | endforeach () 30 | endif() 31 | 32 | option(${PROJECT_NAME}_INSTALL_TESTS "install quotest application" ON) 33 | add_feature_info(InstallQuotest ${PROJECT_NAME}_INSTALL_TESTS 34 | "the library functional test suite") 35 | 36 | if (${PROJECT_NAME}_INSTALL_TESTS) 37 | install(TARGETS quotest RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 38 | endif () 39 | -------------------------------------------------------------------------------- /Quotient/events/stickerevent.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Carl Schwan 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include "roomevent.h" 7 | #include "eventcontent.h" 8 | 9 | namespace Quotient { 10 | 11 | /// Sticker messages are specialised image messages that are displayed without 12 | /// controls (e.g. no "download" link, or light-box view on click, as would be 13 | /// displayed for for m.image events). 14 | class QUOTIENT_API StickerEvent : public RoomEvent 15 | { 16 | public: 17 | QUO_EVENT(StickerEvent, "m.sticker") 18 | 19 | explicit StickerEvent(const QJsonObject& obj) 20 | : RoomEvent(obj) 21 | , m_imageContent( 22 | EventContent::ImageContent(obj["content"_L1].toObject())) 23 | {} 24 | 25 | /// \brief A textual representation or associated description of the 26 | /// sticker image. 27 | /// 28 | /// This could be the alt text of the original image, or a message to 29 | /// accompany and further describe the sticker. 30 | QUO_CONTENT_GETTER(QString, body) 31 | 32 | /// \brief Metadata about the image referred to in url including a 33 | /// thumbnail representation. 34 | const EventContent::ImageContent& image() const 35 | { 36 | return m_imageContent; 37 | } 38 | 39 | /// \brief The URL to the sticker image. This must be a valid mxc:// URI. 40 | QUrl url() const 41 | { 42 | return m_imageContent.url(); 43 | } 44 | 45 | private: 46 | EventContent::ImageContent m_imageContent; 47 | }; 48 | } // namespace Quotient 49 | -------------------------------------------------------------------------------- /Quotient/csapi/appservice_room_directory.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Updates a room's visibility in the application service's room directory. 10 | //! 11 | //! Updates the visibility of a given room on the application service's room 12 | //! directory. 13 | //! 14 | //! This API is similar to the room directory visibility API used by clients 15 | //! to update the homeserver's more general room directory. 16 | //! 17 | //! This API requires the use of an application service access token (`as_token`) 18 | //! instead of a typical client's access_token. This API cannot be invoked by 19 | //! users who are not identified as application services. 20 | class QUOTIENT_API UpdateAppserviceRoomDirectoryVisibilityJob : public BaseJob { 21 | public: 22 | //! \param networkId 23 | //! The protocol (network) ID to update the room list for. This would 24 | //! have been provided by the application service as being listed as 25 | //! a supported protocol. 26 | //! 27 | //! \param roomId 28 | //! The room ID to add to the directory. 29 | //! 30 | //! \param visibility 31 | //! Whether the room should be visible (public) in the directory 32 | //! or not (private). 33 | explicit UpdateAppserviceRoomDirectoryVisibilityJob(const QString& networkId, 34 | const QString& roomId, 35 | const QString& visibility); 36 | }; 37 | 38 | } // namespace Quotient 39 | -------------------------------------------------------------------------------- /Quotient/e2ee/qolmutility.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Carl Schwan 2 | // 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #include "qolmutility.h" 6 | 7 | #include "e2ee_common.h" 8 | 9 | #include 10 | 11 | using namespace Quotient; 12 | 13 | OlmErrorCode QOlmUtility::lastErrorCode() const { 14 | return olm_utility_last_error_code(olmDataHolder.get()); 15 | } 16 | 17 | const char* QOlmUtility::lastError() const 18 | { 19 | return olm_utility_last_error(olmDataHolder.get()); 20 | } 21 | 22 | QOlmUtility::QOlmUtility() 23 | : olmDataHolder( 24 | makeCStruct(olm_utility, olm_utility_size, olm_clear_utility)) 25 | {} 26 | 27 | QString QOlmUtility::sha256Bytes(const QByteArray& inputBuf) const 28 | { 29 | const auto outputLength = olm_sha256_length(olmDataHolder.get()); 30 | auto outputBuf = byteArrayForOlm(outputLength); 31 | olm_sha256(olmDataHolder.get(), inputBuf.data(), unsignedSize(inputBuf), 32 | outputBuf.data(), outputLength); 33 | 34 | return QString::fromUtf8(outputBuf); 35 | } 36 | 37 | QString QOlmUtility::sha256Utf8Msg(const QString& message) const 38 | { 39 | return sha256Bytes(message.toUtf8()); 40 | } 41 | 42 | bool QOlmUtility::ed25519Verify(const QByteArray& key, const QByteArray& message, 43 | QByteArray signature) const 44 | { 45 | return olm_ed25519_verify(olmDataHolder.get(), key.data(), unsignedSize(key), 46 | message.data(), unsignedSize(message), 47 | signature.data(), unsignedSize(signature)) 48 | == 0; 49 | } 50 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/client_device.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | //! A client device 9 | struct QUOTIENT_API Device { 10 | //! Identifier of this device. 11 | QString deviceId; 12 | 13 | //! Display name set by the user for this device. Absent if no name has been 14 | //! set. 15 | QString displayName{}; 16 | 17 | //! The IP address where this device was last seen. (May be a few minutes out 18 | //! of date, for efficiency reasons). 19 | QString lastSeenIp{}; 20 | 21 | //! The timestamp (in milliseconds since the unix epoch) when this devices 22 | //! was last seen. (May be a few minutes out of date, for efficiency 23 | //! reasons). 24 | std::optional lastSeenTs{}; 25 | }; 26 | 27 | template <> 28 | struct JsonObjectConverter { 29 | static void dumpTo(QJsonObject& jo, const Device& pod) 30 | { 31 | addParam(jo, "device_id"_L1, pod.deviceId); 32 | addParam(jo, "display_name"_L1, pod.displayName); 33 | addParam(jo, "last_seen_ip"_L1, pod.lastSeenIp); 34 | addParam(jo, "last_seen_ts"_L1, pod.lastSeenTs); 35 | } 36 | static void fillFrom(const QJsonObject& jo, Device& pod) 37 | { 38 | fillFromJson(jo.value("device_id"_L1), pod.deviceId); 39 | fillFromJson(jo.value("display_name"_L1), pod.displayName); 40 | fillFromJson(jo.value("last_seen_ip"_L1), pod.lastSeenIp); 41 | fillFromJson(jo.value("last_seen_ts"_L1), pod.lastSeenTs); 42 | } 43 | }; 44 | 45 | } // namespace Quotient 46 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/openid_token.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | struct QUOTIENT_API OpenIdCredentials { 10 | //! An access token the consumer may use to verify the identity of 11 | //! the person who generated the token. This is given to the federation 12 | //! API `GET /openid/userinfo` to verify the user's identity. 13 | QString accessToken; 14 | 15 | //! The string `Bearer`. 16 | QString tokenType; 17 | 18 | //! The homeserver domain the consumer should use when attempting to 19 | //! verify the user's identity. 20 | QString matrixServerName; 21 | 22 | //! The number of seconds before this token expires and a new one must 23 | //! be generated. 24 | int expiresIn; 25 | }; 26 | 27 | template <> 28 | struct JsonObjectConverter { 29 | static void dumpTo(QJsonObject& jo, const OpenIdCredentials& pod) 30 | { 31 | addParam(jo, "access_token"_L1, pod.accessToken); 32 | addParam(jo, "token_type"_L1, pod.tokenType); 33 | addParam(jo, "matrix_server_name"_L1, pod.matrixServerName); 34 | addParam(jo, "expires_in"_L1, pod.expiresIn); 35 | } 36 | static void fillFrom(const QJsonObject& jo, OpenIdCredentials& pod) 37 | { 38 | fillFromJson(jo.value("access_token"_L1), pod.accessToken); 39 | fillFromJson(jo.value("token_type"_L1), pod.tokenType); 40 | fillFromJson(jo.value("matrix_server_name"_L1), pod.matrixServerName); 41 | fillFromJson(jo.value("expires_in"_L1), pod.expiresIn); 42 | } 43 | }; 44 | 45 | } // namespace Quotient 46 | -------------------------------------------------------------------------------- /Quotient/csapi/room_send.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Send a message event to the given room. 10 | //! 11 | //! This endpoint is used to send a message event to a room. Message events 12 | //! allow access to historical events and pagination, making them suited 13 | //! for "once-off" activity in a room. 14 | //! 15 | //! The body of the request should be the content object of the event; the 16 | //! fields in this object will vary depending on the type of event. See 17 | //! [Room Events](/client-server-api/#room-events) for the m. event specification. 18 | class QUOTIENT_API SendMessageJob : public BaseJob { 19 | public: 20 | //! \param roomId 21 | //! The room to send the event to. 22 | //! 23 | //! \param eventType 24 | //! The type of event to send. 25 | //! 26 | //! \param txnId 27 | //! The [transaction ID](/client-server-api/#transaction-identifiers) for this event. Clients 28 | //! should generate an ID unique across requests with the same access token; it will be used 29 | //! by the server to ensure idempotency of requests. 30 | //! 31 | explicit SendMessageJob(const QString& roomId, const QString& eventType, const QString& txnId, 32 | const QJsonObject& content = {}); 33 | 34 | // Result properties 35 | 36 | //! A unique identifier for the event. 37 | QString eventId() const { return loadFromJson("event_id"_L1); } 38 | }; 39 | 40 | inline auto collectResponse(const SendMessageJob* job) { return job->eventId(); } 41 | 42 | } // namespace Quotient 43 | -------------------------------------------------------------------------------- /autotests/key-export.data: -------------------------------------------------------------------------------- 1 | -----BEGIN MEGOLM SESSION DATA----- 2 | ATQrLZT5CO0z1mJGtJcBaa4mabBB0pRII3RsKeXzvtKSAAehIFJ3016VcwFNTWDHPXC6cixX6u9YXWP7rTk7Z5vMVkIPRkUND2SbGmdrXYDWJgN/Ueg++VQZKkTWbiIQ 3 | 6l/h9Xo3Jp8Quskrb1g9kqerF0s9m+z5xfp2plwCzsYwEtvm7mFbS2v5yvY8Gc9c4oaMRU/+W1ZqBB0AznubfcvmU+EbWXtCKk+7EhnCbeD12ve9YLL4G/gfiE9afeRk 4 | T318G8xYYT1eCMt/wxXAiSSmjwXc+Vp1n6qbg7KIGHWmu8eNvnj55aRu9Kk0+jtBXGhrMCGnjiJ3XDyPnjZnBJV81Cb3FIE8o3AAZGZ1aGgxK/PSBScaCI4aoWURd1oP 5 | 4SXNgehJdahqhLNfulagns9Cxx3lo/IlRG9tf28gDE6Bb3IFj5GbH8SUICylHZsJSD+vTSXb7aUNNqjcqowNS4evGFJCzOtfDqkoHyfqiln+8rMQ1OL0MuqEcX/2G6Tt 6 | encqwrJN/6dMgdInABXArSgM1sqiBe59ZRVoml2cSDGekvpsq6sufmS/tg/25tvLVDRs7nwXLRSELAEo3WuWeBugUuJsYWn0b7Nb+ye3TW4ep7tvGh3v1TIhV4UQ9nbv 7 | c6xPJnKS7CHw+xXUEXDg+4o20wY9+6w/lb59549/86GLaa0jPNr4AuV6QcSFy0kXXt2E2y0uzfRUEiUJmMT/YcgiSrgQR4Hc+lVMIDIPhhY5bzgCPytt7u78Y2faX+ef 8 | 9gU+1vHHvc5WeVt3TlVoEx2nDeCMSR/a/waBIV0bkSq0WwgIrYioNNTQ0SchAGHXN5eERJKAuXMd4n6khhXAI0+XCRQFgNn9b20hCCjEq4yb6GQ4cRHmvjtY12Z11VDH 9 | 4L7rmGFN0bvcmvjvrirZXj7Myi0engMHUhvd3zNhVKMpHzPghyDTClUWfnA/amZe1VN12jrkSAxSoLmQYALCBooIu7z/G8jZAZrgr72OmesYg4QE8A0UR+Y2cEWoOKvg 10 | QvmBMnnu/kBIOqhdFfAcoJlWbTcYH/AJJsbqMsb9RmOhmBch8kSXDq7oTuV4iGT6xp47ZejI1St40oAXRjYEL4uJkXyZTvK/jkdjkFPbxHH8zAIcrOWWnczs8sfE7OMa 11 | ASeiPjD0UWNEm8vXJIXE50zvHg0tDoIHlWSrt3khW+UlEEvK4J5oJYRTAUfKzXZrbZaq+C6Q0z30ksuE45aLtSdODlsKAc1fksscqycmVU22/SfvQje5Dv/uD9i8J86Y 12 | +6OuXTzVw0H21+2tIBAjKaCM4jRFDruc/stok5Ixhxepl7184blfhZ4uRbt1hYGjcPfl6viB7lcboN4+i2WyB0xcpaZpQD6UBrglmFO05XKmSfjegR00YhmfS8yFXygs 13 | fBJPNYihZoMmyRJjXqkETS7YVUSRPruMA+y8tyrwRO199LXaBbWORBI4EbFeVZlndGeileYQ6JNB7JdFu+RkeUkvkJPAyM8YD7NxR/5t+vNToJGUshy41CGbnVs30gxz 14 | OPV+SCzfqD8qIjFiDWjsIGj7Hqm0qA== 15 | -----END MEGOLM SESSION DATA----- 16 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/cross_signing_key.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | //! Cross signing key 9 | struct QUOTIENT_API CrossSigningKey { 10 | //! The ID of the user the key belongs to. 11 | QString userId; 12 | 13 | //! What the key is used for. 14 | QStringList usage; 15 | 16 | //! The public key. The object must have exactly one property, whose name is 17 | //! in the form `:`, and whose value 18 | //! is the unpadded base64 public key. 19 | QHash keys; 20 | 21 | //! Signatures of the key, calculated using the process described at [Signing 22 | //! JSON](/appendices/#signing-json). Optional for the master key. Other keys must be signed by 23 | //! the user\'s master key. 24 | QJsonObject signatures{}; 25 | }; 26 | 27 | template <> 28 | struct JsonObjectConverter { 29 | static void dumpTo(QJsonObject& jo, const CrossSigningKey& pod) 30 | { 31 | addParam(jo, "user_id"_L1, pod.userId); 32 | addParam(jo, "usage"_L1, pod.usage); 33 | addParam(jo, "keys"_L1, pod.keys); 34 | addParam(jo, "signatures"_L1, pod.signatures); 35 | } 36 | static void fillFrom(const QJsonObject& jo, CrossSigningKey& pod) 37 | { 38 | fillFromJson(jo.value("user_id"_L1), pod.userId); 39 | fillFromJson(jo.value("usage"_L1), pod.usage); 40 | fillFromJson(jo.value("keys"_L1), pod.keys); 41 | fillFromJson(jo.value("signatures"_L1), pod.signatures); 42 | } 43 | }; 44 | 45 | } // namespace Quotient 46 | -------------------------------------------------------------------------------- /Quotient/csapi/message_pagination.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "message_pagination.h" 4 | 5 | using namespace Quotient; 6 | 7 | auto queryToGetRoomEvents(const QString& from, const QString& to, const QString& dir, 8 | std::optional limit, const QString& filter) 9 | { 10 | QUrlQuery _q; 11 | addParam(_q, u"from"_s, from); 12 | addParam(_q, u"to"_s, to); 13 | addParam(_q, u"dir"_s, dir); 14 | addParam(_q, u"limit"_s, limit); 15 | addParam(_q, u"filter"_s, filter); 16 | return _q; 17 | } 18 | 19 | QUrl GetRoomEventsJob::makeRequestUrl(const HomeserverData& hsData, const QString& roomId, 20 | const QString& dir, const QString& from, const QString& to, 21 | std::optional limit, const QString& filter) 22 | { 23 | return BaseJob::makeRequestUrl(hsData, 24 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/messages"), 25 | queryToGetRoomEvents(from, to, dir, limit, filter)); 26 | } 27 | 28 | GetRoomEventsJob::GetRoomEventsJob(const QString& roomId, const QString& dir, const QString& from, 29 | const QString& to, std::optional limit, 30 | const QString& filter) 31 | : BaseJob(HttpVerb::Get, u"GetRoomEventsJob"_s, 32 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/messages"), 33 | queryToGetRoomEvents(from, to, dir, limit, filter)) 34 | { 35 | addExpectedKey(u"start"_s); 36 | addExpectedKey(u"chunk"_s); 37 | } 38 | -------------------------------------------------------------------------------- /Quotient/csapi/joining.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "joining.h" 4 | 5 | using namespace Quotient; 6 | 7 | JoinRoomByIdJob::JoinRoomByIdJob(const QString& roomId, 8 | const std::optional& thirdPartySigned, 9 | const QString& reason) 10 | : BaseJob(HttpVerb::Post, u"JoinRoomByIdJob"_s, 11 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/join")) 12 | { 13 | QJsonObject _dataJson; 14 | addParam(_dataJson, "third_party_signed"_L1, thirdPartySigned); 15 | addParam(_dataJson, "reason"_L1, reason); 16 | setRequestData({ _dataJson }); 17 | addExpectedKey(u"room_id"_s); 18 | } 19 | 20 | auto queryToJoinRoom(const QStringList& serverName, const QStringList& via) 21 | { 22 | QUrlQuery _q; 23 | addParam(_q, u"server_name"_s, serverName); 24 | addParam(_q, u"via"_s, via); 25 | return _q; 26 | } 27 | 28 | JoinRoomJob::JoinRoomJob(const QString& roomIdOrAlias, const QStringList& serverName, 29 | const QStringList& via, 30 | const std::optional& thirdPartySigned, 31 | const QString& reason) 32 | : BaseJob(HttpVerb::Post, u"JoinRoomJob"_s, 33 | makePath("/_matrix/client/v3", "/join/", roomIdOrAlias), 34 | queryToJoinRoom(serverName, via)) 35 | { 36 | QJsonObject _dataJson; 37 | addParam(_dataJson, "third_party_signed"_L1, thirdPartySigned); 38 | addParam(_dataJson, "reason"_L1, reason); 39 | setRequestData({ _dataJson }); 40 | addExpectedKey(u"room_id"_s); 41 | } 42 | -------------------------------------------------------------------------------- /Quotient/csapi/tags.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "tags.h" 4 | 5 | using namespace Quotient; 6 | 7 | QUrl GetRoomTagsJob::makeRequestUrl(const HomeserverData& hsData, const QString& userId, 8 | const QString& roomId) 9 | { 10 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/user/", userId, 11 | "/rooms/", roomId, "/tags")); 12 | } 13 | 14 | GetRoomTagsJob::GetRoomTagsJob(const QString& userId, const QString& roomId) 15 | : BaseJob(HttpVerb::Get, u"GetRoomTagsJob"_s, 16 | makePath("/_matrix/client/v3", "/user/", userId, "/rooms/", roomId, "/tags")) 17 | {} 18 | 19 | SetRoomTagJob::SetRoomTagJob(const QString& userId, const QString& roomId, const QString& tag, 20 | const Tag& data) 21 | : BaseJob(HttpVerb::Put, u"SetRoomTagJob"_s, 22 | makePath("/_matrix/client/v3", "/user/", userId, "/rooms/", roomId, "/tags/", tag)) 23 | { 24 | setRequestData({ toJson(data) }); 25 | } 26 | 27 | QUrl DeleteRoomTagJob::makeRequestUrl(const HomeserverData& hsData, const QString& userId, 28 | const QString& roomId, const QString& tag) 29 | { 30 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/user/", userId, 31 | "/rooms/", roomId, "/tags/", tag)); 32 | } 33 | 34 | DeleteRoomTagJob::DeleteRoomTagJob(const QString& userId, const QString& roomId, const QString& tag) 35 | : BaseJob(HttpVerb::Delete, u"DeleteRoomTagJob"_s, 36 | makePath("/_matrix/client/v3", "/user/", userId, "/rooms/", roomId, "/tags/", tag)) 37 | {} 38 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/request_token_response.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | struct QUOTIENT_API RequestTokenResponse { 10 | //! The session ID. Session IDs are opaque strings that must consist entirely 11 | //! of the characters `[0-9a-zA-Z.=_-]`. Their length must not exceed 255 12 | //! characters and they must not be empty. 13 | QString sid; 14 | 15 | //! An optional field containing a URL where the client must submit the 16 | //! validation token to, with identical parameters to the Identity Service 17 | //! API's `POST /validate/email/submitToken` endpoint (without the requirement 18 | //! for an access token). The homeserver must send this token to the user (if 19 | //! applicable), who should then be prompted to provide it to the client. 20 | //! 21 | //! If this field is not present, the client can assume that verification 22 | //! will happen without the client's involvement provided the homeserver 23 | //! advertises this specification version in the `/versions` response 24 | //! (ie: r0.5.0). 25 | QUrl submitUrl{}; 26 | }; 27 | 28 | template <> 29 | struct JsonObjectConverter { 30 | static void dumpTo(QJsonObject& jo, const RequestTokenResponse& pod) 31 | { 32 | addParam(jo, "sid"_L1, pod.sid); 33 | addParam(jo, "submit_url"_L1, pod.submitUrl); 34 | } 35 | static void fillFrom(const QJsonObject& jo, RequestTokenResponse& pod) 36 | { 37 | fillFromJson(jo.value("sid"_L1), pod.sid); 38 | fillFromJson(jo.value("submit_url"_L1), pod.submitUrl); 39 | } 40 | }; 41 | 42 | } // namespace Quotient 43 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/key_backup_data.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | //! The key data 9 | struct QUOTIENT_API KeyBackupData { 10 | //! The index of the first message in the session that the key can decrypt. 11 | int firstMessageIndex; 12 | 13 | //! The number of times this key has been forwarded via key-sharing between devices. 14 | int forwardedCount; 15 | 16 | //! Whether the device backing up the key verified the device that the key 17 | //! is from. 18 | bool isVerified; 19 | 20 | //! Algorithm-dependent data. See the documentation for the backup 21 | //! algorithms in [Server-side key backups](/client-server-api/#server-side-key-backups) for 22 | //! more information on the expected format of the data. 23 | QJsonObject sessionData; 24 | }; 25 | 26 | template <> 27 | struct JsonObjectConverter { 28 | static void dumpTo(QJsonObject& jo, const KeyBackupData& pod) 29 | { 30 | addParam(jo, "first_message_index"_L1, pod.firstMessageIndex); 31 | addParam(jo, "forwarded_count"_L1, pod.forwardedCount); 32 | addParam(jo, "is_verified"_L1, pod.isVerified); 33 | addParam(jo, "session_data"_L1, pod.sessionData); 34 | } 35 | static void fillFrom(const QJsonObject& jo, KeyBackupData& pod) 36 | { 37 | fillFromJson(jo.value("first_message_index"_L1), pod.firstMessageIndex); 38 | fillFromJson(jo.value("forwarded_count"_L1), pod.forwardedCount); 39 | fillFromJson(jo.value("is_verified"_L1), pod.isVerified); 40 | fillFromJson(jo.value("session_data"_L1), pod.sessionData); 41 | } 42 | }; 43 | 44 | } // namespace Quotient 45 | -------------------------------------------------------------------------------- /Quotient/csapi/sso_login_redirect.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "sso_login_redirect.h" 4 | 5 | using namespace Quotient; 6 | 7 | auto queryToRedirectToSSO(const QString& redirectUrl) 8 | { 9 | QUrlQuery _q; 10 | addParam(_q, u"redirectUrl"_s, redirectUrl); 11 | return _q; 12 | } 13 | 14 | QUrl RedirectToSSOJob::makeRequestUrl(const HomeserverData& hsData, const QString& redirectUrl) 15 | { 16 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/login/sso/redirect"), 17 | queryToRedirectToSSO(redirectUrl)); 18 | } 19 | 20 | RedirectToSSOJob::RedirectToSSOJob(const QString& redirectUrl) 21 | : BaseJob(HttpVerb::Get, u"RedirectToSSOJob"_s, 22 | makePath("/_matrix/client/v3", "/login/sso/redirect"), 23 | queryToRedirectToSSO(redirectUrl), {}, false) 24 | {} 25 | 26 | auto queryToRedirectToIdP(const QString& redirectUrl) 27 | { 28 | QUrlQuery _q; 29 | addParam(_q, u"redirectUrl"_s, redirectUrl); 30 | return _q; 31 | } 32 | 33 | QUrl RedirectToIdPJob::makeRequestUrl(const HomeserverData& hsData, const QString& idpId, 34 | const QString& redirectUrl) 35 | { 36 | return BaseJob::makeRequestUrl(hsData, 37 | makePath("/_matrix/client/v3", "/login/sso/redirect/", idpId), 38 | queryToRedirectToIdP(redirectUrl)); 39 | } 40 | 41 | RedirectToIdPJob::RedirectToIdPJob(const QString& idpId, const QString& redirectUrl) 42 | : BaseJob(HttpVerb::Get, u"RedirectToIdPJob"_s, 43 | makePath("/_matrix/client/v3", "/login/sso/redirect/", idpId), 44 | queryToRedirectToIdP(redirectUrl), {}, false) 45 | {} 46 | -------------------------------------------------------------------------------- /Quotient/csapi/space_hierarchy.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "space_hierarchy.h" 4 | 5 | using namespace Quotient; 6 | 7 | auto queryToGetSpaceHierarchy(std::optional suggestedOnly, std::optional limit, 8 | std::optional maxDepth, const QString& from) 9 | { 10 | QUrlQuery _q; 11 | addParam(_q, u"suggested_only"_s, suggestedOnly); 12 | addParam(_q, u"limit"_s, limit); 13 | addParam(_q, u"max_depth"_s, maxDepth); 14 | addParam(_q, u"from"_s, from); 15 | return _q; 16 | } 17 | 18 | QUrl GetSpaceHierarchyJob::makeRequestUrl(const HomeserverData& hsData, const QString& roomId, 19 | std::optional suggestedOnly, 20 | std::optional limit, std::optional maxDepth, 21 | const QString& from) 22 | { 23 | return BaseJob::makeRequestUrl(hsData, 24 | makePath("/_matrix/client/v1", "/rooms/", roomId, "/hierarchy"), 25 | queryToGetSpaceHierarchy(suggestedOnly, limit, maxDepth, from)); 26 | } 27 | 28 | GetSpaceHierarchyJob::GetSpaceHierarchyJob(const QString& roomId, std::optional suggestedOnly, 29 | std::optional limit, std::optional maxDepth, 30 | const QString& from) 31 | : BaseJob(HttpVerb::Get, u"GetSpaceHierarchyJob"_s, 32 | makePath("/_matrix/client/v1", "/rooms/", roomId, "/hierarchy"), 33 | queryToGetSpaceHierarchy(suggestedOnly, limit, maxDepth, from)) 34 | { 35 | addExpectedKey(u"rooms"_s); 36 | } 37 | -------------------------------------------------------------------------------- /Quotient/csapi/create_room.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "create_room.h" 4 | 5 | using namespace Quotient; 6 | 7 | CreateRoomJob::CreateRoomJob(const QString& visibility, const QString& roomAliasName, 8 | const QString& name, const QString& topic, const QStringList& invite, 9 | const QVector& invite3pid, const QString& roomVersion, 10 | const QJsonObject& creationContent, 11 | const QVector& initialState, const QString& preset, 12 | std::optional isDirect, 13 | const QJsonObject& powerLevelContentOverride) 14 | : BaseJob(HttpVerb::Post, u"CreateRoomJob"_s, makePath("/_matrix/client/v3", "/createRoom")) 15 | { 16 | QJsonObject _dataJson; 17 | addParam(_dataJson, "visibility"_L1, visibility); 18 | addParam(_dataJson, "room_alias_name"_L1, roomAliasName); 19 | addParam(_dataJson, "name"_L1, name); 20 | addParam(_dataJson, "topic"_L1, topic); 21 | addParam(_dataJson, "invite"_L1, invite); 22 | addParam(_dataJson, "invite_3pid"_L1, invite3pid); 23 | addParam(_dataJson, "room_version"_L1, roomVersion); 24 | addParam(_dataJson, "creation_content"_L1, creationContent); 25 | addParam(_dataJson, "initial_state"_L1, initialState); 26 | addParam(_dataJson, "preset"_L1, preset); 27 | addParam(_dataJson, "is_direct"_L1, isDirect); 28 | addParam(_dataJson, "power_level_content_override"_L1, powerLevelContentOverride); 29 | setRequestData({ _dataJson }); 30 | addExpectedKey(u"room_id"_s); 31 | } 32 | -------------------------------------------------------------------------------- /Quotient/csapi/openid.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace Quotient { 10 | 11 | //! \brief Get an OpenID token object to verify the requester's identity. 12 | //! 13 | //! Gets an OpenID token object that the requester may supply to another 14 | //! service to verify their identity in Matrix. The generated token is only 15 | //! valid for exchanging for user information from the federation API for 16 | //! OpenID. 17 | //! 18 | //! The access token generated is only valid for the OpenID API. It cannot 19 | //! be used to request another OpenID access token or call `/sync`, for 20 | //! example. 21 | class QUOTIENT_API RequestOpenIdTokenJob : public BaseJob { 22 | public: 23 | //! \param userId 24 | //! The user to request an OpenID token for. Should be the user who 25 | //! is authenticated for the request. 26 | //! 27 | //! \param dontUse 28 | //! An empty object. Reserved for future expansion. 29 | explicit RequestOpenIdTokenJob(const QString& userId, const QJsonObject& dontUse = {}); 30 | 31 | // Result properties 32 | 33 | //! OpenID token information. This response is nearly compatible with the 34 | //! response documented in the 35 | //! [OpenID Connect 1.0 36 | //! Specification](http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse) with the 37 | //! only difference being the lack of an `id_token`. Instead, the Matrix homeserver's name is 38 | //! provided. 39 | OpenIdCredentials tokenData() const { return fromJson(jsonData()); } 40 | }; 41 | 42 | inline auto collectResponse(const RequestOpenIdTokenJob* job) { return job->tokenData(); } 43 | 44 | } // namespace Quotient 45 | -------------------------------------------------------------------------------- /Quotient/csapi/redaction.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Strips all non-integrity-critical information out of an event. 10 | //! 11 | //! Strips all information out of an event which isn't critical to the 12 | //! integrity of the server-side representation of the room. 13 | //! 14 | //! This cannot be undone. 15 | //! 16 | //! Any user with a power level greater than or equal to the `m.room.redaction` 17 | //! event power level may send redaction events in the room. If the user's power 18 | //! level greater is also greater than or equal to the `redact` power level 19 | //! of the room, the user may redact events sent by other users. 20 | //! 21 | //! Server administrators may redact events sent by users on their server. 22 | class QUOTIENT_API RedactEventJob : public BaseJob { 23 | public: 24 | //! \param roomId 25 | //! The room from which to redact the event. 26 | //! 27 | //! \param eventId 28 | //! The ID of the event to redact 29 | //! 30 | //! \param txnId 31 | //! The [transaction ID](/client-server-api/#transaction-identifiers) for this event. Clients 32 | //! should generate a unique ID; it will be used by the server to ensure idempotency of 33 | //! requests. 34 | //! 35 | //! \param reason 36 | //! The reason for the event being redacted. 37 | explicit RedactEventJob(const QString& roomId, const QString& eventId, const QString& txnId, 38 | const QString& reason = {}); 39 | 40 | // Result properties 41 | 42 | //! A unique identifier for the event. 43 | QString eventId() const { return loadFromJson("event_id"_L1); } 44 | }; 45 | 46 | inline auto collectResponse(const RedactEventJob* job) { return job->eventId(); } 47 | 48 | } // namespace Quotient 49 | -------------------------------------------------------------------------------- /autotests/testutils.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Carl Schwan 2 | // 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | 13 | namespace Quotient { 14 | 15 | class Connection; 16 | template 17 | class JobHandle; 18 | 19 | std::shared_ptr createTestConnection(QLatin1StringView localUserName, 20 | QLatin1StringView secret, 21 | QLatin1StringView deviceName); 22 | 23 | 24 | template 25 | inline event_ptr_tt loadEventFromFile(const QString &eventFileName) 26 | { 27 | if (!eventFileName.isEmpty()) { 28 | QFile testEventFile; 29 | testEventFile.setFileName(QLatin1StringView(DATA_DIR) + u'/' + eventFileName); 30 | testEventFile.open(QIODevice::ReadOnly); 31 | return loadEvent(QJsonDocument::fromJson(testEventFile.readAll()).object()); 32 | } 33 | return nullptr; 34 | } 35 | } 36 | 37 | #define CREATE_CONNECTION(VAR, USERNAME, SECRET, DEVICE_NAME) \ 38 | const auto VAR = createTestConnection(USERNAME, SECRET, DEVICE_NAME); \ 39 | if (!VAR) \ 40 | QFAIL("Could not set up test connection"); 41 | 42 | template 43 | inline bool waitForJob(const Quotient::JobHandle& job) 44 | { 45 | const auto& [timeouts, retryIntervals, _] = job->currentBackoffStrategy(); 46 | return QTest::qWaitFor([job] { return job.isFinished(); }, 47 | std::chrono::milliseconds( 48 | std::reduce(timeouts.cbegin(), timeouts.cend()) 49 | + std::reduce(retryIntervals.cbegin(), retryIntervals.cend())) 50 | .count()); 51 | } 52 | -------------------------------------------------------------------------------- /Quotient/csapi/directory.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "directory.h" 4 | 5 | using namespace Quotient; 6 | 7 | SetRoomAliasJob::SetRoomAliasJob(const QString& roomAlias, const QString& roomId) 8 | : BaseJob(HttpVerb::Put, u"SetRoomAliasJob"_s, 9 | makePath("/_matrix/client/v3", "/directory/room/", roomAlias)) 10 | { 11 | QJsonObject _dataJson; 12 | addParam(_dataJson, "room_id"_L1, roomId); 13 | setRequestData({ _dataJson }); 14 | } 15 | 16 | QUrl GetRoomIdByAliasJob::makeRequestUrl(const HomeserverData& hsData, const QString& roomAlias) 17 | { 18 | return BaseJob::makeRequestUrl(hsData, 19 | makePath("/_matrix/client/v3", "/directory/room/", roomAlias)); 20 | } 21 | 22 | GetRoomIdByAliasJob::GetRoomIdByAliasJob(const QString& roomAlias) 23 | : BaseJob(HttpVerb::Get, u"GetRoomIdByAliasJob"_s, 24 | makePath("/_matrix/client/v3", "/directory/room/", roomAlias), false) 25 | {} 26 | 27 | QUrl DeleteRoomAliasJob::makeRequestUrl(const HomeserverData& hsData, const QString& roomAlias) 28 | { 29 | return BaseJob::makeRequestUrl(hsData, 30 | makePath("/_matrix/client/v3", "/directory/room/", roomAlias)); 31 | } 32 | 33 | DeleteRoomAliasJob::DeleteRoomAliasJob(const QString& roomAlias) 34 | : BaseJob(HttpVerb::Delete, u"DeleteRoomAliasJob"_s, 35 | makePath("/_matrix/client/v3", "/directory/room/", roomAlias)) 36 | {} 37 | 38 | QUrl GetLocalAliasesJob::makeRequestUrl(const HomeserverData& hsData, const QString& roomId) 39 | { 40 | return BaseJob::makeRequestUrl(hsData, 41 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/aliases")); 42 | } 43 | 44 | GetLocalAliasesJob::GetLocalAliasesJob(const QString& roomId) 45 | : BaseJob(HttpVerb::Get, u"GetLocalAliasesJob"_s, 46 | makePath("/_matrix/client/v3", "/rooms/", roomId, "/aliases")) 47 | { 48 | addExpectedKey(u"aliases"_s); 49 | } 50 | -------------------------------------------------------------------------------- /Quotient/e2ee/sssshandler.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include "cryptoutils.h" 7 | 8 | #include "../connection.h" 9 | 10 | #include 11 | #include 12 | 13 | namespace Quotient { 14 | class QUOTIENT_API SSSSHandler : public QObject 15 | { 16 | Q_OBJECT 17 | Q_PROPERTY(Quotient::Connection* connection READ connection WRITE setConnection NOTIFY connectionChanged) 18 | 19 | public: 20 | enum Error 21 | { 22 | WrongKeyError, 23 | NoKeyError, 24 | DecryptionError, 25 | InvalidSignatureError, 26 | UnsupportedAlgorithmError, 27 | }; 28 | Q_ENUM(Error) 29 | 30 | using QObject::QObject; 31 | 32 | //! \brief Unlock the secret backup from the given passprhase 33 | Q_INVOKABLE void unlockSSSSWithPassphrase(const QString& passphrase); 34 | 35 | //! \brief Unlock the secret backup by requesting the decryption keys from other devices 36 | Q_INVOKABLE void unlockSSSSFromCrossSigning(); 37 | 38 | //! \brief Unlock the secret backup from the given security key 39 | Q_INVOKABLE void unlockSSSSFromSecurityKey(const QString& encodedKey); 40 | 41 | Connection* connection() const; 42 | void setConnection(Connection* connection); 43 | 44 | Q_SIGNALS: 45 | void keyBackupUnlocked(); 46 | void error(Error error); 47 | void connectionChanged(); 48 | 49 | //! \brief Emitted after keys are loaded 50 | void finished(); 51 | 52 | private: 53 | QPointer m_connection; 54 | 55 | //! \brief Decrypt the key with this name from the account data 56 | QByteArray decryptKey(event_type_t keyType, const QString& defaultKey, key_view_t decryptionKey); 57 | 58 | void loadMegolmBackup(const QByteArray& megolmDecryptionKey); 59 | struct UnlockData; 60 | void unlockAndLoad(const UnlockData& unlockData, key_view_t decryptingKey); 61 | }; 62 | } // namespace Quotient 63 | -------------------------------------------------------------------------------- /Quotient/csapi/banning.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Ban a user in the room. 10 | //! 11 | //! Ban a user in the room. If the user is currently in the room, also kick them. 12 | //! 13 | //! When a user is banned from a room, they may not join it or be invited to it until they are 14 | //! unbanned. 15 | //! 16 | //! The caller must have the required power level in order to perform this operation. 17 | class QUOTIENT_API BanJob : public BaseJob { 18 | public: 19 | //! \param roomId 20 | //! The room identifier (not alias) from which the user should be banned. 21 | //! 22 | //! \param userId 23 | //! The fully qualified user ID of the user being banned. 24 | //! 25 | //! \param reason 26 | //! The reason the user has been banned. This will be supplied as the `reason` on the target's 27 | //! updated [`m.room.member`](/client-server-api/#mroommember) event. 28 | explicit BanJob(const QString& roomId, const QString& userId, const QString& reason = {}); 29 | }; 30 | 31 | //! \brief Unban a user from the room. 32 | //! 33 | //! Unban a user from the room. This allows them to be invited to the room, 34 | //! and join if they would otherwise be allowed to join according to its join rules. 35 | //! 36 | //! The caller must have the required power level in order to perform this operation. 37 | class QUOTIENT_API UnbanJob : public BaseJob { 38 | public: 39 | //! \param roomId 40 | //! The room identifier (not alias) from which the user should be unbanned. 41 | //! 42 | //! \param userId 43 | //! The fully qualified user ID of the user being unbanned. 44 | //! 45 | //! \param reason 46 | //! Optional reason to be included as the `reason` on the subsequent 47 | //! membership event. 48 | explicit UnbanJob(const QString& roomId, const QString& userId, const QString& reason = {}); 49 | }; 50 | 51 | } // namespace Quotient 52 | -------------------------------------------------------------------------------- /Quotient/events/roommemberevent.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2015 Felix Rohrbach 2 | // SPDX-FileCopyrightText: 2017 Kitsune Ral 3 | // SPDX-FileCopyrightText: 2019 Karol Kosek 4 | // SPDX-License-Identifier: LGPL-2.1-or-later 5 | 6 | #pragma once 7 | 8 | #include "stateevent.h" 9 | #include 10 | 11 | namespace Quotient { 12 | class QUOTIENT_API MemberEventContent { 13 | public: 14 | Q_IMPLICIT MemberEventContent(Membership ms) : membership(ms) {} 15 | explicit MemberEventContent(const QJsonObject& json); 16 | QJsonObject toJson() const; 17 | 18 | Membership membership; 19 | /// (Only for invites) Whether the invite is to a direct chat 20 | bool isDirect = false; 21 | std::optional displayName; 22 | std::optional avatarUrl; 23 | QString reason; 24 | }; 25 | 26 | class QUOTIENT_API RoomMemberEvent 27 | : public KeyedStateEventBase { 28 | Q_GADGET 29 | public: 30 | QUO_EVENT(RoomMemberEvent, "m.room.member") 31 | 32 | static bool isValid(const QJsonObject& fullJson) 33 | { 34 | return !fullJson[StateKeyKey].toString().isEmpty(); 35 | } 36 | 37 | using KeyedStateEventBase::KeyedStateEventBase; 38 | 39 | Membership membership() const { return content().membership; } 40 | QString userId() const { return stateKey(); } 41 | bool isDirect() const { return content().isDirect; } 42 | std::optional newDisplayName() const { return content().displayName; } 43 | std::optional newAvatarUrl() const { return content().avatarUrl; } 44 | QString reason() const { return content().reason; } 45 | bool changesMembership() const; 46 | bool isBan() const; 47 | bool isUnban() const; 48 | bool isInvite() const; 49 | bool isRejectedInvite() const; 50 | bool isJoin() const; 51 | bool isLeave() const; 52 | bool isRename() const; 53 | bool isAvatarUpdate() const; 54 | }; 55 | } // namespace Quotient 56 | -------------------------------------------------------------------------------- /autotests/testutils.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Carl Schwan 2 | // SPDX-FileCopyrightText: 2022 Kitsune Ral 3 | // 4 | // SPDX-License-Identifier: LGPL-2.1-or-later 5 | 6 | #include "testutils.h" 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | using Quotient::Connection; 14 | 15 | bool waitForSignal(auto objPtr, auto signal) 16 | { 17 | return QSignalSpy(std::to_address(objPtr), signal).wait(60000); 18 | } 19 | 20 | std::shared_ptr Quotient::createTestConnection(QLatin1StringView localUserName, 21 | QLatin1StringView secret, 22 | QLatin1StringView deviceName) 23 | { 24 | static constexpr auto homeserverAddr = "localhost:1234"_L1; 25 | auto* const nam = NetworkAccessManager::instance(); 26 | QObject::connect(nam, &QNetworkAccessManager::sslErrors, nam, 27 | [](QNetworkReply* reply) { reply->ignoreSslErrors(); }); 28 | 29 | auto c = std::make_shared(); 30 | c->enableEncryption(true); 31 | const QString userId{ u'@' % localUserName % u':' % homeserverAddr }; 32 | c->setHomeserver(QUrl(u"https://" % homeserverAddr)); 33 | if (!waitForSignal(c, &Connection::loginFlowsChanged) 34 | || !c->supportsPasswordAuth()) { 35 | qCritical().noquote() << "Can't use password login at" << homeserverAddr 36 | << "- check that the homeserver is running"; 37 | return nullptr; 38 | } 39 | c->loginWithPassword(localUserName, secret, deviceName); 40 | if (!waitForSignal(c, &Connection::connected)) { 41 | qCritical().noquote() 42 | << "Could not achieve the logged in state for" << userId 43 | << "- check the credentials in the test code and at the homeserver"; 44 | return nullptr; 45 | } 46 | return c; 47 | } 48 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/push_rule.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace Quotient { 10 | 11 | struct QUOTIENT_API PushRule { 12 | //! The actions to perform when this rule is matched. 13 | QVector actions; 14 | 15 | //! Whether this is a default rule, or has been set explicitly. 16 | bool isDefault; 17 | 18 | //! Whether the push rule is enabled or not. 19 | bool enabled; 20 | 21 | //! The ID of this rule. 22 | QString ruleId; 23 | 24 | //! The conditions that must hold true for an event in order for a rule to be 25 | //! applied to an event. A rule with no conditions always matches. Only 26 | //! applicable to `underride` and `override` rules. 27 | QVector conditions{}; 28 | 29 | //! The [glob-style pattern](/appendices#glob-style-matching) to match against. 30 | //! Only applicable to `content` rules. 31 | QString pattern{}; 32 | }; 33 | 34 | template <> 35 | struct JsonObjectConverter { 36 | static void dumpTo(QJsonObject& jo, const PushRule& pod) 37 | { 38 | addParam(jo, "actions"_L1, pod.actions); 39 | addParam(jo, "default"_L1, pod.isDefault); 40 | addParam(jo, "enabled"_L1, pod.enabled); 41 | addParam(jo, "rule_id"_L1, pod.ruleId); 42 | addParam(jo, "conditions"_L1, pod.conditions); 43 | addParam(jo, "pattern"_L1, pod.pattern); 44 | } 45 | static void fillFrom(const QJsonObject& jo, PushRule& pod) 46 | { 47 | fillFromJson(jo.value("actions"_L1), pod.actions); 48 | fillFromJson(jo.value("default"_L1), pod.isDefault); 49 | fillFromJson(jo.value("enabled"_L1), pod.enabled); 50 | fillFromJson(jo.value("rule_id"_L1), pod.ruleId); 51 | fillFromJson(jo.value("conditions"_L1), pod.conditions); 52 | fillFromJson(jo.value("pattern"_L1), pod.pattern); 53 | } 54 | }; 55 | 56 | } // namespace Quotient 57 | -------------------------------------------------------------------------------- /Quotient/events/eventrelation.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include "eventrelation.h" 5 | 6 | #include "../logging_categories_p.h" 7 | #include "roomevent.h" 8 | 9 | using namespace Quotient; 10 | 11 | void JsonObjectConverter::dumpTo(QJsonObject& jo, 12 | const EventRelation& pod) 13 | { 14 | if (pod.type.isEmpty()) { 15 | qCWarning(MAIN) << "Empty relation type; won't dump to JSON"; 16 | return; 17 | } 18 | 19 | if (pod.type == EventRelation::ReplyType) { 20 | jo.insert(EventRelation::ReplyType, QJsonObject{{EventIdKey, pod.eventId}}); 21 | return; 22 | } 23 | 24 | jo.insert(RelTypeKey, pod.type); 25 | jo.insert(EventIdKey, pod.eventId); 26 | if (pod.type == EventRelation::AnnotationType) 27 | jo.insert("key"_L1, pod.key); 28 | if (pod.type == EventRelation::ThreadType) { 29 | jo.insert(EventRelation::ReplyType, QJsonObject{{EventIdKey, pod.inThreadReplyEventId}}); 30 | jo.insert(IsFallingBackKey, pod.isFallingBack); 31 | } 32 | } 33 | 34 | void JsonObjectConverter::fillFrom(const QJsonObject& jo, 35 | EventRelation& pod) 36 | { 37 | const auto replyJson = jo.value(EventRelation::ReplyType).toObject(); 38 | if (!replyJson.isEmpty() && jo.value(RelTypeKey).isUndefined()) { 39 | pod.type = EventRelation::ReplyType; 40 | fromJson(replyJson[EventIdKey], pod.eventId); 41 | return; 42 | } 43 | 44 | // The experimental logic for generic relationships (MSC1849) 45 | fromJson(jo[RelTypeKey], pod.type); 46 | fromJson(jo[EventIdKey], pod.eventId); 47 | if (pod.type == EventRelation::AnnotationType) 48 | fromJson(jo["key"_L1], pod.key); 49 | if (pod.type == EventRelation::ThreadType) { 50 | fromJson(replyJson[EventIdKey], pod.inThreadReplyEventId); 51 | } 52 | fromJson(jo[IsFallingBackKey], pod.isFallingBack); 53 | } 54 | -------------------------------------------------------------------------------- /Quotient/events/roompowerlevelsevent.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Black Hat 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include "roompowerlevelsevent.h" 5 | 6 | using namespace Quotient; 7 | 8 | PowerLevelsEventContent JsonConverter::load(const QJsonValue& jv) 9 | { 10 | const auto& jo = jv.toObject(); 11 | PowerLevelsEventContent c; 12 | #define FROM_JSON(member) fromJson(jo[toSnakeCase(#member##_L1)], c.member) 13 | FROM_JSON(invite); 14 | FROM_JSON(kick); 15 | FROM_JSON(ban); 16 | FROM_JSON(redact); 17 | FROM_JSON(events); 18 | FROM_JSON(eventsDefault); 19 | FROM_JSON(stateDefault); 20 | FROM_JSON(users); 21 | FROM_JSON(usersDefault); 22 | fromJson(jo["notifications"_L1]["room"_L1], c.notifications.room); 23 | #undef FROM_JSON 24 | return c; 25 | } 26 | 27 | QJsonObject JsonConverter::dump(const PowerLevelsEventContent& c) { 28 | return QJsonObject{ { u"invite"_s, c.invite }, 29 | { u"kick"_s, c.kick }, 30 | { u"ban"_s, c.ban }, 31 | { u"redact"_s, c.redact }, 32 | { u"events"_s, Quotient::toJson(c.events) }, 33 | { u"events_default"_s, c.eventsDefault }, 34 | { u"state_default"_s, c.stateDefault }, 35 | { u"users"_s, Quotient::toJson(c.users) }, 36 | { u"users_default"_s, c.usersDefault }, 37 | { u"notifications"_s, QJsonObject{ { u"room"_s, c.notifications.room } } } }; 38 | } 39 | 40 | int RoomPowerLevelsEvent::powerLevelForEvent(const QString& eventTypeId) const 41 | { 42 | return events().value(eventTypeId, eventsDefault()); 43 | } 44 | 45 | int RoomPowerLevelsEvent::powerLevelForState(const QString& eventTypeId) const 46 | { 47 | return events().value(eventTypeId, stateDefault()); 48 | } 49 | 50 | int RoomPowerLevelsEvent::powerLevelForUser(const QString& userId) const 51 | { 52 | return users().value(userId, usersDefault()); 53 | } 54 | -------------------------------------------------------------------------------- /Quotient/csapi/definitions/device_keys.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | //! Device identity keys 9 | struct QUOTIENT_API DeviceKeys { 10 | //! The ID of the user the device belongs to. Must match the user ID used 11 | //! when logging in. 12 | QString userId; 13 | 14 | //! The ID of the device these keys belong to. Must match the device ID used 15 | //! when logging in. 16 | QString deviceId; 17 | 18 | //! The encryption algorithms supported by this device. 19 | QStringList algorithms; 20 | 21 | //! Public identity keys. The names of the properties should be in the 22 | //! format `:`. The keys themselves should be 23 | //! encoded as specified by the key algorithm. 24 | QHash keys; 25 | 26 | //! Signatures for the device key object. A map from user ID, to a map from 27 | //! `:` to the signature. 28 | //! 29 | //! The signature is calculated using the process described at [Signing 30 | //! JSON](/appendices/#signing-json). 31 | QHash> signatures; 32 | }; 33 | 34 | template <> 35 | struct JsonObjectConverter { 36 | static void dumpTo(QJsonObject& jo, const DeviceKeys& pod) 37 | { 38 | addParam(jo, "user_id"_L1, pod.userId); 39 | addParam(jo, "device_id"_L1, pod.deviceId); 40 | addParam(jo, "algorithms"_L1, pod.algorithms); 41 | addParam(jo, "keys"_L1, pod.keys); 42 | addParam(jo, "signatures"_L1, pod.signatures); 43 | } 44 | static void fillFrom(const QJsonObject& jo, DeviceKeys& pod) 45 | { 46 | fillFromJson(jo.value("user_id"_L1), pod.userId); 47 | fillFromJson(jo.value("device_id"_L1), pod.deviceId); 48 | fillFromJson(jo.value("algorithms"_L1), pod.algorithms); 49 | fillFromJson(jo.value("keys"_L1), pod.keys); 50 | fillFromJson(jo.value("signatures"_L1), pod.signatures); 51 | } 52 | }; 53 | 54 | } // namespace Quotient 55 | -------------------------------------------------------------------------------- /Quotient/events/simplestateevents.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2017 Kitsune Ral 2 | // SPDX-FileCopyrightText: 2024 James Graham 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #pragma once 6 | 7 | #include "stateevent.h" 8 | #include "single_key_value.h" 9 | 10 | namespace Quotient { 11 | 12 | #define DEFINE_SIMPLE_STATE_EVENT(Name_, TypeId_, ValueType_, GetterName_, JsonKey_) \ 13 | constexpr inline auto Name_##Key = JsonKey_##_L1; \ 14 | class QUOTIENT_API Name_ : public ::Quotient::KeylessStateEventBase< \ 15 | Name_, EventContent::SingleKeyValue> { \ 16 | public: \ 17 | using value_type = ValueType_; \ 18 | QUO_EVENT(Name_, TypeId_) \ 19 | using KeylessStateEventBase::KeylessStateEventBase; \ 20 | auto GetterName_() const { return content().value; } \ 21 | }; \ 22 | // End of macro 23 | 24 | DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name, "name") 25 | DEFINE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic", QString, topic, 26 | "topic") 27 | DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEventsEvent, "m.room.pinned_events", 28 | QStringList, pinnedEvents, "pinned") 29 | 30 | class QUOTIENT_API RoomServerAclEvent : public StateEvent { 31 | public: 32 | QUO_EVENT(RoomServerAclEvent, "m.room.server_acl") 33 | 34 | using StateEvent::StateEvent; 35 | 36 | QUO_CONTENT_GETTER(QStringList, allow) 37 | QUO_CONTENT_GETTER(bool, allowIpLiterals) 38 | QUO_CONTENT_GETTER(QStringList, deny) 39 | }; 40 | } // namespace Quotient 41 | -------------------------------------------------------------------------------- /Quotient/events/eventrelation.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | namespace Quotient { 9 | 10 | constexpr inline auto RelatesToKey = "m.relates_to"_L1; 11 | constexpr inline auto RelTypeKey = "rel_type"_L1; 12 | constexpr inline auto IsFallingBackKey = "is_falling_back"_L1; 13 | 14 | struct QUOTIENT_API EventRelation { 15 | using reltypeid_t = QLatin1String; 16 | 17 | QString type; 18 | QString eventId; 19 | QString key = {}; // Only used for m.annotation for now 20 | bool isFallingBack = false; 21 | // Only used for m.thread to provide the reply event fallback for non-threaded clients 22 | // or to allow a reply within the thread. 23 | QString inThreadReplyEventId = {}; 24 | 25 | static constexpr auto ReplyType = "m.in_reply_to"_L1; 26 | static constexpr auto AnnotationType = "m.annotation"_L1; 27 | static constexpr auto ReplacementType = "m.replace"_L1; 28 | static constexpr auto ThreadType = "m.thread"_L1; 29 | 30 | static EventRelation replyTo(QString eventId) 31 | { 32 | return { ReplyType, std::move(eventId) }; 33 | } 34 | static EventRelation annotate(QString eventId, QString key) 35 | { 36 | return { AnnotationType, std::move(eventId), std::move(key) }; 37 | } 38 | static EventRelation replace(QString eventId) 39 | { 40 | return { ReplacementType, std::move(eventId) }; 41 | } 42 | static EventRelation replyInThread(QString threadRootId, bool isFallingBack, 43 | QString inThreadReplyEventId) 44 | { 45 | return { 46 | ThreadType, std::move(threadRootId), {}, isFallingBack, std::move(inThreadReplyEventId) 47 | }; 48 | } 49 | }; 50 | 51 | template <> 52 | struct QUOTIENT_API JsonObjectConverter { 53 | static void dumpTo(QJsonObject& jo, const EventRelation& pod); 54 | static void fillFrom(const QJsonObject& jo, EventRelation& pod); 55 | }; 56 | 57 | } // namespace Quotient 58 | -------------------------------------------------------------------------------- /autotests/testkeyimport.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | class TestKeyImport : public QObject 11 | { 12 | Q_OBJECT 13 | private slots: 14 | void testImport(); 15 | void testExport(); 16 | }; 17 | 18 | using namespace Quotient; 19 | 20 | void TestKeyImport::testImport() 21 | { 22 | KeyImport keyImport; 23 | 24 | auto path = QString::fromUtf8(__FILE__); 25 | path = path.left(path.lastIndexOf(QDir::separator())); 26 | path += "/key-export.data"_L1; 27 | QFile file(path); 28 | file.open(QIODevice::ReadOnly); 29 | auto data = file.readAll(); 30 | QVERIFY(!data.isEmpty()); 31 | const auto result = keyImport.decrypt(QString::fromUtf8(data), u"123passphrase"_s); 32 | QVERIFY(result.has_value()); 33 | const auto &json = result.value(); 34 | QCOMPARE(json.size(), 2); 35 | } 36 | 37 | void TestKeyImport::testExport() 38 | { 39 | KeyImport keyImport; 40 | 41 | QJsonArray sessions; 42 | sessions += QJsonObject{ 43 | { "algorithm"_L1, "m.megolm.v1.aes-sha2"_L1 }, 44 | { "forwarding_curve25519_key_chain"_L1, QJsonArray() }, 45 | { "room_id"_L1, "!asdf:foo.bar"_L1 }, 46 | { "sender_claimed_keys"_L1, QJsonObject{ { "ed25519"_L1, "asdfkey"_L1 } } }, 47 | { "sender_key"_L1, "senderkey"_L1 }, 48 | { "session_id"_L1, "sessionidasdf"_L1 }, 49 | { "session_key"_L1, "sessionkeyfoo"_L1 }, 50 | 51 | }; 52 | auto result = keyImport.encrypt(sessions, u"a passphrase"_s); 53 | QVERIFY(result.has_value()); 54 | QVERIFY(result.value().size() > 0); 55 | 56 | auto plain = keyImport.decrypt(QString::fromLatin1(result.value()), u"a passphrase"_s); 57 | QVERIFY(plain.has_value()); 58 | auto value = plain.value(); 59 | QCOMPARE(value.size(), 1); 60 | QCOMPARE(value[0]["algorithm"_L1].toString(), "m.megolm.v1.aes-sha2"_L1); 61 | } 62 | 63 | QTEST_GUILESS_MAIN(TestKeyImport) 64 | #include "testkeyimport.moc" 65 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ----------------------------- | 7 | | dev | :white_check_mark: (unstable) | 8 | | 0.9.x | :white_check_mark: | 9 | | 0.8.x | :white_check_mark: | 10 | | older | :x: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | If you find a vulnerability, or evidence of one, use either of the following contacts: 15 | - via email: [Kitsune Ral](mailto:Kitsune-Ral@users.sf.net); or 16 | - via Matrix: [direct chat with @kitsune:matrix.org](https://matrix.to/#/@kitsune:matrix.org?action=chat). 17 | 18 | In any of these two options, indicate that you have such information (do not share it yet), and we'll tell you the next steps. 19 | 20 | By default, we will give credit to anyone who reports a vulnerability in a responsible way so that we can fix it before public disclosure. 21 | If you want to remain anonymous or pseudonymous instead, please let us know; we will gladly respect your wishes. 22 | NEVER provide a fix as a PR upfront and be very cautious about pushing fixes 23 | to public repos (e.g. at non-private GitHub), as all of this can reveal 24 | the vulnerability itself. 25 | 26 | ## Timeline and commitments 27 | 28 | Initial reaction to the message about a vulnerability (see above) will be 29 | no more than 5 days. From the moment of the private report or public disclosure 30 | (if it hasn't been reported earlier in private) of each vulnerability, we take 31 | effort to fix it on priority before any other issues. In case of vulnerabilities 32 | with [CVSS v2](https://nvd.nist.gov/cvss.cfm) score of 4.0 and higher 33 | the commitment is to provide a workaround within 30 days and a full fix 34 | within 60 days after the project has been made aware about the vulnerability 35 | (in private or in public). For vulnerabilities with lower score there is 36 | no commitment on the timeline, only prioritisation. The full fix doesn't imply 37 | that all software functionality remains accessible (in the worst case 38 | the vulnerable functionality may be disabled or removed to prevent the attack). 39 | -------------------------------------------------------------------------------- /autotests/callcandidateseventtest.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Carl Schwan 2 | // 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #include 6 | 7 | #include 8 | 9 | class TestCallCandidatesEvent : public QObject 10 | { 11 | Q_OBJECT 12 | 13 | private Q_SLOTS: 14 | void fromJson(); 15 | }; 16 | 17 | void TestCallCandidatesEvent::fromJson() 18 | { 19 | auto document = QJsonDocument::fromJson(QByteArrayLiteral(R"({ 20 | "age": 242352, 21 | "content": { 22 | "call_id": "12345", 23 | "candidates": [ 24 | { 25 | "candidate": "candidate:863018703 1 udp 2122260223 10.9.64.156 43670 typ host generation 0", 26 | "sdpMLineIndex": 0, 27 | "sdpMid": "audio" 28 | } 29 | ], 30 | "version": 0 31 | }, 32 | "event_id": "$WLGTSEFSEF:localhost", 33 | "origin_server_ts": 1431961217939, 34 | "room_id": "!Cuyf34gef24t:localhost", 35 | "sender": "@example:localhost", 36 | "type": "m.call.candidates" 37 | })")); 38 | 39 | QVERIFY(document.isObject()); 40 | 41 | auto object = document.object(); 42 | 43 | using namespace Quotient; 44 | const auto& callCandidatesEvent = loadEvent(object); 45 | QVERIFY(callCandidatesEvent); 46 | QVERIFY(callCandidatesEvent->is()); 47 | 48 | QCOMPARE(callCandidatesEvent->version(), 0); 49 | QCOMPARE(callCandidatesEvent->callId(), u"12345"); 50 | QCOMPARE(callCandidatesEvent->candidates().size(), 1); 51 | 52 | const auto& candidate = callCandidatesEvent->candidates().at(0).toObject(); 53 | QCOMPARE(candidate.value("sdpMid"_L1).toString(), u"audio"); 54 | QCOMPARE(candidate.value("sdpMLineIndex"_L1).toInt(), 0); 55 | QCOMPARE(candidate.value("candidate"_L1).toString(), 56 | u"candidate:863018703 1 udp 2122260223 10.9.64.156 43670 typ host generation 0"); 57 | } 58 | 59 | QTEST_APPLESS_MAIN(TestCallCandidatesEvent) 60 | #include "callcandidateseventtest.moc" 61 | -------------------------------------------------------------------------------- /Quotient/csapi/device_management.cpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #include "device_management.h" 4 | 5 | using namespace Quotient; 6 | 7 | QUrl GetDevicesJob::makeRequestUrl(const HomeserverData& hsData) 8 | { 9 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/devices")); 10 | } 11 | 12 | GetDevicesJob::GetDevicesJob() 13 | : BaseJob(HttpVerb::Get, u"GetDevicesJob"_s, makePath("/_matrix/client/v3", "/devices")) 14 | {} 15 | 16 | QUrl GetDeviceJob::makeRequestUrl(const HomeserverData& hsData, const QString& deviceId) 17 | { 18 | return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/devices/", deviceId)); 19 | } 20 | 21 | GetDeviceJob::GetDeviceJob(const QString& deviceId) 22 | : BaseJob(HttpVerb::Get, u"GetDeviceJob"_s, 23 | makePath("/_matrix/client/v3", "/devices/", deviceId)) 24 | {} 25 | 26 | UpdateDeviceJob::UpdateDeviceJob(const QString& deviceId, const QString& displayName) 27 | : BaseJob(HttpVerb::Put, u"UpdateDeviceJob"_s, 28 | makePath("/_matrix/client/v3", "/devices/", deviceId)) 29 | { 30 | QJsonObject _dataJson; 31 | addParam(_dataJson, "display_name"_L1, displayName); 32 | setRequestData({ _dataJson }); 33 | } 34 | 35 | DeleteDeviceJob::DeleteDeviceJob(const QString& deviceId, 36 | const std::optional& auth) 37 | : BaseJob(HttpVerb::Delete, u"DeleteDeviceJob"_s, 38 | makePath("/_matrix/client/v3", "/devices/", deviceId)) 39 | { 40 | QJsonObject _dataJson; 41 | addParam(_dataJson, "auth"_L1, auth); 42 | setRequestData({ _dataJson }); 43 | } 44 | 45 | DeleteDevicesJob::DeleteDevicesJob(const QStringList& devices, 46 | const std::optional& auth) 47 | : BaseJob(HttpVerb::Post, u"DeleteDevicesJob"_s, 48 | makePath("/_matrix/client/v3", "/delete_devices")) 49 | { 50 | QJsonObject _dataJson; 51 | addParam(_dataJson, "devices"_L1, devices); 52 | addParam(_dataJson, "auth"_L1, auth); 53 | setRequestData({ _dataJson }); 54 | } 55 | -------------------------------------------------------------------------------- /Quotient/jobs/syncjob.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2016 Kitsune Ral 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include "syncjob.h" 5 | 6 | #include "../logging_categories_p.h" 7 | 8 | using namespace Quotient; 9 | 10 | static size_t jobId = 0; 11 | 12 | SyncJob::SyncJob(const QString& since, const QString& filter, int timeout, const QString& presence) 13 | : BaseJob(HttpVerb::Get, "SyncJob-"_L1 + QString::number(++jobId), "_matrix/client/r0/sync") 14 | { 15 | setLoggingCategory(SYNCJOB); 16 | QUrlQuery query; 17 | addParam(query, u"filter"_s, filter); 18 | addParam(query, u"set_presence"_s, presence); 19 | using namespace std::chrono_literals; 20 | // Add time for the request roundtrip on top of the server-side timeout specified above 21 | JobBackoffStrategy backoffStrategy { { defaultTimeout + 10s }, { 2s, 5s, 15s }, std::nullopt }; 22 | if (timeout >= 0) { 23 | query.addQueryItem(u"timeout"_s, QString::number(timeout)); 24 | backoffStrategy.jobTimeouts = { std::chrono::seconds(timeout / 1000 + 10) }; 25 | } else 26 | backoffStrategy.jobTimeouts = { std::chrono::weeks { 3 } }; // effectively disable the timeout 27 | setBackoffStrategy(std::move(backoffStrategy)); 28 | addParam(query, u"since"_s, since); 29 | setRequestQuery(query); 30 | } 31 | 32 | SyncJob::SyncJob(const QString& since, const Filter& filter, int timeout, 33 | const QString& presence) 34 | : SyncJob(since, 35 | QString::fromUtf8(QJsonDocument(toJson(filter)).toJson(QJsonDocument::Compact)), 36 | timeout, presence) 37 | {} 38 | 39 | BaseJob::Status SyncJob::prepareResult() 40 | { 41 | d.parseJson(jsonData()); 42 | if (Q_LIKELY(d.unresolvedRooms().isEmpty())) 43 | return Success; 44 | 45 | Q_ASSERT(d.unresolvedRooms().isEmpty()); 46 | qCCritical(MAIN).noquote() << "Rooms missing after processing sync " 47 | "response, possibly a bug in SyncData: " 48 | << d.unresolvedRooms().join(u','); 49 | return IncorrectResponse; 50 | } 51 | -------------------------------------------------------------------------------- /autotests/setup-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 1.95.1 is the latest release that allows user registration over HTTPS with self-signed certs 4 | # The setup has to be changed to support newer versions 5 | SYNAPSE_REF=${SYNAPSE_REF:-v1.95.1} 6 | SYNAPSE_IMAGE="matrixdotorg/synapse:$SYNAPSE_REF" 7 | SCRIPT_DIR="$PWD/autotests" 8 | 9 | if [ ! -f $SCRIPT_DIR/adjust-config.sh ]; then 10 | echo "This script should be run from the directory above autotests/" 11 | echo "(i.e. autotests/setup-tests.sh). Other ways of invocation are not supported." 12 | return 1 13 | fi 14 | 15 | DATA_PATH="$SCRIPT_DIR/synapse-data" 16 | if [ ! -d "$DATA_PATH" ]; then 17 | mkdir -p -- "$DATA_PATH" 18 | chmod 0777 -- "$DATA_PATH" 19 | else 20 | rm -rf $DATA_PATH/* 21 | fi 22 | 23 | rm -rf ~/.local/share/testolmaccount 24 | 25 | echo "Generating the configuration" 26 | docker run -v $DATA_PATH:/data:z --rm \ 27 | -e SYNAPSE_SERVER_NAME=localhost -e SYNAPSE_REPORT_STATS=no $SYNAPSE_IMAGE generate 28 | 29 | echo "Adjusting the configuration and preparing the data directory" 30 | (cd "$DATA_PATH" && . "$SCRIPT_DIR/adjust-config.sh") 31 | 32 | echo "Starting Synapse" 33 | docker run -d \ 34 | --name synapse \ 35 | -p 1234:8008 \ 36 | -p 8448:8008 \ 37 | -p 8008:8008 \ 38 | -v $DATA_PATH:/data:z $SYNAPSE_IMAGE 39 | 40 | if [ -z "$KEEP_SYNAPSE" ]; then 41 | TRAP_CMD="docker rm -f synapse 2>&1 >/dev/null" 42 | if [ -z "$KEEP_DATA_PATH" ]; then 43 | TRAP_CMD="$TRAP_CMD; rm -rf $DATA_PATH" 44 | fi 45 | trap "$TRAP_CMD; trap - EXIT" EXIT 46 | fi 47 | 48 | printf "Waiting for synapse to start " 49 | until curl -s -f -k https://localhost:1234/_matrix/client/versions; do printf "."; sleep 2; done 50 | echo 51 | 52 | if docker exec synapse /bin/sh /data/register-users.sh; then 53 | echo "You can run ctest with a full set of tests now!" 54 | echo "If you don't find the synapse container running, make sure to source" 55 | echo "this script instead of running it in a subshell (the container will be" 56 | echo "deleted when you exit the shell then), or run it with KEEP_SYNAPSE" 57 | echo "environment variable set to any value" 58 | fi 59 | -------------------------------------------------------------------------------- /Quotient/csapi/room_state.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Send a state event to the given room. 10 | //! 11 | //! State events can be sent using this endpoint. These events will be 12 | //! overwritten if ``, `` and `` all 13 | //! match. 14 | //! 15 | //! Requests to this endpoint **cannot use transaction IDs** 16 | //! like other `PUT` paths because they cannot be differentiated from the 17 | //! `state_key`. Furthermore, `POST` is unsupported on state paths. 18 | //! 19 | //! The body of the request should be the content object of the event; the 20 | //! fields in this object will vary depending on the type of event. See 21 | //! [Room Events](/client-server-api/#room-events) for the `m.` event specification. 22 | //! 23 | //! If the event type being sent is `m.room.canonical_alias` servers 24 | //! SHOULD ensure that any new aliases being listed in the event are valid 25 | //! per their grammar/syntax and that they point to the room ID where the 26 | //! state event is to be sent. Servers do not validate aliases which are 27 | //! being removed or are already present in the state event. 28 | class QUOTIENT_API SetRoomStateWithKeyJob : public BaseJob { 29 | public: 30 | //! \param roomId 31 | //! The room to set the state in 32 | //! 33 | //! \param eventType 34 | //! The type of event to send. 35 | //! 36 | //! \param stateKey 37 | //! The state_key for the state to send. Defaults to the empty string. When 38 | //! an empty string, the trailing slash on this endpoint is optional. 39 | //! 40 | explicit SetRoomStateWithKeyJob(const QString& roomId, const QString& eventType, 41 | const QString& stateKey, const QJsonObject& content = {}); 42 | 43 | // Result properties 44 | 45 | //! A unique identifier for the event. 46 | QString eventId() const { return loadFromJson("event_id"_L1); } 47 | }; 48 | 49 | inline auto collectResponse(const SetRoomStateWithKeyJob* job) { return job->eventId(); } 50 | 51 | } // namespace Quotient 52 | -------------------------------------------------------------------------------- /Quotient/csapi/logout.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Invalidates a user access token 10 | //! 11 | //! Invalidates an existing access token, so that it can no longer be used for 12 | //! authorization. The device associated with the access token is also deleted. 13 | //! [Device keys](/client-server-api/#device-keys) for the device are deleted alongside the device. 14 | class QUOTIENT_API LogoutJob : public BaseJob { 15 | public: 16 | explicit LogoutJob(); 17 | 18 | //! \brief Construct a URL without creating a full-fledged job object 19 | //! 20 | //! This function can be used when a URL for LogoutJob 21 | //! is necessary but the job itself isn't. 22 | static QUrl makeRequestUrl(const HomeserverData& hsData); 23 | }; 24 | 25 | //! \brief Invalidates all access tokens for a user 26 | //! 27 | //! Invalidates all access tokens for a user, so that they can no longer be used for 28 | //! authorization. This includes the access token that made this request. All devices 29 | //! for the user are also deleted. [Device keys](/client-server-api/#device-keys) for the device are 30 | //! deleted alongside the device. 31 | //! 32 | //! This endpoint does not use the [User-Interactive Authentication 33 | //! API](/client-server-api/#user-interactive-authentication-api) because User-Interactive 34 | //! Authentication is designed to protect against attacks where the someone gets hold of a single 35 | //! access token then takes over the account. This endpoint invalidates all access tokens for the 36 | //! user, including the token used in the request, and therefore the attacker is unable to take over 37 | //! the account in this way. 38 | class QUOTIENT_API LogoutAllJob : public BaseJob { 39 | public: 40 | explicit LogoutAllJob(); 41 | 42 | //! \brief Construct a URL without creating a full-fledged job object 43 | //! 44 | //! This function can be used when a URL for LogoutAllJob 45 | //! is necessary but the job itself isn't. 46 | static QUrl makeRequestUrl(const HomeserverData& hsData); 47 | }; 48 | 49 | } // namespace Quotient 50 | -------------------------------------------------------------------------------- /Quotient/e2ee/qolmoutboundsession.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Carl Schwan 2 | // 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | struct OlmOutboundGroupSession; 10 | 11 | namespace Quotient { 12 | 13 | //! An out-bound group session is responsible for encrypting outgoing 14 | //! communication in a Megolm session. 15 | class QUOTIENT_API QOlmOutboundGroupSession 16 | { 17 | public: 18 | QOlmOutboundGroupSession(); 19 | 20 | //! Serialises a `QOlmOutboundGroupSession` to encrypted Base64. 21 | QByteArray pickle(const PicklingKey &key) const; 22 | //! Deserialises from encrypted Base64 that was previously obtained by 23 | //! pickling a `QOlmOutboundGroupSession`. 24 | static QOlmExpected unpickle(QByteArray&& pickled, const PicklingKey& key); 25 | 26 | //! Encrypts a plaintext message using the session. 27 | QByteArray encrypt(const QByteArray& plaintext) const; 28 | 29 | //! Get the current message index for this session. 30 | //! 31 | //! Each message is sent with an increasing index; this returns the 32 | //! index for the next message. 33 | uint32_t sessionMessageIndex() const; 34 | 35 | //! Get a base64-encoded identifier for this session. 36 | QByteArray sessionId() const; 37 | 38 | //! Get the base64-encoded current ratchet key for this session. 39 | //! 40 | //! Each message is sent with a different ratchet key. This function returns the 41 | //! ratchet key that will be used for the next message. 42 | QByteArray sessionKey() const; 43 | 44 | int messageCount() const; 45 | void setMessageCount(int messageCount); 46 | 47 | QDateTime creationTime() const; 48 | void setCreationTime(const QDateTime& creationTime); 49 | 50 | OlmErrorCode lastErrorCode() const; 51 | const char* lastError() const; 52 | 53 | private: 54 | CStructPtr m_groupSession; 55 | int m_messageCount = 0; 56 | QDateTime m_creationTime = QDateTime::currentDateTime(); 57 | OlmOutboundGroupSession* olmData = m_groupSession.get(); 58 | }; 59 | 60 | } // namespace Quotient 61 | -------------------------------------------------------------------------------- /autotests/testgroupsession.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Carl Schwan 2 | // 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #include "testgroupsession.h" 6 | #include 7 | #include 8 | 9 | using namespace Quotient; 10 | 11 | void TestGroupSession::groupSessionPicklingValid() 12 | { 13 | QOlmOutboundGroupSession ogs{}; 14 | const auto ogsId = ogs.sessionId(); 15 | QVERIFY(QByteArray::fromBase64(ogsId).size() > 0); 16 | QCOMPARE(0, ogs.sessionMessageIndex()); 17 | 18 | auto&& ogsPickled = ogs.pickle(PicklingKey::mock()); 19 | auto ogs2 = QOlmOutboundGroupSession::unpickle(std::move(ogsPickled), 20 | PicklingKey::mock()) 21 | .value(); 22 | QCOMPARE(ogsId, ogs2.sessionId()); 23 | 24 | auto igs = QOlmInboundGroupSession::create(ogs.sessionKey()); 25 | QVERIFY(igs.has_value()); 26 | const auto igsId = igs->sessionId(); 27 | // ID is valid base64? 28 | QVERIFY(QByteArray::fromBase64(igsId).size() > 0); 29 | 30 | //// no messages have been sent yet 31 | QCOMPARE(0, igs->firstKnownIndex()); 32 | 33 | auto igsPickled = igs->pickle(PicklingKey::mock()); 34 | igs = QOlmInboundGroupSession::unpickle(std::move(igsPickled), 35 | PicklingKey::mock()).value(); 36 | QVERIFY(igs.has_value()); 37 | QCOMPARE(igsId, igs->sessionId()); 38 | } 39 | 40 | void TestGroupSession::groupSessionCryptoValid() 41 | { 42 | QOlmOutboundGroupSession ogs{}; 43 | auto igs = QOlmInboundGroupSession::create(ogs.sessionKey()); 44 | QVERIFY(igs.has_value()); 45 | QCOMPARE(ogs.sessionId(), igs->sessionId()); 46 | 47 | const auto plainText = "Hello world!"; 48 | const auto ciphertext = ogs.encrypt(plainText); 49 | // ciphertext valid base64? 50 | QVERIFY(QByteArray::fromBase64(ciphertext).size() > 0); 51 | 52 | const auto decryptionResult = igs->decrypt(ciphertext).value(); 53 | 54 | //// correct plaintext? 55 | QCOMPARE(plainText, decryptionResult.first); 56 | 57 | QCOMPARE(0, decryptionResult.second); 58 | } 59 | QTEST_GUILESS_MAIN(TestGroupSession) 60 | -------------------------------------------------------------------------------- /Quotient/csapi/knocking.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Knock on a room, requesting permission to join. 10 | //! 11 | //! *Note that this API takes either a room ID or alias, unlike other membership APIs.* 12 | //! 13 | //! This API "knocks" on the room to ask for permission to join, if the user 14 | //! is allowed to knock on the room. Acceptance of the knock happens out of 15 | //! band from this API, meaning that the client will have to watch for updates 16 | //! regarding the acceptance/rejection of the knock. 17 | //! 18 | //! If the room history settings allow, the user will still be able to see 19 | //! history of the room while being in the "knock" state. The user will have 20 | //! to accept the invitation to join the room (acceptance of knock) to see 21 | //! messages reliably. See the `/join` endpoints for more information about 22 | //! history visibility to the user. 23 | //! 24 | //! The knock will appear as an entry in the response of the 25 | //! [`/sync`](/client-server-api/#get_matrixclientv3sync) API. 26 | class QUOTIENT_API KnockRoomJob : public BaseJob { 27 | public: 28 | //! \param roomIdOrAlias 29 | //! The room identifier or alias to knock upon. 30 | //! 31 | //! \param serverName 32 | //! The servers to attempt to knock on the room through. One of the servers 33 | //! must be participating in the room. 34 | //! 35 | //! \param via 36 | //! The servers to attempt to knock on the room through. One of the servers 37 | //! must be participating in the room. 38 | //! 39 | //! \param reason 40 | //! Optional reason to be included as the `reason` on the subsequent 41 | //! membership event. 42 | explicit KnockRoomJob(const QString& roomIdOrAlias, const QStringList& serverName = {}, 43 | const QStringList& via = {}, const QString& reason = {}); 44 | 45 | // Result properties 46 | 47 | //! The knocked room ID. 48 | QString roomId() const { return loadFromJson("room_id"_L1); } 49 | }; 50 | 51 | inline auto collectResponse(const KnockRoomJob* job) { return job->roomId(); } 52 | 53 | } // namespace Quotient 54 | -------------------------------------------------------------------------------- /Quotient/e2ee/qolmsession.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Alexey Andreyev 2 | // 3 | // SPDX-License-Identifier: LGPL-2.1-or-later 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | struct OlmSession; 11 | 12 | namespace Quotient { 13 | 14 | class QOlmAccount; 15 | 16 | //! Either an outbound or inbound session for secure communication. 17 | class QUOTIENT_API QOlmSession 18 | { 19 | public: 20 | //! Serialises an `QOlmSession` to encrypted Base64. 21 | QByteArray pickle(const PicklingKey& key) const; 22 | 23 | //! Deserialises from encrypted Base64 previously made with pickle() 24 | static QOlmExpected unpickle(QByteArray&& pickled, 25 | const PicklingKey& key); 26 | 27 | //! Encrypts a plaintext message using the session. 28 | QOlmMessage encrypt(const QByteArray& plaintext) const; 29 | 30 | //! Decrypts a message using this session. Decoding is lossy, meaning if 31 | //! the decrypted plaintext contains invalid UTF-8 symbols, they will 32 | //! be returned as `U+FFFD`. 33 | QOlmExpected decrypt(const QOlmMessage &message) const; 34 | 35 | //! Get a base64-encoded identifier for this session. 36 | QByteArray sessionId() const; 37 | 38 | //! Checker for any received messages for this session. 39 | bool hasReceivedMessage() const; 40 | 41 | //! Checks if the 'prekey' message is for this in-bound session. 42 | bool matchesInboundSession(const QOlmMessage& preKeyMessage) const; 43 | 44 | //! Checks if the 'prekey' message is for this in-bound session. 45 | bool matchesInboundSessionFrom(QByteArray theirIdentityKey, 46 | const QOlmMessage& preKeyMessage) const; 47 | 48 | friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs) 49 | { 50 | return lhs.sessionId() < rhs.sessionId(); 51 | } 52 | 53 | OlmErrorCode lastErrorCode() const; 54 | const char* lastError() const; 55 | 56 | private: 57 | QOlmSession(); 58 | CStructPtr olmDataHolder; 59 | OlmSession* olmData = olmDataHolder.get(); 60 | 61 | friend class QOlmAccount; 62 | }; 63 | } //namespace Quotient 64 | -------------------------------------------------------------------------------- /Quotient/csapi/leaving.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace Quotient { 8 | 9 | //! \brief Stop the requesting user participating in a particular room. 10 | //! 11 | //! This API stops a user participating in a particular room. 12 | //! 13 | //! If the user was already in the room, they will no longer be able to see 14 | //! new events in the room. If the room requires an invite to join, they 15 | //! will need to be re-invited before they can re-join. 16 | //! 17 | //! If the user was invited to the room, but had not joined, this call 18 | //! serves to reject the invite. 19 | //! 20 | //! The user will still be allowed to retrieve history from the room which 21 | //! they were previously allowed to see. 22 | class QUOTIENT_API LeaveRoomJob : public BaseJob { 23 | public: 24 | //! \param roomId 25 | //! The room identifier to leave. 26 | //! 27 | //! \param reason 28 | //! Optional reason to be included as the `reason` on the subsequent 29 | //! membership event. 30 | explicit LeaveRoomJob(const QString& roomId, const QString& reason = {}); 31 | }; 32 | 33 | //! \brief Stop the requesting user remembering about a particular room. 34 | //! 35 | //! This API stops a user remembering about a particular room. 36 | //! 37 | //! In general, history is a first class citizen in Matrix. After this API 38 | //! is called, however, a user will no longer be able to retrieve history 39 | //! for this room. If all users on a homeserver forget a room, the room is 40 | //! eligible for deletion from that homeserver. 41 | //! 42 | //! If the user is currently joined to the room, they must leave the room 43 | //! before calling this API. 44 | class QUOTIENT_API ForgetRoomJob : public BaseJob { 45 | public: 46 | //! \param roomId 47 | //! The room identifier to forget. 48 | explicit ForgetRoomJob(const QString& roomId); 49 | 50 | //! \brief Construct a URL without creating a full-fledged job object 51 | //! 52 | //! This function can be used when a URL for ForgetRoomJob 53 | //! is necessary but the job itself isn't. 54 | static QUrl makeRequestUrl(const HomeserverData& hsData, const QString& roomId); 55 | }; 56 | 57 | } // namespace Quotient 58 | -------------------------------------------------------------------------------- /Quotient/csapi/filter.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace Quotient { 10 | 11 | //! \brief Upload a new filter. 12 | //! 13 | //! Uploads a new filter definition to the homeserver. 14 | //! Returns a filter ID that may be used in future requests to 15 | //! restrict which events are returned to the client. 16 | class QUOTIENT_API DefineFilterJob : public BaseJob { 17 | public: 18 | //! \param userId 19 | //! The id of the user uploading the filter. The access token must be authorized to make 20 | //! requests for this user id. 21 | //! 22 | //! \param filter 23 | //! The filter to upload. 24 | explicit DefineFilterJob(const QString& userId, const Filter& filter); 25 | 26 | // Result properties 27 | 28 | //! The ID of the filter that was created. Cannot start 29 | //! with a `{` as this character is used to determine 30 | //! if the filter provided is inline JSON or a previously 31 | //! declared filter by homeservers on some APIs. 32 | QString filterId() const { return loadFromJson("filter_id"_L1); } 33 | }; 34 | 35 | inline auto collectResponse(const DefineFilterJob* job) { return job->filterId(); } 36 | 37 | //! \brief Download a filter 38 | class QUOTIENT_API GetFilterJob : public BaseJob { 39 | public: 40 | //! \param userId 41 | //! The user ID to download a filter for. 42 | //! 43 | //! \param filterId 44 | //! The filter ID to download. 45 | explicit GetFilterJob(const QString& userId, const QString& filterId); 46 | 47 | //! \brief Construct a URL without creating a full-fledged job object 48 | //! 49 | //! This function can be used when a URL for GetFilterJob 50 | //! is necessary but the job itself isn't. 51 | static QUrl makeRequestUrl(const HomeserverData& hsData, const QString& userId, 52 | const QString& filterId); 53 | 54 | // Result properties 55 | 56 | //! The filter definition. 57 | Filter filter() const { return fromJson(jsonData()); } 58 | }; 59 | 60 | inline auto collectResponse(const GetFilterJob* job) { return job->filter(); } 61 | 62 | } // namespace Quotient 63 | --------------------------------------------------------------------------------