Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F140071
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
15 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/base/event.cpp b/src/base/event.cpp
index c8a9d92..0634ccb 100644
--- a/src/base/event.cpp
+++ b/src/base/event.cpp
@@ -1,126 +1,173 @@
/*
* This file is part of libkazv.
- * SPDX-FileCopyrightText: 2020 Tusooa Zhu
+ * SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "libkazv-config.hpp"
+#include <optional>
#include "event.hpp"
+#include "json-utils.hpp"
namespace Kazv
{
const JsonWrap Event::notYetDecryptedEvent = {
json{{"type", "m.room.message"},
{"content", {
{"msgtype", "xyz.tusooa.kazv.not.yet.decrypted"},
{"body", "**This message has not yet been decrypted.**"}}}}};
Event::Event() : m_json(json::object()) {}
Event::Event(JsonWrap j)
: m_json(j) {
using namespace std::string_literals;
if (m_json.get().contains("type"s)) {
auto type = m_json.get().at("type"s).get<std::string>();
if (type == "m.room.encrypted"s) {
m_encrypted = true;
m_decryptedJson = notYetDecryptedEvent;
}
}
}
Event Event::fromSync(Event e, std::string roomId) {
auto j = e.originalJson().get();
j["room_id"] = roomId;
return Event(j);
}
std::string Event::id() const {
// the decrypted json does not have an event id
return originalJson().get().contains("event_id")
? originalJson().get().at("event_id")
: "";
}
std::string Event::sender() const {
return originalJson().get().contains("sender")
? originalJson().get().at("sender")
: "";
}
Timestamp Event::originServerTs() const {
return (originalJson().get().contains("origin_server_ts")
&& originalJson().get().at("origin_server_ts").is_number())
? originalJson().get().at("origin_server_ts").template get<Timestamp>()
: 0;
}
std::string Event::type() const {
return raw().get().contains("type")
? raw().get().at("type")
: "";
}
JsonWrap Event::content() const {
return raw().get().contains("content")
? raw().get().at("content")
: json::object();
}
std::string Event::stateKey() const {
return raw().get().contains("state_key")
? raw().get().at("state_key")
: "";
}
bool Event::isState() const
{
return raw().get().contains("state_key");
}
/// returns the decrypted json
JsonWrap Event::raw() const {
return m_encrypted ? decryptedJson() : m_json;
}
/// returns the original json we fetched, probably encrypted.
JsonWrap Event::originalJson() const {
return m_json;
}
JsonWrap Event::decryptedJson() const {
return m_decryptedJson;
}
bool Event::encrypted() const {
return m_encrypted;
}
bool Event::decrypted() const {
return m_decrypted == Decrypted;
}
Event Event::setDecryptedJson(JsonWrap decryptedJson, DecryptionStatus decrypted) const {
Event e(*this);
e.m_decryptedJson = decryptedJson;
e.m_decrypted = decrypted;
return e;
}
bool Event::redacted() const
{
auto ptr = nlohmann::json_pointer<std::string>("/unsigned/redacted_because");
return originalJson().get().contains(ptr)
&& originalJson().get()[ptr].is_object();
}
+ std::string Event::replyingTo() const
+ {
+ auto [relType, eventId] = relationship();
+ if (relType == "m.in_reply_to") {
+ return eventId;
+ }
+ return "";
+ }
+
+ static std::optional<std::pair<std::string, std::string>> getRel(const json &eventJson)
+ {
+ if (!hasAtThat(eventJson, "content", &json::is_object)) {
+ return std::nullopt;
+ }
+
+ auto content = eventJson["content"];
+ if (hasAtThat(content, "m.relates_to", &json::is_object)) {
+ if (hasAtThat(content["m.relates_to"], "event_id", &json::is_string)
+ && hasAtThat(content["m.relates_to"], "rel_type", &json::is_string)) {
+ return std::make_pair(
+ content["m.relates_to"]["rel_type"].template get<std::string>(),
+ content["m.relates_to"]["event_id"].template get<std::string>()
+ );
+ } else if (hasAtThat(content["m.relates_to"], "m.in_reply_to", &json::is_object)
+ && hasAtThat(content["m.relates_to"]["m.in_reply_to"], "event_id", &json::is_string)) {
+ return std::make_pair(
+ std::string("m.in_reply_to"),
+ content["m.relates_to"]["m.in_reply_to"]["event_id"].template get<std::string>()
+ );
+ }
+ }
+ return std::nullopt;
+ }
+
+ std::pair<std::string/* relType */, std::string/* eventId */> Event::relationship() const
+ {
+ std::optional<std::pair<std::string/* relType */, std::string/* eventId */>> relOpt;
+ if ((relOpt = getRel(originalJson().get())).has_value()) {
+ return relOpt.value();
+ } else if (encrypted() && (relOpt = getRel(raw().get())).has_value()) {
+ return relOpt.value();
+ }
+ return {"", ""};
+ }
+
bool operator==(Event a, Event b)
{
return a.id() == b.id()
&& a.originalJson() == b.originalJson()
&& a.raw() == b.raw();
}
}
diff --git a/src/base/event.hpp b/src/base/event.hpp
index e5b728e..7cf52d2 100644
--- a/src/base/event.hpp
+++ b/src/base/event.hpp
@@ -1,105 +1,121 @@
/*
* This file is part of libkazv.
- * SPDX-FileCopyrightText: 2020-2021 Tusooa Zhu <tusooa@kazv.moe>
+ * SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include "libkazv-config.hpp"
#include <string>
#include <cstdint>
#include "jsonwrap.hpp"
namespace Kazv
{
using Timestamp = std::int_fast64_t;
class Event
{
public:
static const JsonWrap notYetDecryptedEvent;
enum DecryptionStatus {
NotDecrypted,
Decrypted
};
Event();
Event(JsonWrap j);
static Event fromSync(Event e, std::string roomId);
/// returns the id of this event
std::string id() const;
std::string sender() const;
Timestamp originServerTs() const;
std::string type() const;
std::string stateKey() const;
/**
* @return whether this event is a state event.
* An event is considered a state event if and only if
* it has a maybe empty stateKey.
*/
bool isState() const;
JsonWrap content() const;
/// returns the decrypted json
JsonWrap raw() const;
/// returns the original json we fetched, probably encrypted.
JsonWrap originalJson() const;
JsonWrap decryptedJson() const;
bool encrypted() const;
bool decrypted() const;
/// internal. only to be called from inside the client.
Event setDecryptedJson(JsonWrap decryptedJson, DecryptionStatus decrypted) const;
/// returns whether this event has been redacted.
bool redacted() const;
+ /**
+ * Get the event id this event is replying to.
+ *
+ * @return The event id this event is replying to, or empty string if this
+ * event is not a reply.
+ */
+ std::string replyingTo() const;
+
+ /**
+ * Get the relationship that this event contains.
+ *
+ * @return A Pair containing the rel_type and event_id in the m.relates_to section
+ * of this event, or a Pair of empty strings if there are not any.
+ */
+ std::pair<std::string/* relType */, std::string/* eventId */> relationship() const;
+
template<class Archive>
void serialize(Archive &ar, std::uint32_t const /*version*/ ) {
ar & m_json & m_decryptedJson & m_decrypted & m_encrypted;
}
private:
JsonWrap m_json;
JsonWrap m_decryptedJson;
DecryptionStatus m_decrypted{NotDecrypted};
bool m_encrypted{false};
};
bool operator==(Event a, Event b);
inline bool operator!=(Event a, Event b) { return !(a == b); };
}
BOOST_CLASS_VERSION(Kazv::Event, 0)
namespace nlohmann
{
template <>
struct adl_serializer<Kazv::Event> {
static void to_json(json& j, Kazv::Event w) {
j = w.originalJson();
}
static void from_json(const json& j, Kazv::Event &w) {
w = Kazv::Event(Kazv::JsonWrap(j));
}
};
}
diff --git a/src/base/json-utils.hpp b/src/base/json-utils.hpp
new file mode 100644
index 0000000..2501006
--- /dev/null
+++ b/src/base/json-utils.hpp
@@ -0,0 +1,20 @@
+/*
+ * This file is part of libkazv.
+ * SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include "libkazv-config.hpp"
+
+#include <functional>
+#include <nlohmann/json.hpp>
+
+namespace Kazv
+{
+ template<class Jsonish, class Key, class Func>
+ bool hasAtThat(Jsonish &&j, Key &&k, Func &&f)
+ {
+ return j.contains(k)
+ && std::invoke(std::forward<Func>(f), std::forward<Jsonish>(j)[std::forward<Key>(k)]);
+ }
+}
diff --git a/src/tests/event-test.cpp b/src/tests/event-test.cpp
index 84416e6..1eb9ce8 100644
--- a/src/tests/event-test.cpp
+++ b/src/tests/event-test.cpp
@@ -1,55 +1,162 @@
/*
* This file is part of libkazv.
- * SPDX-FileCopyrightText: 2021 Tusooa Zhu <tusooa@kazv.moe>
+ * SPDX-FileCopyrightText: 2021-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <catch2/catch_all.hpp>
#include <event.hpp>
using namespace Kazv;
TEST_CASE("Should distinguish between state events and other events", "[base][event]")
{
Event state = R"({
"state_key": "",
"sender": "@example:example.org",
"type": "moe.kazv.mxc.custom.state.type",
"event_id": "!dummy",
"origin_server_ts": 1234,
"content": {}
})"_json;
REQUIRE(state.isState());
Event nonState = R"({
"sender": "@example:example.org",
"type": "moe.kazv.mxc.custom.non.state.type",
"event_id": "!dummy",
"origin_server_ts": 1234,
"content": {}
})"_json;
REQUIRE(!nonState.isState());
}
TEST_CASE("redacted()", "[base][event]")
{
auto eventJson = R"({
"state_key": "",
"sender": "@example:example.org",
"type": "moe.kazv.mxc.custom.state.type",
"event_id": "!dummy",
"origin_server_ts": 1234,
"content": {}
})"_json;
REQUIRE(!Event(eventJson).redacted());
eventJson["unsigned"] = json::object({{"redacted_because", json::object()}});
REQUIRE(Event(eventJson).redacted());
}
+
+TEST_CASE("relationship()", "[base][event]")
+{
+ auto eventJson = json{
+ {"sender", "@example:example.org"},
+ {"type", "moe.kazv.mxc.custom.state.type"},
+ {"event_id", "$dummy"},
+ {"origin_server_ts", 1234},
+ {"content", {
+ {"m.relates_to", {
+ {"rel_type", "moe.kazv.mxc.some-type"},
+ {"event_id", "$another"},
+ },
+ }}},
+ };
+
+ WHEN("m.relates_to is a normal relation") {
+ REQUIRE(Event(eventJson).relationship() == std::pair<std::string, std::string>{"moe.kazv.mxc.some-type", "$another"});
+ REQUIRE(Event(eventJson).replyingTo() == "");
+ }
+
+ WHEN("m.relates_to is a reply") {
+ eventJson["content"]["m.relates_to"] = {
+ {"m.in_reply_to", {
+ {"event_id", "$another"}
+ }},
+ };
+ REQUIRE(Event(eventJson).relationship() == std::pair<std::string, std::string>{"m.in_reply_to", "$another"});
+ REQUIRE(Event(eventJson).replyingTo() == "$another");
+ }
+
+ WHEN("m.relates_to is missing rel_type") {
+ eventJson["content"]["m.relates_to"].erase("rel_type");
+ REQUIRE(Event(eventJson).relationship() == std::pair<std::string, std::string>{"", ""});
+ }
+
+ WHEN("m.relates_to is missing event_id") {
+ eventJson["content"]["m.relates_to"].erase("event_id");
+ REQUIRE(Event(eventJson).relationship() == std::pair<std::string, std::string>{"", ""});
+ }
+
+ WHEN("m.relates_to is not an object") {
+ eventJson["content"]["m.relates_to"] = json::array();
+ REQUIRE(Event(eventJson).relationship() == std::pair<std::string, std::string>{"", ""});
+ }
+
+ WHEN("m.relates_to does not exist") {
+ eventJson["content"].erase("m.relates_to");
+ REQUIRE(Event(eventJson).relationship() == std::pair<std::string, std::string>{"", ""});
+ }
+}
+
+TEST_CASE("relationship(), with encrypted event", "[base][event]")
+{
+ auto eventJsonWithRel = json{
+ {"sender", "@example:example.org"},
+ {"type", "moe.kazv.mxc.custom.state.type"},
+ {"event_id", "$dummy"},
+ {"origin_server_ts", 1234},
+ {"content", {
+ {"m.relates_to", {
+ {"rel_type", "moe.kazv.mxc.some-type"},
+ {"event_id", "$another"},
+ },
+ }}},
+ };
+
+ auto eventJson = eventJsonWithRel;
+ eventJson["content"].erase("m.relates_to");
+ auto plainJsonWithRel = eventJsonWithRel;
+ plainJsonWithRel["type"] = "m.room.encrypted";
+ plainJsonWithRel["content"]["m.relates_to"] = {
+ {"rel_type", "moe.kazv.mxc.some-other-type"},
+ {"event_id", "$another-2"},
+ };
+
+ auto plainJson = plainJsonWithRel;
+ plainJson["content"].erase("m.relates_to");
+
+ WHEN("plaintext with relationship / ciphertext with relationship") {
+ auto event = Event(plainJsonWithRel).setDecryptedJson(eventJsonWithRel, Event::Decrypted);
+ THEN("should use the relationship within plaintext") {
+ REQUIRE(event.relationship() == std::pair<std::string, std::string>{"moe.kazv.mxc.some-other-type", "$another-2"});
+ }
+ }
+
+ WHEN("plaintext without relationship / ciphertext with relationship") {
+ auto event = Event(plainJson).setDecryptedJson(eventJsonWithRel, Event::Decrypted);
+ THEN("should use the relationship within ciphertext") {
+ REQUIRE(event.relationship() == std::pair<std::string, std::string>{"moe.kazv.mxc.some-type", "$another"});
+ }
+ }
+
+ WHEN("plaintext with relationship / ciphertext without relationship") {
+ auto event = Event(plainJsonWithRel).setDecryptedJson(eventJson, Event::Decrypted);
+ THEN("should use the relationship within plaintext") {
+ REQUIRE(event.relationship() == std::pair<std::string, std::string>{"moe.kazv.mxc.some-other-type", "$another-2"});
+ }
+ }
+
+ WHEN("plaintext without relationship / ciphertext without relationship") {
+ auto event = Event(plainJson).setDecryptedJson(eventJson, Event::Decrypted);
+ THEN("should return empty pair") {
+ REQUIRE(event.relationship() == std::pair<std::string, std::string>{"", ""});
+ }
+ }
+}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 11:54 AM (2 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55142
Default Alt Text
(15 KB)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline
Log In to Comment