diff --git a/CMakeLists.txt b/CMakeLists.txt index 81ad381b9..013bc0c3d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ endif() set(API_VERSION "0.9") project(Quotient VERSION "${API_VERSION}.0" LANGUAGES CXX) -set(PRE_STAGE "beta") +set(PRE_STAGE "rc") set(FULL_VERSION ${PROJECT_VERSION}~${PRE_STAGE}) message(STATUS) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b8225c396..eeb00d5af 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,8 +126,8 @@ to make the review easier. ### C++ feature set -As of Quotient 0.9, the C++ standard for newly written code is C++20, save for a few exceptions -that the currently supported toolchains still don't have, most notably: +As of Quotient 0.9, the C++ standard for new code is C++20, except a few features that currently +supported toolchains still don't have, most notably: - template parameteres for type aliases and aggregates still cannot be deduced yet, you have to explicitly specify those; - modules support, while formally there, is missing standard library header units; sticking with @@ -151,22 +151,20 @@ use `// clang-format off` and `// clang-format on` to protect them. Most fundamental things from `.clang-format`: * We (mostly) use Webkit style: 4-space indents, no tabs, no trailing spaces, no last empty lines. - If you spot code that doesn't follow this, fix it on the spot, thank you. + If you see code that doesn't follow this, fix it on the spot, thank you. * Prefer keeping lines within 100 characters. Slight overflows are ok if that helps readability. Ideally, just use `clang-format` to format lines. ### API conventions -All non-inline symbols (functions, classes/structs, even namespace-level static -variables) that are intended for use in client code must be declared with -`QUOTIENT_API` macro (the macro itself is defined in the dedicated -`quotient_export.h` file). This is concerned with symbols visibility in -dynamic/shared libraries: the macro marks these symbols for exporting in -the library symbol table. If you forget to use this macro where needed you will -get linkage errors if you're lucky, obscure runtime errors otherwise (such as -split-brained singleton instances). You only need to use the macro on the -namespace level; inner symbols (member functions, e.g.) are exported if -their class is exported. +All non-inline symbols (functions, classes/structs, even namespace-level static variables) that are +intended for use in client code must be declared with `QUOTIENT_API` macro (the macro itself is +defined in the dedicated `quotient_export.h` file) in order to export them in the library symbol +table when it is compiled as dynamic/shared. If you forget to use this macro and a client +application uses the symbol there will be linkage errors in a lucky case and obscure runtime errors +(such as split-brained singleton instances) otherwise. You only need to use the macro +on the namespace level; nested symbols (member functions, e.g.) are exported if their class is +exported. Some header files of the library are not intended to be (directly) included by clients - these header files have names ending with `_p.h` (e.g. @@ -183,17 +181,14 @@ so tread carefully). ### Generated C++ code for CS API -The code in `Quotient/csapi`, `Quotient/identity` and -`Quotient/application-service`, although stored in Git, is actually generated -from the official Matrix Client-Server API definition files. Make sure to read -[CODE_GENERATION.md](./CODE_GENERATION.md) before trying to change anything -there. +The code in `Quotient/csapi`, `Quotient/identity` and `Quotient/application-service`, although +stored in Git, is actually generated from the official Matrix Client-Server API definition files. +Make sure to read [CODE_GENERATION.md](./CODE_GENERATION.md) before trying to change anything there. ## Documentation changes -Most of the documentation is in Markdown format. All Markdown files use the -`.md` filename extension. +Most of the documentation is in Markdown format; the file names use the `.md` extension. Where reasonable, limit yourself to Markdown that will be accepted by different markdown processors (e.g., what is specified by CommonMark or the original @@ -210,12 +205,10 @@ by GitHub when it renders issue or pull comments; in those cases unfortunately this other algorithm is *also* called GitHub-flavoured markdown. (Yes, it would be better if there were different names for different things.) -In your markdown, please don't use tab characters and avoid "bare" URLs. -In a hyperlink, the link text and URL should be on the same line. -Both in C/C++ code comments and Markdown documents, try to keep your lines -within the 100-character limit _except hyperlinks_ (wrapping breaks them). Some -historical text may not follow that rule - feel free to reformat those parts -when you edit them. +Don't use tab characters and avoid "bare" URLs. In a hyperlink, the link text and URL should be +on the same line. Both in C/C++ code comments and Markdown documents, try to keep your lines +within the 100-character limit _except hyperlinks_ (wrapping breaks them). Some historical text +may not follow that rule - feel free to reformat those parts when you edit them. Do not use trailing two spaces for line breaks, since these cannot be seen and may be silently removed by some tools. If, for whatever reason, a blank line @@ -232,11 +225,11 @@ Further sections are for those who's going to actively hack on the library code. ### More on code style and formatting -* Do not use `struct` when you have protected or private members; only use it - to define plain-old-data structures, with maybe just a function or two among - public members but no substantial behaviour. If you need access control or - specific logic tightly coupled to the data structure, make it a `class` - instead and consider if you still want to keep its member variables public. +* Prefer `class` over `struct` when you have protected or private members in the type; `struct` is + meant for plain-old-data structures, with maybe just a function or two among public members + but no substantial behaviour. If you need access control or specific logic tightly coupled to + the data structure, make it a `class` and consider if you still want to keep its member variables + public. * For newly created classes, keep to [the rule of 3/5/0](http://en.cppreference.com/w/cpp/language/rule_of_three). @@ -250,57 +243,44 @@ Further sections are for those who's going to actively hack on the library code. Classes without a default constructor are a problem too. Examples of that are `SyncRoomData` and `EventsArray<>`. Again, you can use STL containers for structures having those but consider the implications. - * So, the implications. Because QML doesn't know about most of STL containers and cannot pull data - out of them, you're only free to use STL containers in backend code (in the simplest case, - within one .cpp file). The API exposing these containers can only be used from C++ code, with - `std::vector` being a notable exception that QML knows about (but you still can't read - uncopyable vectors such as `EventsArray<>`, as the previous bullet already said). For these - cases you have to provide external means to iterate through the container and consume data - from it; exposing a Qt item model is most natural to Qt code. If you don't provide such other - means, expect questions at your pull request. - * Notwithstanding the above (you're not going to use smart pointers with QML - anyway), prefer `std::unique_ptr<>` over `QScopedPointer<>` as it gives - stronger guarantees; also, some features of `QScopedPointer` are deprecated - in Qt 6. - -* Always use `QVector` instead of `QList` unless Qt's own API uses it - see the - [great article by Marc Mutz on Qt containers](https://marcmutz.wordpress.com/effective-qt/containers/) - for details. With Qt 6, these two become the same type matching what used - to be `QVector` in Qt 5. - - (Note: unfortunately, `QVector` is a type alias in Qt 6 and that breaks - templated code because type deduction doesn't work with aliases. This breakage - will go away as compilers adopt C++23 sufficiently but it may take a - couple more years, as of this writing; in the meantime, the fix boils down - to specifying the template parameter of `QVector` explicitly.) + * So, the implications. QML doesn't know about most of STL containers and in any case requires + data types passed to it (let alone from it) to be default-constructible and copyable. For that + reason, STL containers can only be used in backend code that is not meant for consumption by + QML. The only STL container mapped to QML as of Qt 6.6 is `std::vector`; and even then you + still can't expose uncopyable vectors such as `EventsArray<>` to QML. For these cases you have + to provide external means to iterate through the container and consume data from it; exposing + a Qt item model is most natural to Qt code. If you don't provide such other means, expect + questions at your pull request. +* Prefer `std::unique_ptr<>` over `QScopedPointer<>`; `std::as_const()` to `qAsConst()`; + `std::swap()` to `qSwap()` etc.; basically, do not use compat facilities provided by Qt if the + standard library provides an _equivalent_ facility. * When you write logs within the library always use logging categories defined in `logging_categories_p.h` instead of plain `qDebug()`, to avoid a log line being assigned the default category. `qCDebug(CATEGORY)` is the preferred form; `qDebug(CATEGORY)` (without `C`) is accepted as well. Do not add new logging categories without necessity; if you do, make sure to add the new category to `logging_categories_p.h`, so that there's a central reference for all - of them (mentioned in README.md, by the way). + of them (also listed in README.md, by the way). ### Comments -Whenever you add a new call to the library API that you expect to be used -from client code, make sure to supply a proper doc-comment along with the call. -Quotient uses the Doxygen C++-styled doc-comments (`//!`, `\brief`); some legacy -code may use Javadoc (`/** ... */`, `@brief`) or C-styled Doxygen (`/*! ... */`) -but that is not encouraged any more. Some parts are not documented at all; -adding doc-comments to them and/or converting the existing ones to the assumed -style is highly encouraged; it's also a nice and easy first-time contribution. +Whenever you add or change the library API, make sure to supply a proper doc-comment along with +the call. Quotient uses the Doxygen C++-styled doc-comments (`//!`, `\brief`); some legacy code +may use Javadoc (`/** ... */`, `@brief`) or C-styled Doxygen (`/*! ... */`) but that is +not encouraged any more. Some parts are not documented at all; adding doc-comments to them +and/or converting the existing ones to the assumed style is highly encouraged; it's also a nice +and easy first-time contribution. -Use `\brief` for the summary, and follow with details after -an empty doc-comment line, using `\param`, `\return` etc. as necessary. +Use `\brief` for the summary, and follow with details after an empty doc-comment line, using +`\param`, `\return` etc. as necessary. Adding `\since` to indicate the first version when a +symbol is introduce is nice, too. When commenting in-code: -* Don't restate what's happening in the code unless it's not really obvious. - We assume the readers to have some command of C++ and Qt. If your code is - not obvious, consider making it clearer itself before commenting. +* Don't restate what's happening in the code. We assume the readers to have some command of C++ + and Qt. If your code is not obvious, consider making it clearer itself before commenting. * That said, both C++ and Qt have their arcane/novel features and dark corners, and education of code readers is a great thing. Use your experience to figure what might be not that well-known, - and comment such cases: leave references to web pages, Quotient wiki etc. Do not comment `std::` + and comment such cases: add references to web pages, Quotient wiki etc. Do not comment `std::` calls just because they are less known - readers are expected to know about cppreference.com and look it up. * More important than everything above - make sure to document not so much "what" but more "why" @@ -316,17 +296,19 @@ have just started adding those to the new code (you guessed it; adding more tests to the old code is very welcome and also is a good exercise to get to know the library). -On top of that, libQuotient comes with a command-line end-to-end test suite -called Quotest. Any significant addition to the library API should be -accompanied by a respective test in `autotests/` and/or in Quotest. +On top of autotests, libQuotient comes with a command-line end-to-end test suite called Quotest. +Any significant addition to the library API should be accompanied by a respective test +in `autotests/` and/or in Quotest. To add a test to autotests: + - In a new `.cpp` file in `autotests/` (you don't need a header file), define a test class derived from `QObject` and write tests as member functions in its `private slots:` section. See other autotests to get an idea of what it should look like. - Add a `quotient_add_test` macro call with your test to `autotests/CMakeLists.txt` To add a test to Quotest: + - In `quotest.cpp`, add a new test to the `TestSuite` class. Similar to Qt Test, each test in Quotest is a private slot; unlike Qt Test, you should use special macros, `TEST_DECL()` and `TEST_IMPL()`, to declare and define @@ -348,8 +330,7 @@ from it). ### Security and privacy -Pay attention to security, and work *with*, not against, the usual security -hardening practices. +Pay attention to security, and work *with*, not against, the good security practices. `char *` and similar unchecked C-style read/write arrays are forbidden - use Qt containers (`QString`/`QLatin1String` for strings, in particular) or `std::array<>`/`std::span<>` instead @@ -429,7 +410,7 @@ If you want the IDE to be _really_ picky about your code you can use the following line for the Clang analyzer code model to enable most compiler warnings while keeping the number of false positives at bay (that does not include `clang-tidy`/`clazy` warnings - see the next section on those): -`-Weverything -Werror=return-type -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-c++20-compat -Wno-unused-macros -Wno-newline-eof -Wno-exit-time-destructors -Wno-global-constructors -Wno-gnu-zero-variadic-macro-arguments -Wno-documentation -Wno-missing-prototypes -Wno-shadow-field-in-constructor -Wno-padded -Wno-weak-vtables -Wno-unknown-attributes -Wno-comma -Wno-shadow-uncaptured-local -Wno-switch-enum -Wno-pragma-once-outside-header -Wno-range-loop-bind-reference -Wno-unsafe-buffer-usage` +`-Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-c++20-compat -Wno-unused-macros -Wno-newline-eof -Wno-exit-time-destructors -Wno-global-constructors -Wno-gnu-zero-variadic-macro-arguments -Wno-documentation -Wno-missing-prototypes -Wno-shadow-field-in-constructor -Wno-padded -Wno-weak-vtables -Wno-unknown-attributes -Wno-comma -Wno-shadow-uncaptured-local -Wno-switch-enum -Wno-pragma-once-outside-header -Wno-range-loop-bind-reference -Wno-unsafe-buffer-usage` ### Static analysis tools diff --git a/Quotient/application-service/definitions/protocol.h b/Quotient/application-service/definitions/protocol.h index bd3368548..f521649aa 100644 --- a/Quotient/application-service/definitions/protocol.h +++ b/Quotient/application-service/definitions/protocol.h @@ -12,7 +12,7 @@ struct QUOTIENT_API FieldType { //! may apply additional validation or filtering. QString regexp; - //! An placeholder serving as a valid example of the field value. + //! A placeholder serving as a valid example of the field value. QString placeholder; }; @@ -79,9 +79,9 @@ struct QUOTIENT_API ThirdPartyProtocol { //! A content URI representing an icon for the third-party protocol. QString icon; - //! The type definitions for the fields defined in the `user_fields` and - //! `location_fields`. Each entry in those arrays MUST have an entry here. The - //! `string` key for this object is field name itself. + //! The type definitions for the fields defined in `user_fields` and + //! `location_fields`. Each entry in those arrays MUST have an entry here. + //! The `string` key for this object is the field name itself. //! //! May be an empty object if no fields are defined. QHash fieldTypes; diff --git a/Quotient/application-service/definitions/user.h b/Quotient/application-service/definitions/user.h index 968b225cd..4dbe183a9 100644 --- a/Quotient/application-service/definitions/user.h +++ b/Quotient/application-service/definitions/user.h @@ -7,7 +7,7 @@ namespace Quotient { struct QUOTIENT_API ThirdPartyUser { - //! A Matrix User ID represting a third-party user. + //! A Matrix User ID representing a third-party user. QString userid; //! The protocol ID that the third-party location is a part of. diff --git a/Quotient/connection.cpp b/Quotient/connection.cpp index 36a39b452..f205fadb8 100644 --- a/Quotient/connection.cpp +++ b/Quotient/connection.cpp @@ -660,16 +660,14 @@ JobHandle Connection::joinRoom(const QString& roomAlias, const QStr // Upon completion, ensure a room object is created in case it hasn't come with a sync yet. // If the room object is not there, provideRoom() will create it in Join state. Using // the continuation ensures that the room is provided before any client connections. - return callApi(roomAlias, serverNames).then([this](const QString& roomId) { - provideRoom(roomId); - }); + return callApi(roomAlias, serverNames, serverNames) + .then([this](const QString& roomId) { provideRoom(roomId); }); } QFuture Connection::joinAndGetRoom(const QString& roomAlias, const QStringList& serverNames) { - return callApi(roomAlias, serverNames).then([this](const QString& roomId) { - return provideRoom(roomId); - }); + return callApi(roomAlias, serverNames, serverNames) + .then([this](const QString& roomId) { return provideRoom(roomId); }); } LeaveRoomJob* Connection::leaveRoom(Room* room) diff --git a/Quotient/connectionencryptiondata_p.cpp b/Quotient/connectionencryptiondata_p.cpp index ebf8858f9..4e362eb40 100644 --- a/Quotient/connectionencryptiondata_p.cpp +++ b/Quotient/connectionencryptiondata_p.cpp @@ -131,7 +131,7 @@ void ConnectionEncryptionData::saveDevicesList() auto query = database.prepareQuery(u"DELETE FROM tracked_users"_s); database.execute(query); query.prepare(u"INSERT INTO tracked_users(matrixId) VALUES(:matrixId);"_s); - for (const auto& user : trackedUsers) { + for (const auto& user : std::as_const(trackedUsers)) { query.bindValue(u":matrixId"_s, user); database.execute(query); } @@ -139,7 +139,7 @@ void ConnectionEncryptionData::saveDevicesList() query.prepare(u"DELETE FROM outdated_users"_s); database.execute(query); query.prepare(u"INSERT INTO outdated_users(matrixId) VALUES(:matrixId);"_s); - for (const auto& user : outdatedUsers) { + for (const auto& user : std::as_const(outdatedUsers)) { query.bindValue(u":matrixId"_s, user); database.execute(query); } @@ -153,7 +153,7 @@ void ConnectionEncryptionData::saveDevicesList() database.prepareQuery(u"DELETE FROM tracked_devices WHERE matrixId=:matrixId;"_s); deleteQuery.bindValue(u":matrixId"_s, user); database.execute(deleteQuery); - for (const auto& device : devices) { + for (const auto& device : std::as_const(devices)) { const auto keys = device.keys.asKeyValueRange(); deleteQuery.prepare( u"DELETE FROM tracked_devices WHERE matrixId=:matrixId AND deviceId=:deviceId;"_s); @@ -161,14 +161,20 @@ void ConnectionEncryptionData::saveDevicesList() deleteQuery.bindValue(u":deviceId"_s, device.deviceId); database.execute(deleteQuery); + if (device.deviceId.isEmpty()) { + qCCritical(E2EE) << "Clearing an invalid tracked device record with empty deviceId"; + continue; + } const auto curveKeyIt = std::ranges::find_if(keys, [](const auto& p) { return p.first.startsWith("curve"_L1); }); - Q_ASSERT(curveKeyIt != keys.end()); const auto edKeyIt = std::ranges::find_if(keys, [](const auto& p) { return p.first.startsWith("ed"_L1); }); - Q_ASSERT(edKeyIt != keys.end()); + if (curveKeyIt == keys.end() || edKeyIt == keys.end()) { + qCCritical(E2EE) << "Clearing an invalid tracked device record due to keys missing"; + continue; + } query.bindValue(u":matrixId"_s, user); query.bindValue(u":deviceId"_s, device.deviceId); diff --git a/Quotient/csapi/authed-content-repo.h b/Quotient/csapi/authed-content-repo.h index bbeb02531..2c26aa63b 100644 --- a/Quotient/csapi/authed-content-repo.h +++ b/Quotient/csapi/authed-content-repo.h @@ -46,9 +46,41 @@ class QUOTIENT_API GetContentAuthedJob : public BaseJob { // Result properties //! The content type of the file that was previously uploaded. + //! + //! The server MUST return a `Content-Type` which is either exactly the same + //! as the original upload, or reasonably close. The bounds of "reasonable" + //! are: + //! + //! * Adding a charset to `text/*` content types. + //! * Detecting HTML and using `text/html` instead of `text/plain`. + //! * Using `application/octet-stream` when the server determines the + //! content type is obviously wrong. For example, an encrypted file being + //! claimed as `image/png`. + //! * Returning `application/octet-stream` when the media has an + //! unknown/unprovided `Content-Type`. For example, being uploaded before + //! the server tracked content types or when the remote server is + //! non-compliantly omitting the header entirely. + //! + //! Actions not in the spirit of the above are not considered "reasonable". QString contentType() const { return QString::fromUtf8(reply()->rawHeader("Content-Type")); } - //! The name of the file that was previously uploaded, if set. + //! The + //! [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + //! of the returned content. MUST be one of `inline` or `attachment`, + //! and SHOULD contain a file name. + //! + //! If the `Content-Type` is allowed in the [restrictions for serving + //! inline content](/client-server-api/#serving-inline-content), + //! servers SHOULD use `inline`, otherwise they SHOULD use + //! `attachment`. + //! + //! If the upload was made with a `filename`, this header MUST + //! contain the same `filename`. Otherwise, `filename` is excluded + //! from the header. If the media being downloaded is remote, the + //! remote server's `filename` in the `Content-Disposition` header + //! is used as the `filename` instead. When the header is not + //! supplied, or does not supply a `filename`, the local download + //! response does not include a `filename`. QString contentDisposition() const { return QString::fromUtf8(reply()->rawHeader("Content-Disposition")); @@ -103,10 +135,33 @@ class QUOTIENT_API GetContentOverrideNameAuthedJob : public BaseJob { // Result properties //! The content type of the file that was previously uploaded. + //! + //! The server MUST return a `Content-Type` which is either exactly the same + //! as the original upload, or reasonably close. The bounds of "reasonable" + //! are: + //! + //! * Adding a charset to `text/*` content types. + //! * Detecting HTML and using `text/html` instead of `text/plain`. + //! * Using `application/octet-stream` when the server determines the + //! content type is obviously wrong. For example, an encrypted file being + //! claimed as `image/png`. + //! * Returning `application/octet-stream` when the media has an + //! unknown/unprovided `Content-Type`. For example, being uploaded before + //! the server tracked content types or when the remote server is + //! non-compliantly omitting the header entirely. + //! + //! Actions not in the spirit of the above are not considered "reasonable". QString contentType() const { return QString::fromUtf8(reply()->rawHeader("Content-Type")); } - //! The `fileName` requested or the name of the file that was previously - //! uploaded, if set. + //! The + //! [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + //! of the returned content. MUST be one of `inline` or `attachment`, + //! and MUST contain the file name requested in the path. + //! + //! If the `Content-Type` is allowed in the [restrictions for serving + //! inline content](/client-server-api/#serving-inline-content), + //! servers SHOULD use `inline`, otherwise they SHOULD use + //! `attachment`. QString contentDisposition() const { return QString::fromUtf8(reply()->rawHeader("Content-Disposition")); @@ -187,6 +242,19 @@ class QUOTIENT_API GetContentThumbnailAuthedJob : public BaseJob { // Result properties + //! The + //! [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + //! of the returned content. MUST be `inline`, and SHOULD contain a file name (e.g. + //! `thumbnail.png`). + //! + //! Servers should note the [Content-Type restrictions for serving inline + //! content](/client-server-api/#serving-inline-content), as these limitations imply which + //! formats should be used for thumbnail generation. + QString contentDisposition() const + { + return QString::fromUtf8(reply()->rawHeader("Content-Disposition")); + } + //! The content type of the thumbnail. QString contentType() const { return QString::fromUtf8(reply()->rawHeader("Content-Type")); } diff --git a/Quotient/csapi/capabilities.h b/Quotient/csapi/capabilities.h index ad391dbf4..42c4eb66f 100644 --- a/Quotient/csapi/capabilities.h +++ b/Quotient/csapi/capabilities.h @@ -6,6 +6,23 @@ namespace Quotient { +struct QUOTIENT_API BooleanCapability { + //! True if the user can perform the action, false otherwise. + bool enabled; +}; + +template <> +struct JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const BooleanCapability& pod) + { + addParam<>(jo, "enabled"_L1, pod.enabled); + } + static void fillFrom(const QJsonObject& jo, BooleanCapability& pod) + { + fillFromJson(jo.value("enabled"_L1), pod.enabled); + } +}; + //! \brief Gets information about the server's capabilities. //! //! Gets information about the server's supported feature set @@ -14,12 +31,6 @@ class QUOTIENT_API GetCapabilitiesJob : public BaseJob { public: // Inner data structures - //! Capability to indicate if the user can change their password. - struct QUOTIENT_API ChangePasswordCapability { - //! True if the user can change their password, false otherwise. - bool enabled; - }; - //! The room versions the server supports. struct QUOTIENT_API RoomVersionsCapability { //! The default room version the server is using for new rooms. @@ -33,12 +44,28 @@ class QUOTIENT_API GetCapabilitiesJob : public BaseJob { //! Java package naming convention. struct QUOTIENT_API Capabilities { //! Capability to indicate if the user can change their password. - std::optional changePassword{}; + std::optional changePassword{}; //! The room versions the server supports. std::optional roomVersions{}; - QHash additionalProperties{}; + //! Capability to indicate if the user can change their display name. + std::optional setDisplayname{}; + + //! Capability to indicate if the user can change their avatar. + std::optional setAvatarUrl{}; + + //! Capability to indicate if the user can change 3PID associations on their account. + std::optional thirdPartyIdChanges{}; + + //! Capability to indicate if the user can generate tokens to log further clients into their + //! account. + std::optional getLoginToken{}; + + //! Application-dependent keys using the + //! [Common Namespaced Identifier + //! Grammar](/appendices/#common-namespaced-identifier-grammar). + QVariantHash additionalProperties{}; }; // Construction/destruction @@ -60,14 +87,6 @@ class QUOTIENT_API GetCapabilitiesJob : public BaseJob { inline auto collectResponse(const GetCapabilitiesJob* job) { return job->capabilities(); } -template <> -struct QUOTIENT_API JsonObjectConverter { - static void fillFrom(const QJsonObject& jo, GetCapabilitiesJob::ChangePasswordCapability& result) - { - fillFromJson(jo.value("enabled"_L1), result.enabled); - } -}; - template <> struct QUOTIENT_API JsonObjectConverter { static void fillFrom(const QJsonObject& jo, GetCapabilitiesJob::RoomVersionsCapability& result) @@ -83,6 +102,10 @@ struct QUOTIENT_API JsonObjectConverter { { fillFromJson(jo.take("m.change_password"_L1), result.changePassword); fillFromJson(jo.take("m.room_versions"_L1), result.roomVersions); + fillFromJson(jo.take("m.set_displayname"_L1), result.setDisplayname); + fillFromJson(jo.take("m.set_avatar_url"_L1), result.setAvatarUrl); + fillFromJson(jo.take("m.3pid_changes"_L1), result.thirdPartyIdChanges); + fillFromJson(jo.take("m.get_login_token"_L1), result.getLoginToken); fromJson(jo, result.additionalProperties); } }; diff --git a/Quotient/csapi/content-repo.h b/Quotient/csapi/content-repo.h index 31c25b893..9c864d750 100644 --- a/Quotient/csapi/content-repo.h +++ b/Quotient/csapi/content-repo.h @@ -17,7 +17,11 @@ class QUOTIENT_API UploadContentJob : public BaseJob { //! The name of the file being uploaded //! //! \param contentType - //! The content type of the file being uploaded + //! **Optional.** The content type of the file being uploaded. + //! + //! Clients SHOULD always supply this header. + //! + //! Defaults to `application/octet-stream` if it is not set. explicit UploadContentJob(QIODevice* content, const QString& filename = {}, const QString& contentType = {}); @@ -46,7 +50,11 @@ class QUOTIENT_API UploadContentToMXCJob : public BaseJob { //! The name of the file being uploaded //! //! \param contentType - //! The content type of the file being uploaded + //! **Optional.** The content type of the file being uploaded. + //! + //! Clients SHOULD always supply this header. + //! + //! Defaults to `application/octet-stream` if it is not set. explicit UploadContentToMXCJob(const QString& serverName, const QString& mediaId, QIODevice* content, const QString& filename = {}, const QString& contentType = {}); @@ -166,9 +174,41 @@ class [[deprecated("Check the documentation for details")]] QUOTIENT_API GetCont // Result properties //! The content type of the file that was previously uploaded. + //! + //! The server MUST return a `Content-Type` which is either exactly the same + //! as the original upload, or reasonably close. The bounds of "reasonable" + //! are: + //! + //! * Adding a charset to `text/*` content types. + //! * Detecting HTML and using `text/html` instead of `text/plain`. + //! * Using `application/octet-stream` when the server determines the + //! content type is obviously wrong. For example, an encrypted file being + //! claimed as `image/png`. + //! * Returning `application/octet-stream` when the media has an + //! unknown/unprovided `Content-Type`. For example, being uploaded before + //! the server tracked content types or when the remote server is + //! non-compliantly omitting the header entirely. + //! + //! Actions not in the spirit of the above are not considered "reasonable". QString contentType() const { return QString::fromUtf8(reply()->rawHeader("Content-Type")); } - //! The name of the file that was previously uploaded, if set. + //! The + //! [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + //! of the returned content. MUST be one of `inline` or `attachment`, + //! and SHOULD contain a file name. + //! + //! If the `Content-Type` is allowed in the [restrictions for serving + //! inline content](/client-server-api/#serving-inline-content), + //! servers SHOULD use `inline`, otherwise they SHOULD use + //! `attachment`. + //! + //! If the upload was made with a `filename`, this header MUST + //! contain the same `filename`. Otherwise, `filename` is excluded + //! from the header. If the media being downloaded is remote, the + //! remote server's `filename` in the `Content-Disposition` header + //! is used as the `filename` instead. When the header is not + //! supplied, or does not supply a `filename`, the local download + //! response does not include a `filename`. QString contentDisposition() const { return QString::fromUtf8(reply()->rawHeader("Content-Disposition")); @@ -240,10 +280,33 @@ class [[deprecated("Check the documentation for details")]] QUOTIENT_API GetCont // Result properties //! The content type of the file that was previously uploaded. + //! + //! The server MUST return a `Content-Type` which is either exactly the same + //! as the original upload, or reasonably close. The bounds of "reasonable" + //! are: + //! + //! * Adding a charset to `text/*` content types. + //! * Detecting HTML and using `text/html` instead of `text/plain`. + //! * Using `application/octet-stream` when the server determines the + //! content type is obviously wrong. For example, an encrypted file being + //! claimed as `image/png`. + //! * Returning `application/octet-stream` when the media has an + //! unknown/unprovided `Content-Type`. For example, being uploaded before + //! the server tracked content types or when the remote server is + //! non-compliantly omitting the header entirely. + //! + //! Actions not in the spirit of the above are not considered "reasonable". QString contentType() const { return QString::fromUtf8(reply()->rawHeader("Content-Type")); } - //! The `fileName` requested or the name of the file that was previously - //! uploaded, if set. + //! The + //! [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + //! of the returned content. MUST be one of `inline` or `attachment`, + //! and MUST contain the file name requested in the path. + //! + //! If the `Content-Type` is allowed in the [restrictions for serving + //! inline content](/client-server-api/#serving-inline-content), + //! servers SHOULD use `inline`, otherwise they SHOULD use + //! `attachment`. QString contentDisposition() const { return QString::fromUtf8(reply()->rawHeader("Content-Disposition")); @@ -340,6 +403,19 @@ class [[deprecated("Check the documentation for details")]] QUOTIENT_API GetCont // Result properties + //! The + //! [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + //! of the returned content. MUST be `inline`, and SHOULD contain a file name (e.g. + //! `thumbnail.png`). + //! + //! Servers should note the [Content-Type restrictions for serving inline + //! content](/client-server-api/#serving-inline-content), as these limitations imply which + //! formats should be used for thumbnail generation. + QString contentDisposition() const + { + return QString::fromUtf8(reply()->rawHeader("Content-Disposition")); + } + //! The content type of the thumbnail. QString contentType() const { return QString::fromUtf8(reply()->rawHeader("Content-Type")); } diff --git a/Quotient/csapi/definitions/user_identifier.h b/Quotient/csapi/definitions/user_identifier.h index 3ca6b6f78..035cda5b9 100644 --- a/Quotient/csapi/definitions/user_identifier.h +++ b/Quotient/csapi/definitions/user_identifier.h @@ -7,10 +7,11 @@ namespace Quotient { //! Identification information for a user struct QUOTIENT_API UserIdentifier { - //! The type of identification. See [Identifier types](/client-server-api/#identifier-types) + //! The type of identification. See [Identifier types](/client-server-api/#identifier-types) //! for supported values and additional property descriptions. QString type; + //! Keys dependent on the identification type. QVariantHash additionalProperties{}; }; diff --git a/Quotient/csapi/definitions/wellknown/full.h b/Quotient/csapi/definitions/wellknown/full.h index a3794b5a2..650f4d7df 100644 --- a/Quotient/csapi/definitions/wellknown/full.h +++ b/Quotient/csapi/definitions/wellknown/full.h @@ -16,7 +16,7 @@ struct QUOTIENT_API DiscoveryInformation { std::optional identityServer{}; //! Application-dependent keys using Java package naming convention. - QHash additionalProperties{}; + QVariantHash additionalProperties{}; }; template <> diff --git a/Quotient/csapi/joining.cpp b/Quotient/csapi/joining.cpp index df4000cbb..ffd4b5acb 100644 --- a/Quotient/csapi/joining.cpp +++ b/Quotient/csapi/joining.cpp @@ -17,18 +17,21 @@ JoinRoomByIdJob::JoinRoomByIdJob(const QString& roomId, addExpectedKey(u"room_id"_s); } -auto queryToJoinRoom(const QStringList& serverName) +auto queryToJoinRoom(const QStringList& serverName, const QStringList& via) { QUrlQuery _q; addParam(_q, u"server_name"_s, serverName); + addParam(_q, u"via"_s, via); return _q; } JoinRoomJob::JoinRoomJob(const QString& roomIdOrAlias, const QStringList& serverName, + const QStringList& via, const std::optional& thirdPartySigned, const QString& reason) : BaseJob(HttpVerb::Post, u"JoinRoomJob"_s, - makePath("/_matrix/client/v3", "/join/", roomIdOrAlias), queryToJoinRoom(serverName)) + makePath("/_matrix/client/v3", "/join/", roomIdOrAlias), + queryToJoinRoom(serverName, via)) { QJsonObject _dataJson; addParam(_dataJson, "third_party_signed"_L1, thirdPartySigned); diff --git a/Quotient/csapi/joining.h b/Quotient/csapi/joining.h index 26d182eb7..0387a164c 100644 --- a/Quotient/csapi/joining.h +++ b/Quotient/csapi/joining.h @@ -67,6 +67,10 @@ class QUOTIENT_API JoinRoomJob : public BaseJob { //! The servers to attempt to join the room through. One of the servers //! must be participating in the room. //! + //! \param via + //! The servers to attempt to join the room through. One of the servers + //! must be participating in the room. + //! //! \param thirdPartySigned //! If a `third_party_signed` was supplied, the homeserver must verify //! that it matches a pending `m.room.third_party_invite` event in the @@ -76,6 +80,7 @@ class QUOTIENT_API JoinRoomJob : public BaseJob { //! Optional reason to be included as the `reason` on the subsequent //! membership event. explicit JoinRoomJob(const QString& roomIdOrAlias, const QStringList& serverName = {}, + const QStringList& via = {}, const std::optional& thirdPartySigned = std::nullopt, const QString& reason = {}); diff --git a/Quotient/csapi/knocking.cpp b/Quotient/csapi/knocking.cpp index 9660e0173..f3e121e42 100644 --- a/Quotient/csapi/knocking.cpp +++ b/Quotient/csapi/knocking.cpp @@ -4,17 +4,19 @@ using namespace Quotient; -auto queryToKnockRoom(const QStringList& serverName) +auto queryToKnockRoom(const QStringList& serverName, const QStringList& via) { QUrlQuery _q; addParam(_q, u"server_name"_s, serverName); + addParam(_q, u"via"_s, via); return _q; } KnockRoomJob::KnockRoomJob(const QString& roomIdOrAlias, const QStringList& serverName, - const QString& reason) + const QStringList& via, const QString& reason) : BaseJob(HttpVerb::Post, u"KnockRoomJob"_s, - makePath("/_matrix/client/v3", "/knock/", roomIdOrAlias), queryToKnockRoom(serverName)) + makePath("/_matrix/client/v3", "/knock/", roomIdOrAlias), + queryToKnockRoom(serverName, via)) { QJsonObject _dataJson; addParam(_dataJson, "reason"_L1, reason); diff --git a/Quotient/csapi/knocking.h b/Quotient/csapi/knocking.h index 4f1892687..69105f783 100644 --- a/Quotient/csapi/knocking.h +++ b/Quotient/csapi/knocking.h @@ -32,11 +32,15 @@ class QUOTIENT_API KnockRoomJob : public BaseJob { //! The servers to attempt to knock on the room through. One of the servers //! must be participating in the room. //! + //! \param via + //! The servers to attempt to knock on the room through. One of the servers + //! must be participating in the room. + //! //! \param reason //! Optional reason to be included as the `reason` on the subsequent //! membership event. explicit KnockRoomJob(const QString& roomIdOrAlias, const QStringList& serverName = {}, - const QString& reason = {}); + const QStringList& via = {}, const QString& reason = {}); // Result properties diff --git a/Quotient/csapi/login_token.h b/Quotient/csapi/login_token.h index 5813edde8..73a9c4d8f 100644 --- a/Quotient/csapi/login_token.h +++ b/Quotient/csapi/login_token.h @@ -23,10 +23,10 @@ namespace Quotient { //! //! Clients, both authenticated and unauthenticated, might wish to hide user interface which exposes //! this feature if the server is not offering it. Authenticated clients can check for support on -//! a per-user basis with the `m.get_login_token` -//! [capability](/client-server-api/#capabilities-negotiation), while unauthenticated clients can -//! detect server support by looking for an `m.login.token` login flow with `get_login_token: true` -//! on [`GET /login`](/client-server-api/#post_matrixclientv3login). +//! a per-user basis with the [`m.get_login_token`](/client-server-api/#mget_login_token-capability) +//! capability, while unauthenticated clients can detect server support by looking for an +//! `m.login.token` login flow with `get_login_token: true` on [`GET +//! /login`](/client-server-api/#post_matrixclientv3login). //! //! In v1.7 of the specification, transmission of the generated token to an unauthenticated client //! is left as an implementation detail. Future MSCs such as diff --git a/Quotient/csapi/profile.h b/Quotient/csapi/profile.h index 98b29c7b2..a96789503 100644 --- a/Quotient/csapi/profile.h +++ b/Quotient/csapi/profile.h @@ -88,8 +88,7 @@ inline auto collectResponse(const GetAvatarUrlJob* job) { return job->avatarUrl( //! //! Get the combined profile information for this user. This API may be used //! to fetch the user's own profile information or other users; either -//! locally or on remote homeservers. This API may return keys which are not -//! limited to `displayname` or `avatar_url`. +//! locally or on remote homeservers. class QUOTIENT_API GetUserProfileJob : public BaseJob { public: //! \param userId diff --git a/Quotient/csapi/pushrules.cpp b/Quotient/csapi/pushrules.cpp index 0375cae04..3e2d2af17 100644 --- a/Quotient/csapi/pushrules.cpp +++ b/Quotient/csapi/pushrules.cpp @@ -15,29 +15,38 @@ GetPushRulesJob::GetPushRulesJob() addExpectedKey(u"global"_s); } -QUrl GetPushRuleJob::makeRequestUrl(const HomeserverData& hsData, const QString& scope, - const QString& kind, const QString& ruleId) +QUrl GetPushRulesGlobalJob::makeRequestUrl(const HomeserverData& hsData) { - return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/pushrules/", scope, "/", + return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/pushrules/global")); +} + +GetPushRulesGlobalJob::GetPushRulesGlobalJob() + : BaseJob(HttpVerb::Get, u"GetPushRulesGlobalJob"_s, + makePath("/_matrix/client/v3", "/pushrules/global")) +{} + +QUrl GetPushRuleJob::makeRequestUrl(const HomeserverData& hsData, const QString& kind, + const QString& ruleId) +{ + return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/pushrules/global/", kind, "/", ruleId)); } -GetPushRuleJob::GetPushRuleJob(const QString& scope, const QString& kind, const QString& ruleId) +GetPushRuleJob::GetPushRuleJob(const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Get, u"GetPushRuleJob"_s, - makePath("/_matrix/client/v3", "/pushrules/", scope, "/", kind, "/", ruleId)) + makePath("/_matrix/client/v3", "/pushrules/global/", kind, "/", ruleId)) {} -QUrl DeletePushRuleJob::makeRequestUrl(const HomeserverData& hsData, const QString& scope, - const QString& kind, const QString& ruleId) +QUrl DeletePushRuleJob::makeRequestUrl(const HomeserverData& hsData, const QString& kind, + const QString& ruleId) { - return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/pushrules/", scope, "/", + return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/pushrules/global/", kind, "/", ruleId)); } -DeletePushRuleJob::DeletePushRuleJob(const QString& scope, const QString& kind, - const QString& ruleId) +DeletePushRuleJob::DeletePushRuleJob(const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Delete, u"DeletePushRuleJob"_s, - makePath("/_matrix/client/v3", "/pushrules/", scope, "/", kind, "/", ruleId)) + makePath("/_matrix/client/v3", "/pushrules/global/", kind, "/", ruleId)) {} auto queryToSetPushRule(const QString& before, const QString& after) @@ -48,12 +57,12 @@ auto queryToSetPushRule(const QString& before, const QString& after) return _q; } -SetPushRuleJob::SetPushRuleJob(const QString& scope, const QString& kind, const QString& ruleId, +SetPushRuleJob::SetPushRuleJob(const QString& kind, const QString& ruleId, const QVector& actions, const QString& before, const QString& after, const QVector& conditions, const QString& pattern) : BaseJob(HttpVerb::Put, u"SetPushRuleJob"_s, - makePath("/_matrix/client/v3", "/pushrules/", scope, "/", kind, "/", ruleId), + makePath("/_matrix/client/v3", "/pushrules/global/", kind, "/", ruleId), queryToSetPushRule(before, after)) { QJsonObject _dataJson; @@ -63,54 +72,48 @@ SetPushRuleJob::SetPushRuleJob(const QString& scope, const QString& kind, const setRequestData({ _dataJson }); } -QUrl IsPushRuleEnabledJob::makeRequestUrl(const HomeserverData& hsData, const QString& scope, - const QString& kind, const QString& ruleId) +QUrl IsPushRuleEnabledJob::makeRequestUrl(const HomeserverData& hsData, const QString& kind, + const QString& ruleId) { - return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/pushrules/", scope, "/", + return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/pushrules/global/", kind, "/", ruleId, "/enabled")); } -IsPushRuleEnabledJob::IsPushRuleEnabledJob(const QString& scope, const QString& kind, - const QString& ruleId) +IsPushRuleEnabledJob::IsPushRuleEnabledJob(const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Get, u"IsPushRuleEnabledJob"_s, - makePath("/_matrix/client/v3", "/pushrules/", scope, "/", kind, "/", ruleId, - "/enabled")) + makePath("/_matrix/client/v3", "/pushrules/global/", kind, "/", ruleId, "/enabled")) { addExpectedKey(u"enabled"_s); } -SetPushRuleEnabledJob::SetPushRuleEnabledJob(const QString& scope, const QString& kind, - const QString& ruleId, bool enabled) +SetPushRuleEnabledJob::SetPushRuleEnabledJob(const QString& kind, const QString& ruleId, + bool enabled) : BaseJob(HttpVerb::Put, u"SetPushRuleEnabledJob"_s, - makePath("/_matrix/client/v3", "/pushrules/", scope, "/", kind, "/", ruleId, - "/enabled")) + makePath("/_matrix/client/v3", "/pushrules/global/", kind, "/", ruleId, "/enabled")) { QJsonObject _dataJson; addParam<>(_dataJson, "enabled"_L1, enabled); setRequestData({ _dataJson }); } -QUrl GetPushRuleActionsJob::makeRequestUrl(const HomeserverData& hsData, const QString& scope, - const QString& kind, const QString& ruleId) +QUrl GetPushRuleActionsJob::makeRequestUrl(const HomeserverData& hsData, const QString& kind, + const QString& ruleId) { - return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/pushrules/", scope, "/", + return BaseJob::makeRequestUrl(hsData, makePath("/_matrix/client/v3", "/pushrules/global/", kind, "/", ruleId, "/actions")); } -GetPushRuleActionsJob::GetPushRuleActionsJob(const QString& scope, const QString& kind, - const QString& ruleId) +GetPushRuleActionsJob::GetPushRuleActionsJob(const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Get, u"GetPushRuleActionsJob"_s, - makePath("/_matrix/client/v3", "/pushrules/", scope, "/", kind, "/", ruleId, - "/actions")) + makePath("/_matrix/client/v3", "/pushrules/global/", kind, "/", ruleId, "/actions")) { addExpectedKey(u"actions"_s); } -SetPushRuleActionsJob::SetPushRuleActionsJob(const QString& scope, const QString& kind, - const QString& ruleId, const QVector& actions) +SetPushRuleActionsJob::SetPushRuleActionsJob(const QString& kind, const QString& ruleId, + const QVector& actions) : BaseJob(HttpVerb::Put, u"SetPushRuleActionsJob"_s, - makePath("/_matrix/client/v3", "/pushrules/", scope, "/", kind, "/", ruleId, - "/actions")) + makePath("/_matrix/client/v3", "/pushrules/global/", kind, "/", ruleId, "/actions")) { QJsonObject _dataJson; addParam<>(_dataJson, "actions"_L1, actions); diff --git a/Quotient/csapi/pushrules.h b/Quotient/csapi/pushrules.h index d31ea1b27..dafe2dc70 100644 --- a/Quotient/csapi/pushrules.h +++ b/Quotient/csapi/pushrules.h @@ -12,10 +12,8 @@ namespace Quotient { //! \brief Retrieve all push rulesets. //! -//! Retrieve all push rulesets for this user. Clients can "drill-down" on -//! the rulesets by suffixing a `scope` to this path e.g. -//! `/pushrules/global/`. This will return a subset of this data under the -//! specified key e.g. the `global` key. +//! Retrieve all push rulesets for this user. Currently the only push ruleset +//! defined is `global`. class QUOTIENT_API GetPushRulesJob : public BaseJob { public: explicit GetPushRulesJob(); @@ -34,27 +32,45 @@ class QUOTIENT_API GetPushRulesJob : public BaseJob { inline auto collectResponse(const GetPushRulesJob* job) { return job->global(); } +//! \brief Retrieve all push rules. +//! +//! Retrieve all push rules for this user. +class QUOTIENT_API GetPushRulesGlobalJob : public BaseJob { +public: + explicit GetPushRulesGlobalJob(); + + //! \brief Construct a URL without creating a full-fledged job object + //! + //! This function can be used when a URL for GetPushRulesGlobalJob + //! is necessary but the job itself isn't. + static QUrl makeRequestUrl(const HomeserverData& hsData); + + // Result properties + + //! All the push rules for this user. + PushRuleset data() const { return fromJson(jsonData()); } +}; + +inline auto collectResponse(const GetPushRulesGlobalJob* job) { return job->data(); } + //! \brief Retrieve a push rule. //! //! Retrieve a single specified push rule. class QUOTIENT_API GetPushRuleJob : public BaseJob { public: - //! \param scope - //! `global` to specify global rules. - //! //! \param kind //! The kind of rule //! //! \param ruleId //! The identifier for the rule. - explicit GetPushRuleJob(const QString& scope, const QString& kind, const QString& ruleId); + explicit GetPushRuleJob(const QString& kind, const QString& ruleId); //! \brief Construct a URL without creating a full-fledged job object //! //! This function can be used when a URL for GetPushRuleJob //! is necessary but the job itself isn't. - static QUrl makeRequestUrl(const HomeserverData& hsData, const QString& scope, - const QString& kind, const QString& ruleId); + static QUrl makeRequestUrl(const HomeserverData& hsData, const QString& kind, + const QString& ruleId); // Result properties @@ -70,22 +86,19 @@ inline auto collectResponse(const GetPushRuleJob* job) { return job->pushRule(); //! This endpoint removes the push rule defined in the path. class QUOTIENT_API DeletePushRuleJob : public BaseJob { public: - //! \param scope - //! `global` to specify global rules. - //! //! \param kind //! The kind of rule //! //! \param ruleId //! The identifier for the rule. - explicit DeletePushRuleJob(const QString& scope, const QString& kind, const QString& ruleId); + explicit DeletePushRuleJob(const QString& kind, const QString& ruleId); //! \brief Construct a URL without creating a full-fledged job object //! //! This function can be used when a URL for DeletePushRuleJob //! is necessary but the job itself isn't. - static QUrl makeRequestUrl(const HomeserverData& hsData, const QString& scope, - const QString& kind, const QString& ruleId); + static QUrl makeRequestUrl(const HomeserverData& hsData, const QString& kind, + const QString& ruleId); }; //! \brief Add or change a push rule. @@ -108,9 +121,6 @@ class QUOTIENT_API DeletePushRuleJob : public BaseJob { //! When creating push rules, they MUST be enabled by default. class QUOTIENT_API SetPushRuleJob : public BaseJob { public: - //! \param scope - //! `global` to specify global rules. - //! //! \param kind //! The kind of rule //! @@ -139,7 +149,7 @@ class QUOTIENT_API SetPushRuleJob : public BaseJob { //! //! \param pattern //! Only applicable to `content` rules. The glob-style pattern to match against. - explicit SetPushRuleJob(const QString& scope, const QString& kind, const QString& ruleId, + explicit SetPushRuleJob(const QString& kind, const QString& ruleId, const QVector& actions, const QString& before = {}, const QString& after = {}, const QVector& conditions = {}, const QString& pattern = {}); @@ -150,23 +160,19 @@ class QUOTIENT_API SetPushRuleJob : public BaseJob { //! This endpoint gets whether the specified push rule is enabled. class QUOTIENT_API IsPushRuleEnabledJob : public BaseJob { public: - //! \param scope - //! Either `global` or `device/` to specify global - //! rules or device rules for the given `profile_tag`. - //! //! \param kind //! The kind of rule //! //! \param ruleId //! The identifier for the rule. - explicit IsPushRuleEnabledJob(const QString& scope, const QString& kind, const QString& ruleId); + explicit IsPushRuleEnabledJob(const QString& kind, const QString& ruleId); //! \brief Construct a URL without creating a full-fledged job object //! //! This function can be used when a URL for IsPushRuleEnabledJob //! is necessary but the job itself isn't. - static QUrl makeRequestUrl(const HomeserverData& hsData, const QString& scope, - const QString& kind, const QString& ruleId); + static QUrl makeRequestUrl(const HomeserverData& hsData, const QString& kind, + const QString& ruleId); // Result properties @@ -181,9 +187,6 @@ inline auto collectResponse(const IsPushRuleEnabledJob* job) { return job->enabl //! This endpoint allows clients to enable or disable the specified push rule. class QUOTIENT_API SetPushRuleEnabledJob : public BaseJob { public: - //! \param scope - //! `global` to specify global rules. - //! //! \param kind //! The kind of rule //! @@ -192,8 +195,7 @@ class QUOTIENT_API SetPushRuleEnabledJob : public BaseJob { //! //! \param enabled //! Whether the push rule is enabled or not. - explicit SetPushRuleEnabledJob(const QString& scope, const QString& kind, const QString& ruleId, - bool enabled); + explicit SetPushRuleEnabledJob(const QString& kind, const QString& ruleId, bool enabled); }; //! \brief The actions for a push rule @@ -201,23 +203,19 @@ class QUOTIENT_API SetPushRuleEnabledJob : public BaseJob { //! This endpoint get the actions for the specified push rule. class QUOTIENT_API GetPushRuleActionsJob : public BaseJob { public: - //! \param scope - //! Either `global` or `device/` to specify global - //! rules or device rules for the given `profile_tag`. - //! //! \param kind //! The kind of rule //! //! \param ruleId //! The identifier for the rule. - explicit GetPushRuleActionsJob(const QString& scope, const QString& kind, const QString& ruleId); + explicit GetPushRuleActionsJob(const QString& kind, const QString& ruleId); //! \brief Construct a URL without creating a full-fledged job object //! //! This function can be used when a URL for GetPushRuleActionsJob //! is necessary but the job itself isn't. - static QUrl makeRequestUrl(const HomeserverData& hsData, const QString& scope, - const QString& kind, const QString& ruleId); + static QUrl makeRequestUrl(const HomeserverData& hsData, const QString& kind, + const QString& ruleId); // Result properties @@ -233,9 +231,6 @@ inline auto collectResponse(const GetPushRuleActionsJob* job) { return job->acti //! This can be used to change the actions of builtin rules. class QUOTIENT_API SetPushRuleActionsJob : public BaseJob { public: - //! \param scope - //! `global` to specify global rules. - //! //! \param kind //! The kind of rule //! @@ -244,7 +239,7 @@ class QUOTIENT_API SetPushRuleActionsJob : public BaseJob { //! //! \param actions //! The action(s) to perform for this rule. - explicit SetPushRuleActionsJob(const QString& scope, const QString& kind, const QString& ruleId, + explicit SetPushRuleActionsJob(const QString& kind, const QString& ruleId, const QVector& actions); }; diff --git a/Quotient/csapi/read_markers.cpp b/Quotient/csapi/read_markers.cpp index 4673983eb..6f53b7f0d 100644 --- a/Quotient/csapi/read_markers.cpp +++ b/Quotient/csapi/read_markers.cpp @@ -4,14 +4,14 @@ using namespace Quotient; -SetReadMarkerJob::SetReadMarkerJob(const QString& roomId, const QString& mFullyRead, - const QString& mRead, const QString& mReadPrivate) +SetReadMarkerJob::SetReadMarkerJob(const QString& roomId, const QString& fullyRead, + const QString& read, const QString& readPrivate) : BaseJob(HttpVerb::Post, u"SetReadMarkerJob"_s, makePath("/_matrix/client/v3", "/rooms/", roomId, "/read_markers")) { QJsonObject _dataJson; - addParam(_dataJson, "m.fully_read"_L1, mFullyRead); - addParam(_dataJson, "m.read"_L1, mRead); - addParam(_dataJson, "m.read.private"_L1, mReadPrivate); + addParam(_dataJson, "m.fully_read"_L1, fullyRead); + addParam(_dataJson, "m.read"_L1, read); + addParam(_dataJson, "m.read.private"_L1, readPrivate); setRequestData({ _dataJson }); } diff --git a/Quotient/csapi/read_markers.h b/Quotient/csapi/read_markers.h index 8d69ef04c..c1e67cd86 100644 --- a/Quotient/csapi/read_markers.h +++ b/Quotient/csapi/read_markers.h @@ -15,21 +15,21 @@ class QUOTIENT_API SetReadMarkerJob : public BaseJob { //! \param roomId //! The room ID to set the read marker in for the user. //! - //! \param mFullyRead + //! \param fullyRead //! The event ID the read marker should be located at. The //! event MUST belong to the room. //! - //! \param mRead + //! \param read //! The event ID to set the read receipt location at. This is //! equivalent to calling `/receipt/m.read/$elsewhere:example.org` //! and is provided here to save that extra call. //! - //! \param mReadPrivate + //! \param readPrivate //! The event ID to set the *private* read receipt location at. This //! equivalent to calling `/receipt/m.read.private/$elsewhere:example.org` //! and is provided here to save that extra call. - explicit SetReadMarkerJob(const QString& roomId, const QString& mFullyRead = {}, - const QString& mRead = {}, const QString& mReadPrivate = {}); + explicit SetReadMarkerJob(const QString& roomId, const QString& fullyRead = {}, + const QString& read = {}, const QString& readPrivate = {}); }; } // namespace Quotient diff --git a/Quotient/events/eventrelation.h b/Quotient/events/eventrelation.h index ccd225f58..6c4f5cf9d 100644 --- a/Quotient/events/eventrelation.h +++ b/Quotient/events/eventrelation.h @@ -38,9 +38,12 @@ struct QUOTIENT_API EventRelation { { return { ReplacementType, std::move(eventId) }; } - static EventRelation replyInThread(QString threadRootId, bool isFallingBack, QString inThreadReplyEventId) + static EventRelation replyInThread(QString threadRootId, bool isFallingBack, + QString inThreadReplyEventId) { - return { ThreadType, std::move(threadRootId), {}, std::move(isFallingBack), std::move(inThreadReplyEventId) }; + return { + ThreadType, std::move(threadRootId), {}, isFallingBack, std::move(inThreadReplyEventId) + }; } }; diff --git a/Quotient/events/roommessageevent.cpp b/Quotient/events/roommessageevent.cpp index b35bd9627..1e75b54c7 100644 --- a/Quotient/events/roommessageevent.cpp +++ b/Quotient/events/roommessageevent.cpp @@ -237,8 +237,7 @@ bool RoomMessageEvent::isReply(bool includeFallbacks) const QString RoomMessageEvent::replyEventId(bool includeFallbacks) const { - const auto relation = relatesTo(); - if (relation.has_value()) { + if (const auto relation = relatesTo()) { if (relation.value().type == EventRelation::ReplyType) { return relation.value().eventId; } else if (relation.value().type == EventRelation::ThreadType && diff --git a/Quotient/jobs/downloadfilejob.cpp b/Quotient/jobs/downloadfilejob.cpp index d734a0545..5413958cd 100644 --- a/Quotient/jobs/downloadfilejob.cpp +++ b/Quotient/jobs/downloadfilejob.cpp @@ -53,7 +53,9 @@ QUrl DownloadFileJob::makeRequestUrl(const HomeserverData& hsData, const QString DownloadFileJob::DownloadFileJob(QString serverName, QString mediaId, const QString& localFilename) : BaseJob(HttpVerb::Get, u"DownloadFileJob"_s, {}) , d(makeImpl(std::move(serverName), std::move(mediaId), localFilename)) -{} +{ + setExpectedContentTypes({ "application/octet-stream" }); +} DownloadFileJob::DownloadFileJob(QString serverName, QString mediaId, const EncryptedFileMetadata& file, const QString& localFilename) diff --git a/Quotient/jobs/mediathumbnailjob.cpp b/Quotient/jobs/mediathumbnailjob.cpp index 9ac357324..24a1275dc 100644 --- a/Quotient/jobs/mediathumbnailjob.cpp +++ b/Quotient/jobs/mediathumbnailjob.cpp @@ -42,6 +42,7 @@ MediaThumbnailJob::MediaThumbnailJob(QString serverName, QString mediaId, QSize , animated(animated) { setLoggingCategory(THUMBNAILJOB); + setExpectedContentTypes({ "image/jpeg", "image/png", "image/apng", "image/gif", "image/webp" }); } MediaThumbnailJob::MediaThumbnailJob(const QUrl& mxcUri, QSize requestedSize, diff --git a/README.md b/README.md index b9d94c9d7..136475435 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # libQuotient -Made for Matrix + [![license](https://img.shields.io/github/license/quotient-im/libQuotient.svg)](https://github.com/quotient-im/libQuotient/blob/dev/COPYING) ![status](https://img.shields.io/badge/status-beta-yellow.svg) @@ -12,13 +12,13 @@ ![Sonar Coverage](https://img.shields.io/sonar/coverage/quotient-im_libQuotient?server=https%3A%2F%2Fsonarcloud.io) ![Matrix](https://img.shields.io/matrix/quotient:matrix.org?logo=matrix) -The Quotient project aims to produce a Qt-based SDK to develop applications -for [Matrix](https://matrix.org). libQuotient is a library that enables client -applications. It is the backbone of -[Quaternion](https://github.com/quotient-im/Quaternion), +The Quotient project aims to produce a Qt-based SDK to develop applications for +[Matrix](https://matrix.org). libQuotient is a library that enables client applications. It is +the backbone of [Quaternion](https://github.com/quotient-im/Quaternion), [NeoChat](https://invent.kde.org/network/neochat) and other projects. ## Contacts + You can find Quotient developers in the Matrix room: [#quotient:matrix.org](https://matrix.to/#/#quotient:matrix.org). @@ -28,204 +28,186 @@ If you find what looks like a security issue, please use instructions in [SECURITY.md](./SECURITY.md). ## Getting and using libQuotient -Depending on your platform, the library can be obtained from a package -management system. Recent releases of Fedora, Debian and openSUSE already have -it. Alternatively, you can build the library from the source and bundle it with -your application, as described below. + +Depending on your platform, the library can be obtained from a package management system. +Recent releases of Fedora, Debian and openSUSE already have it. Alternatively, you can build +the library from the source and bundle it with your application, as described below. ### Pre-requisites + To use libQuotient (i.e. build or run applications with it), you'll need: -- A recent Linux, macOS or Windows system (desktop versions are known to work, - and there's also limited positive experience with Android) - - Recent enough Linux examples: Debian Trixie; Fedora 39; - openSUSE Leap 15.6 (to be released); Ubuntu 24.04 LTS (to be released) + +- A recent Linux, macOS or Windows system (desktop versions are known to work, and there's also + limited positive experience with Android) + - Recent enough Linux examples: Debian Trixie; Fedora 39; openSUSE Leap 15.6; Ubuntu 24.04 LTS - Qt 6.4 or newer - either Open Source or Commercial -- QtKeychain (https://github.com/frankosterfeld/qtkeychain) - 0.12 or newer is - recommended; the build configuration of both QtKeychain and libQuotient - must use the same Qt major version +- QtKeychain (https://github.com/frankosterfeld/qtkeychain) - 0.12 or newer is recommended; + the build configuration of QtKeychain must use the same Qt major version, that is Qt 6. To build applications with libQuotient, you'll also need: + - CMake 3.26 or newer -- A C++ toolchain that supports at least some subset of C++20 (concepts, - in particular): +- A C++ toolchain that supports at least some subset of C++20 (concepts, in particular): - GCC 13 (Windows, Linux, macOS), Clang 16 (Linux), Apple Clang 15 (macOS 14+) and Visual Studio 2022 (Windows) are the oldest officially supported - libolm 3.2.5 or newer (the latest 3.x strongly recommended) -- OpenSSL (both 1.1.x and 3.x are known to work but 3.x is strongly recommended - as 1.1.x is out of support) -- Any build system that works with CMake should be fine; known to work are - GNU Make and ninja (recommended) on any platform, NMake and jom on Windows +- OpenSSL 3.x (1.1.x may still work but is strongly recommended against) +- Any build system that works with CMake should be fine; known to work are GNU Make and + ninja (recommended) on any platform; NMake and jom on Windows -The requirements to build libQuotient itself are basically the same except -that you should install development libraries for the dependencies listed above. +The requirements to build libQuotient itself are basically the same except that you should install +development libraries for the dependencies listed above. #### Linux -Just install the prerequisites using your preferred package manager. If your Qt -package base is fine-grained you might want to run CMake and look at error -messages. The library is entirely offscreen; however, aside from QtCore and -QtNetwork it also depends on QtGui in order to handle avatar thumbnails, without -any on-screen drawing. + +Just install the prerequisites using your preferred package manager. If your Qt package base is +fine-grained you might want to run CMake and look at error messages. The library is entirely +offscreen; however, aside from QtCore and QtNetwork it also depends on QtGui in order to handle +avatar thumbnails, without any on-screen drawing. #### macOS -`brew install qt qtkeychain libolm openssl@3` should get you the most recent -versions of the runtime libraries. -You may need to add `$(brew --prefix qt)`, `$(brew --prefix qtkeychain)` etc. -to `CMAKE_PREFIX_PATH` (see below) to make CMake aware of the library locations. +`brew install qt qtkeychain libolm openssl@3` should get you the most recent versions of +the runtime libraries. + +You may need to add `$(brew --prefix qt)`, `$(brew --prefix qtkeychain)` etc. to `CMAKE_PREFIX_PATH` +(see below) to make CMake aware of the library locations. #### Windows -Install Qt and OpenSSL using The Qt Project official installer; make sure -to also tick the CMake box in the list of components to install unless you -already have it. This will get you both the runtime libraries and -the development files, which are also suitable to build libQuotient itself. -If you go this way, you'll have to build QtKeychain from the source code. - -Alternatively, you can use vcpkg to install Qt, OpenSSL, and QtKeychain. -In that case you're not getting Qt Creator, which is a very nice IDE to deal -with Qt-based projects; but if you already use VSCode or CLion, you might prefer -this route. You can also mix and match, installing Qt Creator from the official -installer and the rest from vcpkg. Mixing Qt from the official installer with -Qt Keychain from vcpkg may or may not work, depending on the Qt version used -to build Qt Keychain. - -_If you build from the command line_: the commands in further sections imply -that `cmake` is in your `PATH`, otherwise you have to prepend those commands -with actual paths. It's a good idea to run the `qtenv2.bat` script that can -be found in `C:\Qt\\\bin` (assuming you installed Qt to -`C:\Qt`). This script adds necessary paths to `PATH`. You might not want to run -that script on system startup but it's very handy to setup the environment -before building. - -_If you use a C++ IDE_: you should be able to configure CMake path and extra -options (`CMAKE_PREFIX_PATH`, in particular) in its settings. It is recommended -NOT to add the path for Qt (or any other library) to `PATH` explicitly; use -`CMAKE_PREFIX_PATH` instead and leave `PATH` unchanged. If your IDE is -Qt Creator, you shouldn't need to deal with Qt paths at all, just pick the right -kit and go straight to building. - -You will also need libolm. You'll have to build it yourself - there's no binary -for Windows that you can download from vcpkg or elsewhere, as of this writing. -The source code is available at https://gitlab.matrix.org/matrix-org/olm; -you can use the same toolchain (CMake+MSVC, e.g.) as for the rest of Quotient. +Install Qt and OpenSSL using The Qt Project official installer; make sure to also tick the CMake box +in the list of components to install unless you already have it. This will get you both the runtime +libraries and the development files, which are also suitable to build libQuotient itself. If you go +this way, you'll have to build QtKeychain from the source code. + +Alternatively, you can use vcpkg to install Qt, OpenSSL, and QtKeychain. In that case you're +not getting Qt Creator, which is a very nice IDE to deal with Qt-based projects; but if you already +use VSCode or CLion, you might prefer this route. You can also mix and match, installing Qt Creator +from the official installer and the rest from vcpkg. Mixing Qt from the official installer with Qt +Keychain from vcpkg may or may not work, depending on the Qt version used to build Qt Keychain. + +_If you build from the command line_: the commands in further sections imply that `cmake` is in your +`PATH`, otherwise you have to prepend those commands with actual paths. It's a good idea to run +the `qtenv2.bat` script that can be found in `C:\Qt\\\bin` (assuming you +installed Qt to `C:\Qt`). This script adds necessary paths to `PATH`. You might not want to run +that script on system startup but it's very handy to setup the environment before building. + +_If you use a C++ IDE_: you should be able to configure CMake path and extra options +(`CMAKE_PREFIX_PATH`, in particular) in its settings. It is recommended NOT to add the path for Qt +(or any other library) to `PATH` explicitly; use `CMAKE_PREFIX_PATH` instead and leave `PATH` +unchanged. If your IDE is Qt Creator, you shouldn't need to deal with Qt paths at all, just pick +the right kit and go straight to building. + +You will also need libolm. You'll have to build it yourself - there's no binary for Windows that you +can download from vcpkg or elsewhere, as of this writing. The source code is available +at https://gitlab.matrix.org/matrix-org/olm; you can use the same toolchain (CMake+MSVC, e.g.) as +for the rest of Quotient. ## Using the library + If you're just starting a project using libQuotient from scratch, you can copy -`quotest/CMakeLists.txt` to your project and change `quotest` to your -project name. If you already have an existing CMakeLists.txt, you need to insert -a `find_package(Quotient REQUIRED)` line to an appropriate place in it (use -`find_package(Quotient)` if libQuotient is not a hard dependency for you) and -then add `Quotient` to your `target_link_libraries()` line. +`quotest/CMakeLists.txt` to your project and change `quotest` to your project name. If you already +have an existing CMakeLists.txt, you need to insert a `find_package(Quotient REQUIRED)` line to an +appropriate place in it (use `find_package(Quotient)` if libQuotient is not a hard dependency for +you) and then add `Quotient` to your `target_link_libraries()` line. -Dynamic linking is only tested on Linux at the moment and is the recommended way -of linking with libQuotient on this platform. Static linking is the default on -Windows/macOS; feel free to experiment with dynamic linking and provide feedback -with your results. +Dynamic linking is only tested on Linux at the moment and is the recommended way of linking with +libQuotient on this platform. Static linking is the default on Windows/macOS; feel free to +experiment with dynamic linking and provide feedback with your results. ### Documentation -A (very basic) overview can be found at -[the respective wiki page](https://github.com/quotient-im/libQuotient/wiki/libQuotient-overview). -The Doxygen documentation for the library can be found at -https://quotient-im.github.io/libQuotient/. Further on, looking at the source -code for [Quotest](quotest) - the test application that comes with libQuotient - -may help you with most common use cases such as sending messages, uploading -files, setting room state etc. - -For examples and patterns of more extensive usage feel free to check out (and -copy, with appropriate attribution) the source code of -[Quaternion](https://github.com/quotient-im/Quaternion) (the reference client -for libQuotient) or [NeoChat](https://invent.kde.org/network/neochat). + +A (very basic) overview can be found +at [the respective wiki page](https://github.com/quotient-im/libQuotient/wiki/libQuotient-overview). +The Doxygen documentation for the library can be found +at https://quotient-im.github.io/libQuotient/. Further on, looking at the source code +for [Quotest](quotest) - the test application that comes with libQuotient - may help you with most +common use cases such as sending messages, uploading files, setting room state etc. + +For examples and patterns of more extensive usage feel free to check out (and copy, with appropriate +attribution) the source code of [Quaternion](https://github.com/quotient-im/Quaternion) +(the reference client for libQuotient) or [NeoChat](https://invent.kde.org/network/neochat). ### API/ABI stability -Since Quotient 0.7.2, all header files of libQuotient -*except those ending with `_p.h`* are considered public and covered by API/ABI -stability guarantees. Specifically, the API and ABI are backwards compatible -within every minor version (0.7.x releases) with every next minor version (0.8, -e.g.) breaking the compatibility. Once we reach 1.0, this rule will apply -to the major version, aligning with [semantic versioning](https://semver.org/) -rules. `_p.h` files are not covered by these guarantees and some of them might -not even be shipped by Linux distributions; client code should not directly -include these files, nor use symbols defined there. +Since Quotient 0.7.2, symbols in all header files of libQuotient *except files ending with `_p.h` +and `namespace _impl`* are considered public and covered by API/ABI stability guarantees. +Specifically, the API and ABI are backwards compatible within every minor version (0.7.x releases) +with every next minor version (0.8, e.g.) breaking the compatibility. Once we reach 1.0, this rule +will apply to the major version, aligning with [semantic versioning](https://semver.org/) rules. +`_p.h` files and `namespace _impl` are not covered by these guarantees; client code should not +directly include these files, nor use symbols defined in these locations. ## Building the library + On platforms other than Linux you will have to build libQuotient yourself before usage - nobody packaged it so far (contributions welcome!). You may also want to build the library on Linux if you need a newer version or snapshot than that coming in your distro. -[The source code is at GitHub](https://github.com/quotient-im/libQuotient). -Checking out a certain commit or tag (rather than downloading the archive) -along with submodules is strongly recommended. If you want to hack on -the library as a part of another project (e.g. you are working on Quaternion -but need to do some changes to the library code), it makes sense -to make a recursive check out of that project (in this case, Quaternion) -and update the library submodule (also recursively) within the appropriate -branch. Be mindful of API compatibility restrictions: e.g., Quaternion 0.0.95 -will not build with the `dev` branch of libQuotient, only with `0.6.x` branch. - -Tags consisting solely of digits and fullstops (e.g., `0.7.0`) correspond to -released versions; tags ending with `-betaN` or `-rcN` mark respective -pre-releases. If/when packaging pre-releases, it is advised to replace -the leading `-` with `~` (tilde). - -libQuotient is a classic CMake-based project; assuming the dependencies are -in place, the following commands issued in the root directory of the project -sources: +[The source code is at GitHub](https://github.com/quotient-im/libQuotient). Checking out a certain +commit or tag (rather than downloading the archive) along with submodules is strongly recommended. +If you want to hack on the library as a part of another project (e.g. you are working on Quaternion +but need to do some changes to the library code), it makes sense to make a recursive check out of +that project (in this case, Quaternion) and update the library submodule (also recursively) within +the appropriate branch. Be mindful of API compatibility restrictions: e.g., each version of +Quaternion may require the specific branch of libQuotient (`0.8.x`, `0.9.x` etc.). + +Tags consisting solely of digits and fullstops (e.g., `0.7.0`) correspond to released versions; tags +ending with `-betaN` or `-rcN` mark respective pre-releases. If/when packaging pre-releases, it is +advised to replace the leading `-` with `~` (tilde). + +libQuotient is a classic CMake-based project; assuming the dependencies are in place, the following +commands issued in the root directory of the project sources: + ```shell script cmake -B build -S . # [-D=...], see below cmake --build build --target all ``` -will get you a compiled library in `build` (make sure it exists before running). -Any C++ IDE that works with CMake should be able to do the same with minimal -configuration effort. - -Static builds are tested on all supported platforms. Dynamic libraries are -the recommended configuratiion on Linux; likely workable on macOS; and untested -on Windows (you're welcome to try and report on the results). - -Before proceeding, double-check that you have installed development libraries -for all prerequisites above. CMake will stop and tell you if something's missing. - -The first CMake invocation above configures the build. You can pass CMake -variables (such as `-DCMAKE_PREFIX_PATH="path1;path2;..."` and -`-DCMAKE_INSTALL_PREFIX=path`) to that invocation if needed. -[CMake documentation](https://cmake.org/cmake/help/latest/index.html) -(pick the CMake version at the top of the page that you use) describes -the standard variables coming with CMake. On top of them, Quotient understands: -- `Quotient_INSTALL_TESTS=`, `ON` by default - install `quotest` along - with the library files when `install` target is invoked. `quotest` is a small - command-line program that (assuming correct parameters, see `quotest --help`) - that tries to connect to a given room as a given user and perform some basic - Matrix operations, such as sending messages and small files, redaction, - setting room tags etc. This is useful to check the sanity of your library - installation. -- `MATRIX_SPEC_PATH` and `GTAD_PATH` - these two variables are used to point - CMake to the directory with the matrix-doc repository containing API files - and to a GTAD binary. These two are used to generate C++ files from Matrix - Client-Server API description made in OpenAPI notation. This is not needed - if you just need to build the library; if you're really into hacking on it, - please read the respective sections in [CONTRIBUTING.md](./CONTRIBUTING.md) + +will get you a compiled library in `build` (make sure it exists before running). Any C++ IDE that +works with CMake should be able to do the same with minimal configuration effort. + +Static builds are tested on all supported platforms. Dynamic libraries are the recommended +configuratiion on Linux; likely workable on macOS; and untested on Windows (you're welcome to try +and report on the results). + +Before proceeding, double-check that you have installed development libraries for all prerequisites +above. CMake will stop and tell you if something's missing. + +The first CMake invocation above configures the build. You can pass CMake variables (such as +`-DCMAKE_PREFIX_PATH="path1;path2;..."` and `-DCMAKE_INSTALL_PREFIX=path`) to that invocation +if needed. [CMake documentation](https://cmake.org/cmake/help/latest/index.html) (pick the CMake +version at the top of the page that you use) describes the standard variables coming with CMake. +On top of them, Quotient understands: + +- `Quotient_INSTALL_TESTS=`, `ON` by default - install `quotest` along with the library + files when `install` target is invoked. `quotest` is a small command-line program that (assuming + correct parameters, see `quotest --help`) that tries to connect to a given room as a given user + and perform some basic Matrix operations, such as sending messages and small files, redaction, + setting room tags etc. This is useful to check the sanity of your library installation. +- `MATRIX_SPEC_PATH` and `GTAD_PATH` - these two variables are used to point CMake to the directory + with the matrix-doc repository containing API files and to a GTAD binary. These two are used to + generate C++ files from Matrix Client-Server API description made in OpenAPI notation. This is + not needed if you just need to build the library; if you're really into hacking on it, please read + the respective sections in [CONTRIBUTING.md](./CONTRIBUTING.md) and [CODE_GENERATION.md](./CODE_GENERATION.md). -- `QUOTIENT_FORCE_NAMESPACED_INCLUDES=`, `OFF` by default (note that - QUOTIENT is in caps here, unlike options above) - when this option is set to - `ON`, CMake skips adding `/Quotient/` to include - paths, thereby forcing the client code to use `#include ` - instead of historically accepted `#include `. By default this is set - to `OFF` for backwards compatibility; eventually this default may/will change - so it is recommended to at least occasionally add - `-DQUOTIENT_FORCE_NAMESPACED_INCLUDES=1` to a CMake invocation (or set - the variable in your IDE) and make sure your code has prefixed paths. +- `QUOTIENT_FORCE_NAMESPACED_INCLUDES=`, `OFF` by default (note that QUOTIENT is in caps + here, unlike options above) - when this option is set to `ON`, CMake skips adding + `/Quotient/` to include paths, thereby forcing the client code to use + `#include ` instead of historically accepted `#include `. By default + this is set to `OFF` for backwards compatibility; eventually this default may/will change so it is + recommended to at least occasionally add `-DQUOTIENT_FORCE_NAMESPACED_INCLUDES=1` to a CMake + invocation (or set the variable in your IDE) and make sure your code has prefixed paths. You can install the library with CMake: ```shell script cmake --build . --target install ``` -This will also install cmake package config files; once this is done, you -should be able to use [`quotest/CMakeLists.txt`](quotest/CMakeLists.txt) -to compile quotest with the _installed_ library. As mentioned above, -installation of the `quotest` binary along with the rest of the library can be -skipped by setting `Quotient_INSTALL_TESTS` to `OFF`. +This will also install cmake package config files; once this is done, you should be able to use +[`quotest/CMakeLists.txt`](quotest/CMakeLists.txt) to compile quotest with the _installed_ library. +As mentioned above, installation of the `quotest` binary along with the rest of the library can be +skipped by setting `Quotient_INSTALL_TESTS` to `OFF`. ## Troubleshooting @@ -249,53 +231,48 @@ skipped by setting `Quotient_INSTALL_TESTS` to `OFF`. Targets not yet defined: Qt::CorePrivate ``` - then you likely have both Qt 5 and Qt 6 on your system, and your code uses - a different major version of Qt than Quotient. Make sure you configure the - build so that the same major Qt version is used both by libQuotient and - your code. + then you likely have both Qt 5 and Qt 6 on your system, and your code uses a different major + version of Qt than Quotient. Make sure you configure the build so that the same major Qt version + is used both by libQuotient and your code. #### Logging configuration -libQuotient uses Qt's logging categories to make switching certain types of -logging easier. In case of troubles at runtime (bugs, crashes) you can increase -logging if you add the following to the `QT_LOGGING_RULES` environment variable: +libQuotient uses Qt's logging categories to make switching certain types of logging easier. In case +of troubles at runtime (bugs, crashes) you can increase logging if you add the following to +the `QT_LOGGING_RULES` environment variable: + ``` quotient..= ``` + where -- `` is one of: `main`, `jobs`, `jobs.sync`, `jobs.thumbnail`, - `events`, `events.state` (covering both the "usual" room state and account - data), `events.members`, `events.messages`, `events.ephemeral`, `database`, - `network`, `e2ee` and `profiler` - you can always find the full list in - `Quotient/logging_categories_p.h`; + +- `` is one of: `main`, `jobs`, `jobs.sync`, `jobs.thumbnail`, `events`, `events.state` + (covering both the "usual" room state and account data), `events.members`, `events.messages`, + `events.ephemeral`, `database`, `network`, `e2ee` and `profiler` - you can always find the full + list in `Quotient/logging_categories_p.h`; - `` is one of `debug`, `info`, and `warning`; - `` is either `true` or `false`. -You can use `*` (asterisk) as a wildcard for any part between two dots, and -semicolon is used for a separator. Latter statements override former ones, so -if you want to switch on all debug logs except `jobs` you can set -```shell script -QT_LOGGING_RULES="quotient.*.debug=true;quotient.jobs.debug=false" -``` +You can use `*` (asterisk) as a wildcard for any part between two dots, and semicolon is used for a +separator. Latter statements override former ones, so if you want to switch on all debug logs except +`jobs` you can set `QT_LOGGING_RULES="quotient.*.debug=true;quotient.jobs.debug=false"` -(Thanks to [@eang:matrix.org](https://matrix.to/#/@eang:matrix.org]) for -contributing the original libQuotient code for logging categories.) +You may also want to set `QT_MESSAGE_PATTERN` to make logs slightly more informative +(see https://doc.qt.io/qt-6/qtlogging.html#qSetMessagePattern for the format description). To give +an example, here's what one of the library developers uses for `QT_MESSAGE_PATTERN`: -You may also want to set `QT_MESSAGE_PATTERN` to make logs slightly more -informative (see https://doc.qt.io/qt-6/qtlogging.html#qSetMessagePattern -for the format description). To give an example, here's what one of the library -developers uses for `QT_MESSAGE_PATTERN`: ``` `%{time h:mm:ss.zzz}|%{category}|%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}|%{message}` ``` + (the scary `%{if}`s are just encoding the logging level into its initial letter). #### Cache format -In case of troubles with room state and caching it may be useful to switch -cache format from binary CBOR to plaintext JSON. To do that, set -`libQuotient/cache_type` key in your client's configuration file/registry -to `json` (you might need to create the libQuotient group as it's the only -recognised key in it so far). This will make cache saving and loading work -slightly slower but the cache will be in text JSON files (very long and with -no indentation; prepare a good JSON viewer or text editor with JSON -formatting capabilities). + +In case of troubles with room state and caching it may be useful to switch cache format from binary +CBOR to plaintext JSON. To do that, set `libQuotient/cache_type` key in your client's configuration +file/registry to `json` (you might need to create the libQuotient group as it's the only recognised +key in it so far). This will make cache saving and loading work slightly slower but the cache will +be in text JSON files (very long and with no indentation; prepare a good JSON viewer or text editor +with JSON formatting capabilities). diff --git a/SECURITY.md b/SECURITY.md index 5803e9a5a..421ffb4ef 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -5,8 +5,8 @@ | Version | Supported | | ------- | ----------------------------- | | dev | :white_check_mark: (unstable) | +| 0.9.x | :white_check_mark: | | 0.8.x | :white_check_mark: | -| 0.7.x | :white_check_mark: | | older | :x: | ## Reporting a Vulnerability diff --git a/gtad/gtad b/gtad/gtad index 081200067..4d62000b3 160000 --- a/gtad/gtad +++ b/gtad/gtad @@ -1 +1 @@ -Subproject commit 08120006796f62db1441ab0eb08c7b260d698a23 +Subproject commit 4d62000b3db86a8eba15a107d915cb477060b85b diff --git a/gtad/gtad.yaml b/gtad/gtad.yaml index 62fcba040..9ab0a7215 100644 --- a/gtad/gtad.yaml +++ b/gtad/gtad.yaml @@ -12,11 +12,8 @@ analyzer: default: defaultVersion # getCapabilities/RoomVersionsCapability origin_server_ts: originServerTimestamp # Instead of originServerTs start: begin # Because start() is a method in BaseJob - m.upload.size: uploadSize - m.homeserver: homeserver - m.identity_server: identityServer - m.change_password: changePassword - m.room_versions: roomVersions + /^.*/m\.([a-z].*)$/: '$1' # Strip leading m. from all identifiers + m.3pid_changes: ThirdPartyIdChanges # Special case because there's a digit after m. AuthenticationData/additionalProperties: authInfo /^/(Location|Protocol|User)$/: 'ThirdParty$1' # Change some request body parameter names diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index bb12c9916..cbe71075d 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -201,8 +201,8 @@ TestManager::TestManager(int& argc, char** argv) connect(c, &Connection::connected, this, [this] { if (QUO_ALARM(c->homeserver().isEmpty() || !c->homeserver().isValid()) || QUO_ALARM(c->domain() != c->userId().section(u':', 1))) { - clog << "Connection information doesn't look right, check the parameters passed to " - "quotest\n"; + clog << "Connection information doesn't look right, " + << "check the parameters passed to quotest" << endl; QCoreApplication::exit(-2); return; } @@ -216,7 +216,7 @@ TestManager::TestManager(int& argc, char** argv) // data is initialised asynchronously if (QUO_ALARM(newC->homeserver() != c->homeserver()) || QUO_ALARM(newC->userId() != c->userId()) || QUO_ALARM(!newC->isLoggedIn())) { - clog << "Connection::assumeIdentity() failed to do its job\n"; + clog << "Connection::assumeIdentity() is broken" << endl; QCoreApplication::exit(-2); return; } @@ -227,7 +227,7 @@ TestManager::TestManager(int& argc, char** argv) }); connect(c, &Connection::resolveError, this, [](const QString& error) { - clog << "Failed to resolve the server: " << error.toStdString() << endl; + clog << "Could not start testing: " << error.toStdString() << endl; QCoreApplication::exit(-2); }, Qt::QueuedConnection);