Page MenuHomePhorge

No OneTemporary

Size
15 KB
Referenced Files
None
Subscribers
None
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

Mime Type
text/x-diff
Expires
Sun, Jan 19, 11:54 AM (4 h, 47 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55142
Default Alt Text
(15 KB)

Event Timeline