Page MenuHomePhorge

No OneTemporary

Size
20 KB
Referenced Files
None
Subscribers
None
diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt
index 1ce2cca..dcce310 100644
--- a/src/client/CMakeLists.txt
+++ b/src/client/CMakeLists.txt
@@ -1,36 +1,37 @@
set(kazvclient_SRCS
sdk-model.cpp
client-model.cpp
client.cpp
actions/auth.cpp
actions/sync.cpp
actions/paginate.cpp
actions/membership.cpp
actions/states.cpp
actions/send.cpp
actions/ephemeral.cpp
actions/content.cpp
actions/encryption.cpp
actions/profile.cpp
device-list-tracker.cpp
encrypted-file.cpp
+ matrix-uri.cpp
room/room-model.cpp
room/room.cpp
)
add_library(kazvclient ${kazvclient_SRCS})
add_library(libkazv::kazvclient ALIAS kazvclient)
set_target_properties(kazvclient PROPERTIES VERSION ${libkazv_VERSION_STRING} SOVERSION ${libkazv_SOVERSION})
target_link_libraries(kazvclient PUBLIC kazvbase kazvapi kazvcrypto kazvstore)
target_include_directories(kazvclient PRIVATE .)
target_include_directories(kazvclient
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include/kazv/client>
)
install(TARGETS kazvclient EXPORT libkazvTargets LIBRARY)
diff --git a/src/client/matrix-uri.cpp b/src/client/matrix-uri.cpp
new file mode 100644
index 0000000..c52d7fe
--- /dev/null
+++ b/src/client/matrix-uri.cpp
@@ -0,0 +1,345 @@
+/*
+ * This file is part of libkazv.
+ * SPDX-FileCopyrightText: 2022 Tusooa Zhu <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libkazv-config.hpp>
+
+#include <boost/algorithm/string/split.hpp>
+
+#include <immer/map.hpp>
+
+#include "matrix-uri.hpp"
+
+namespace Kazv
+{
+ std::string desigilify(std::string id)
+ {
+ if (!id.empty() && (
+ id[0] == '#' || id[0] == '!' || id[0] == '@'
+ )) {
+ id.erase(id.begin());
+ }
+ return id;
+ }
+
+ static const std::string matrixProtocol = "matrix";
+
+ struct MatrixUri::Private
+ {
+ enum Type
+ {
+ Invalid = 0x0,
+ RoomId = 0x1,
+ RoomAlias = 0x2,
+ User = 0x4,
+ Event = 0x8,
+ };
+
+ unsigned int type{Invalid};
+ std::string upperLevel{};
+ std::string lowerLevel{};
+ std::string action{};
+ immer::flex_vector<std::string> routing{};
+ immer::map<std::string, std::string> query{};
+ std::string fragment{};
+ };
+
+ MatrixUri::MatrixUri()
+ : m_d(std::make_unique<Private>())
+ {
+ }
+
+ MatrixUri::MatrixUri(std::unique_ptr<Private> d)
+ : m_d(std::move(d))
+ {
+ }
+
+ MatrixUri MatrixUri::withUser(std::string userId)
+ {
+ return MatrixUri(std::unique_ptr<Private>(new Private{Private::User, std::move(userId)}));
+ }
+
+ MatrixUri MatrixUri::withRoomById(std::string roomId)
+ {
+ return MatrixUri(std::unique_ptr<Private>(new Private{Private::RoomId, std::move(roomId)}));
+ }
+
+ MatrixUri MatrixUri::withRoomByAlias(std::string alias)
+ {
+ return MatrixUri(std::unique_ptr<Private>(new Private{Private::RoomAlias, std::move(alias)}));
+ }
+
+ MatrixUri::~MatrixUri() = default;
+
+ KAZV_DEFINE_COPYABLE_UNIQUE_PTR(MatrixUri, m_d);
+
+ bool MatrixUri::isValid() const
+ {
+ return m_d->type;
+ }
+
+ bool MatrixUri::isRoom() const
+ {
+ return m_d->type == Private::RoomId || m_d->type == Private::RoomAlias;
+ }
+
+ bool MatrixUri::isUser() const
+ {
+ return m_d->type == Private::User;
+ }
+
+ bool MatrixUri::isEvent() const
+ {
+ return m_d->type & Private::Event;
+ }
+
+ std::string MatrixUri::id() const
+ {
+ if (isEvent()) {
+ return m_d->lowerLevel;
+ } else {
+ return m_d->upperLevel;
+ }
+ }
+
+ std::string MatrixUri::userOrRoom() const
+ {
+ return m_d->upperLevel;
+ }
+
+ std::string MatrixUri::action() const
+ {
+ return m_d->action;
+ }
+
+ MatrixUri MatrixUri::withAction(std::string act) const
+ {
+ auto r = *this;
+ r.m_d->action = act;
+ return r;
+ }
+
+ auto MatrixUri::routing() const -> immer::flex_vector<std::string>
+ {
+ return m_d->routing;
+ }
+
+ MatrixUri MatrixUri::withVia(std::string authority) const
+ {
+ auto r = *this;
+ r.m_d->routing = std::move(r.m_d->routing).push_back(authority);
+ return r;
+ }
+
+ std::string MatrixUri::query(std::string key) const
+ {
+ if (key == "via") {
+ return m_d->routing.size() ? m_d->routing.back() : std::string();
+ } else if (key == "action") {
+ return m_d->action;
+ } else {
+ return m_d->query[key];
+ }
+ }
+
+ MatrixUri MatrixUri::withQuery(std::string key, std::string value) const
+ {
+ if (key == "via") {
+ return withVia(std::move(value));
+ } else if (key == "action") {
+ return withAction(std::move(value));
+ } else {
+ auto r = *this;
+ r.m_d->query = std::move(r.m_d->query).set(std::move(key), std::move(value));
+ return r;
+ }
+ }
+
+ std::string MatrixUri::fragment() const
+ {
+ return m_d->fragment;
+ }
+
+ MatrixUri MatrixUri::withFragment(std::string frag) const
+ {
+ auto r = *this;
+ r.m_d->fragment = frag;
+ return r;
+ }
+
+ std::string MatrixUri::toString() const
+ {
+ std::string res = matrixProtocol + ":";
+ bool hasQuery = false;
+
+ auto putUpperLevel = [this, &res]() {
+ if (m_d->type & Private::RoomId) {
+ res += "roomid/";
+ } else if (m_d->type & Private::RoomAlias) {
+ res += "r/";
+ } else if (m_d->type & Private::User) {
+ res += "u/";
+ }
+ res += desigilify(m_d->upperLevel);
+ };
+
+ auto putLowerLevel = [this, &res]() {
+ if (m_d->type & Private::Event) {
+ res += "/e/" + desigilify(m_d->lowerLevel);
+ }
+ };
+
+ auto putQueryPrefix = [this, &res, &hasQuery]() {
+ if (hasQuery) {
+ res += "&";
+ } else {
+ hasQuery = true;
+ res += "?";
+ }
+ };
+
+ auto putAction = [this, &res, putQueryPrefix]() {
+ if (!m_d->action.empty()) {
+ putQueryPrefix();
+ res += "action=" + m_d->action;
+ }
+ };
+
+ auto putRouting = [this, &res, putQueryPrefix]() {
+ for (const auto &value: m_d->routing) {
+ putQueryPrefix();
+ res += "via=" + value;
+ }
+ };
+
+ auto putCustomQuery = [this, &res, putQueryPrefix]() {
+ for (const auto &[key, value]: m_d->query) {
+ putQueryPrefix();
+ res += key + "=" + value;
+ }
+ };
+
+ auto putFragment = [this, &res]() {
+ if (!m_d->fragment.empty()) {
+ res += "#" + m_d->fragment;
+ }
+ };
+
+ putUpperLevel();
+ putLowerLevel();
+ putAction();
+ putRouting();
+ putCustomQuery();
+ putFragment();
+
+ return res;
+ }
+
+ MatrixUri MatrixUri::parse(std::string uri)
+ {
+ // Ref: https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2312-matrix-uri.md#recommended-implementation
+ MatrixUri res;
+ bool valid = true;
+ std::vector<std::string> entities;
+ std::string query;
+ std::string fragment;
+
+ auto verifyProto = [&res, &valid, &uri]() {
+ const auto &protoPart = matrixProtocol + ":";
+ if (uri.substr(0, protoPart.size()) != protoPart) {
+ valid = false;
+ }
+ uri.erase(uri.begin(), uri.begin() + protoPart.size());
+ };
+
+ auto withValid = [valid](auto func) {
+ if (valid) {
+ func();
+ }
+ };
+
+ auto splitParts = [&res, &valid, &uri, &entities, &query, &fragment]() {
+ auto fragPos = uri.find('#');
+ if (fragPos != std::string::npos) {
+ fragment = std::string(uri.begin() + fragPos + 1, uri.end());
+ uri.erase(uri.begin() + fragPos, uri.end());
+ }
+
+ auto queryPos = uri.find('?');
+ if (queryPos != std::string::npos) {
+ query = std::string(uri.begin() + queryPos + 1, uri.end());
+ uri.erase(uri.begin() + queryPos, uri.end());
+ }
+
+ boost::algorithm::split(entities, uri, [](char c) { return c == '/'; });
+ if (entities.size() != 2 && entities.size() != 4) {
+ valid = false;
+ }
+ };
+
+ auto putUpperLevel = [&entities, &res, &valid]() {
+ std::string sigil;
+ if (entities[0] == "room" || entities[0] == "r") {
+ res.m_d->type |= Private::RoomAlias;
+ sigil = "#";
+ } else if (entities[0] == "user" || entities[0] == "u") {
+ res.m_d->type |= Private::User;
+ sigil = "@";
+ } else if (entities[0] == "roomid") {
+ res.m_d->type |= Private::RoomId;
+ sigil = "!";
+ } else {
+ valid = false;
+ }
+
+ res.m_d->upperLevel = sigil + entities[1];
+ entities.erase(entities.begin(), entities.begin() + 2);
+ };
+
+ auto putLowerLevel = [&entities, &res, &valid]() {
+ if (entities.size() < 2) {
+ return;
+ }
+
+ if (entities[0] == "event" || entities[0] == "e") {
+ res.m_d->type |= Private::Event;
+ res.m_d->lowerLevel = "!" + entities[1];
+ } else {
+ valid = false;
+ }
+ };
+
+ auto putQueryParams = [&query, &res]() {
+ std::vector<std::string> queryParams;
+ boost::algorithm::split(queryParams, query, [](char c) { return c == '&'; });
+
+ for (const auto &entry : queryParams) {
+ auto valPos = entry.find('=');
+ auto key = entry.substr(0, valPos);
+ auto value = entry.substr(valPos + 1);
+ res = res.withQuery(key, value);
+ }
+ };
+
+ auto putFragment = [&fragment, &res]() {
+ if (!fragment.empty()) {
+ res = res.withFragment(fragment);
+ }
+ };
+
+ verifyProto();
+ withValid(splitParts);
+ withValid(putUpperLevel);
+ withValid(putLowerLevel);
+ withValid(putQueryParams);
+ withValid(putFragment);
+
+ if (valid) {
+ return res;
+ } else {
+ return MatrixUri();
+ }
+ }
+}
diff --git a/src/client/matrix-uri.hpp b/src/client/matrix-uri.hpp
new file mode 100644
index 0000000..62b6b14
--- /dev/null
+++ b/src/client/matrix-uri.hpp
@@ -0,0 +1,197 @@
+/*
+ * This file is part of libkazv.
+ * SPDX-FileCopyrightText: 2022 Tusooa Zhu <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libkazv-config.hpp>
+
+#include <string>
+
+#include <immer/flex_vector.hpp>
+
+#include <copy-helper.hpp>
+
+namespace Kazv
+{
+ /**
+ * Remove the sigil of an id.
+ *
+ * @param id The id with sigil.
+ * @return id without sigil.
+ */
+ std::string desigilify(std::string id);
+
+ /**
+ * Represents a MatrixUri.
+ *
+ * https://spec.matrix.org/v1.2/appendices/#uris
+ */
+ class MatrixUri
+ {
+ public:
+ /**
+ * Create a matrix uri by parsing a string.
+ *
+ * If uri is not well-formed, it returns an invalid MatrixUri.
+ * @param uri The uri to parse.
+ * @return The MatrixUri parsed.
+ */
+ static MatrixUri parse(std::string uri);
+
+ /**
+ * Create a matrix uri for a room from the alias of it.
+ * @param alias The alias of the room.
+ * @return A MatrixUri representing the room.
+ */
+ static MatrixUri withRoomByAlias(std::string alias);
+
+ /**
+ * Create a matrix uri for a user from the user id.
+ * @param userId The user id of the user.
+ * @rutern A MatrixUri representing the user.
+ */
+ static MatrixUri withUser(std::string userId);
+
+ /**
+ * Create a matrix uri for a room from the id of it.
+ * @param roomId The id of the room.
+ * @return A MatrixUri representing the room.
+ */
+ static MatrixUri withRoomById(std::string roomId);
+
+ /**
+ * Constructs an invalid MatrixUri.
+ */
+ MatrixUri();
+ ~MatrixUri();
+
+ KAZV_DECLARE_COPYABLE(MatrixUri);
+
+ /**
+ * Check whether this is valid.
+ * @return true iff this is valid.
+ */
+ bool isValid() const;
+
+ /**
+ * Check whether this represents a room.
+ * @return true iff this represents a room.
+ */
+ bool isRoom() const;
+
+ /**
+ * Check whether this represents a user.
+ * @return true iff this represents a user.
+ */
+ bool isUser() const;
+
+ /**
+ * Check whether this represents an event.
+ * @return true iff this represents an event.
+ */
+ bool isEvent() const;
+
+ /**
+ * Get the lowest-level id of this.
+ *
+ * @return If this represents an event, the event id.
+ * If this represents a user or room, the user id, room id, or room alias.
+ */
+ std::string id() const;
+
+ /**
+ * Get the user id, room id or room alias of this.
+ * @return If this represents a user, the user id.
+ * If this represents a room, the room id or alias.
+ */
+ std::string userOrRoom() const;
+
+ /**
+ * Get the action for this MatrixUri.
+ * @return The action. Or empty string if there is no action.
+ */
+ std::string action() const;
+
+ /**
+ * Create a MatrixUri from this with action `act`.
+ * @param act The action to use.
+ * @return The resulting MatrixUri.
+ */
+ MatrixUri withAction(std::string act) const;
+
+ /**
+ * Get the authorities specified in the MatrixUri.
+ * @return A RangeT of std::string containing the authorities,
+ * or an empty one if there are none.
+ */
+ auto routing() const -> immer::flex_vector<std::string>;
+
+ /**
+ * Create a MatrixUri from this via `authority`.
+ * @param authority The server to use to discover the entity.
+ * @return The resulting MatrixUri.
+ */
+ MatrixUri withVia(std::string authority) const;
+
+ /**
+ * Create a MatrixUri from this via `authorities`.
+ * @param authorities A RangeT of servers to use to discover the entity.
+ * @return The resulting MatrixUri.
+ */
+ template<class RangeT, class = std::enable_if_t<!std::is_convertible_v<RangeT, std::string>>>
+ MatrixUri withVia(RangeT authorities) const
+ {
+ return std::accumulate(authorities.begin(), authorities.end(), *this,
+ [](MatrixUri acc, std::string next) {
+ return acc.withVia(std::move(next));
+ });
+ }
+
+ /**
+ * Get a query param of this.
+ * @param key The key to obtain.
+ * @return The value corresponding to key, or empty string if
+ * the key does not exist.
+ * If `key` is `"via"`, it returns the last value in the list.
+ */
+ std::string query(std::string key) const;
+
+ /**
+ * Create a MatrixUri from this with a query parameter.
+ * If `key` is `"via"`, it appends value to the routing list.
+ * @param key The key of the query.
+ * @param value The value of the query.
+ * @return The resulting MatrixUri.
+ */
+ MatrixUri withQuery(std::string key, std::string value) const;
+
+ /**
+ * Get the string representation of this.
+ * @return A formatted matrix uri.
+ */
+ std::string toString() const;
+
+ /**
+ * Get the fragment of this.
+ * @return The fragment.
+ */
+ std::string fragment() const;
+
+ /**
+ * Create a MatrixUri from this but with the fragment part replaced with `fragment`.
+ *
+ * @param fragment The fragment part. It does not contain `#`.
+ * @return The resulting MatrixUri.
+ */
+ MatrixUri withFragment(std::string fragment) const;
+
+ private:
+ struct Private;
+ std::unique_ptr<Private> m_d;
+
+ MatrixUri(std::unique_ptr<Private> d);
+ };
+}
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index c18dbe9..e70b5f0 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,55 +1,56 @@
include(CTest)
set(KAZVTEST_RESPATH ${CMAKE_CURRENT_SOURCE_DIR}/resources)
configure_file(kazvtest-respath.hpp.in kazvtest-respath.hpp)
add_executable(kazvtest
testmain.cpp
basejobtest.cpp
event-test.cpp
cursorutiltest.cpp
base/serialization-test.cpp
base/types-test.cpp
client/client-test-util.cpp
client/discovery-test.cpp
client/sync-test.cpp
client/content-test.cpp
client/paginate-test.cpp
client/util-test.cpp
client/serialization-test.cpp
client/encrypted-file-test.cpp
client/sdk-test.cpp
client/thread-safety-test.cpp
client/room-test.cpp
client/random-generator-test.cpp
client/profile-test.cpp
client/kick-test.cpp
client/ban-test.cpp
client/keys-test.cpp
+ client/matrix-uri-test.cpp
client/room/room-actions-test.cpp
kazvjobtest.cpp
event-emitter-test.cpp
crypto-test.cpp
crypto/deterministic-test.cpp
promise-test.cpp
store-test.cpp
file-desc-test.cpp
)
target_include_directories(
kazvtest
PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(kazvtest
PRIVATE Catch2::Catch2
PRIVATE kazv
PRIVATE kazveventemitter
PRIVATE kazvjob
PRIVATE nlohmann_json::nlohmann_json
PRIVATE immer
PRIVATE lager
PRIVATE zug)
diff --git a/src/tests/client/matrix-uri-test.cpp b/src/tests/client/matrix-uri-test.cpp
new file mode 100644
index 0000000..d09b167
--- /dev/null
+++ b/src/tests/client/matrix-uri-test.cpp
@@ -0,0 +1,62 @@
+/*
+ * This file is part of libkazv.
+ * SPDX-FileCopyrightText: 2022 Tusooa Zhu <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libkazv-config.hpp>
+
+#include <catch2/catch.hpp>
+
+#include <matrix-uri.hpp>
+
+using namespace Kazv;
+
+TEST_CASE("MatrixUri::parse()", "[client][matrix-uri]")
+{
+ SECTION("room by alias")
+ {
+ auto uri = MatrixUri::parse("matrix:r/somewhere:example.org");
+ REQUIRE(uri.isValid());
+ REQUIRE(uri.isRoom());
+ REQUIRE(uri.id() == "#somewhere:example.org");
+ }
+
+ SECTION("room by id")
+ {
+ auto uri = MatrixUri::parse("matrix:roomid/somewhere:example.org?via=elsewhere.ca");
+ REQUIRE(uri.isValid());
+ REQUIRE(uri.isRoom());
+ REQUIRE(uri.id() == "!somewhere:example.org");
+ REQUIRE(uri.routing() == immer::flex_vector<std::string>{"elsewhere.ca"});
+ }
+
+ SECTION("room by alias with event")
+ {
+ auto uri = MatrixUri::parse("matrix:r/somewhere:example.org/e/event");
+ REQUIRE(uri.isValid());
+ REQUIRE(uri.isEvent());
+ REQUIRE(uri.userOrRoom() == "#somewhere:example.org");
+ REQUIRE(uri.id() == "!event");
+ }
+
+ SECTION("room by id with event")
+ {
+ auto uri = MatrixUri::parse("matrix:roomid/somewhere:example.org/e/event?via=elsewhere.ca");
+ REQUIRE(uri.isValid());
+ REQUIRE(uri.isEvent());
+ REQUIRE(uri.userOrRoom() == "!somewhere:example.org");
+ REQUIRE(uri.id() == "!event");
+ REQUIRE(uri.routing() == immer::flex_vector<std::string>{"elsewhere.ca"});
+ }
+
+ SECTION("user")
+ {
+ auto uri = MatrixUri::parse("matrix:u/alice:example.org?action=chat");
+ REQUIRE(uri.isValid());
+ REQUIRE(uri.isUser());
+ REQUIRE(uri.userOrRoom() == "@alice:example.org");
+ REQUIRE(uri.id() == "@alice:example.org");
+ REQUIRE(uri.action() == "chat");
+ }
+}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Oct 11, 10:14 AM (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1785723
Default Alt Text
(20 KB)

Event Timeline