Page MenuHomePhorge

No OneTemporary

Size
30 KB
Referenced Files
None
Subscribers
None
diff --git a/src/client/push-rules-desc-p.hpp b/src/client/push-rules-desc-p.hpp
index 338b6f0..436545b 100644
--- a/src/client/push-rules-desc-p.hpp
+++ b/src/client/push-rules-desc-p.hpp
@@ -1,38 +1,40 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <libkazv-config.hpp>
#include <optional>
#include <event.hpp>
#include "push-rules-desc.hpp"
namespace Kazv
{
std::vector<std::string> splitPath(std::string path);
bool matchGlob(std::string target, std::string pattern);
std::optional<json> validateRule(std::string ruleSetName, const json &rule);
Event validatePushRules(const Event &e);
struct PushRulesDescPrivate
{
+ PushRulesDescPrivate(const Event &e);
+
Event pushRulesEvent;
std::optional<std::pair<std::string, json>> matchRule(const Event &e, const RoomModel &room) const;
std::optional<std::pair<std::string, json>> matchRuleSet(std::string ruleSetName, const Event &e, const RoomModel &room) const;
bool matchP(std::string ruleSetName, const json &rule, const Event &e, const RoomModel &room) const;
PushAction handleRule(std::string ruleSetName, const json &rule, const Event &e, const RoomModel &room) const;
};
}
diff --git a/src/client/push-rules-desc.cpp b/src/client/push-rules-desc.cpp
index 4990391..b1b7fc5 100644
--- a/src/client/push-rules-desc.cpp
+++ b/src/client/push-rules-desc.cpp
@@ -1,300 +1,304 @@
/*
* 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 <boost/algorithm/string/regex.hpp>
#include <immer/array.hpp>
#include <event.hpp>
#include <json-utils.hpp>
#include <room/room-model.hpp>
#include "push-rules-desc-p.hpp"
namespace Kazv
{
static immer::array<std::string> ruleSets = {
"override",
"content",
"room",
"sender",
"underride",
};
// https://spec.matrix.org/v1.8/appendices/#dot-separated-property-paths
static const auto splitRegex = boost::regex("(?<!\\\\)\\.");
static const auto unescapeRegex = boost::regex("\\\\(\\\\|\\.)");
std::vector<std::string> splitPath(std::string path)
{
std::vector<std::string> ret;
boost::algorithm::split_regex(ret, path, splitRegex);
for (auto &part : ret) {
part = boost::regex_replace(part, unescapeRegex, "$1");
}
return ret;
}
static const auto metaCharsRegex = boost::regex("([*])|([?])|([.^$|()\\[\\]{}+\\\\])");
bool matchGlob(std::string target, std::string pattern)
{
auto matchRegex = boost::regex_replace(pattern, metaCharsRegex, "(?1.*:)(?2.:)(?3\\\\$3:)", boost::regex_constants::format_all);
return boost::regex_match(target, boost::regex(matchRegex, boost::regex::icase));
}
template<class Jsonish, class Key, class Validator>
static bool cast(Jsonish &ret, const Jsonish &input, Key &&k, Validator &&f)
{
if (!input.contains(k)) {
return false;
}
auto [res, output] = std::invoke(std::forward<Validator>(f), input[k]);
if (!res) {
return false;
}
ret[k] = output;
return true;
}
template<class Func>
static auto identValidate(Func &&f)
{
return [f=std::forward<Func>(f)](auto &&val) mutable {
auto res = std::invoke(std::forward<Func>(f), val);
return std::pair<bool, json>(res, std::forward<decltype(val)>(val));
};
}
static bool isConditionKind(const json &kind)
{
return kind == "event_match"
|| kind == "event_property_is"
|| kind == "event_property_contains";
}
static std::pair<bool, json> validateConditions(const json &conditions)
{
json ret = json::array();
if (!conditions.is_array()) {
return {false, ret};
}
for (const auto &cond : conditions) {
json validatedCond = json::object();
if (!(
cond.is_object()
&& cast(validatedCond, cond, "kind", identValidate(&isConditionKind))
)) {
return {false, ret};
}
if (validatedCond["kind"] == "event_match") {
if (!(
cast(validatedCond, cond, "key", identValidate(&json::is_string))
&& cast(validatedCond, cond, "pattern", identValidate(&json::is_string))
)) {
return {false, ret};
}
} else if (validatedCond["kind"] == "event_property_is" || validatedCond["kind"] == "event_property_contains") {
if (!(
cast(validatedCond, cond, "key", identValidate(&json::is_string))
&& cast(validatedCond, cond, "value", identValidate(&isNonCompoundCanonicalJsonValue))
)) {
return {false, ret};
}
} else {
return {false, ret};
}
ret.push_back(validatedCond);
}
return {true, ret};
}
static std::pair<bool, json> validateActions(const json &actions)
{
json ret = json::array();
if (!actions.is_array()) {
return {false, ret};
}
for (const auto &act : actions) {
if (act == "notify") {
ret.push_back(act);
} else if (act.is_object()) {
json validatedAct = json::object();
if (cast(validatedAct, act, "set_tweak", identValidate([](const auto &t) { return t == "sound"; }))) {
cast(validatedAct, act, "value", identValidate(&json::is_string));
} else if (cast(validatedAct, act, "set_tweak", identValidate([](const auto &t) { return t == "highlight"; }))) {
cast(validatedAct, act, "value", identValidate(&json::is_boolean));
}
ret.push_back(validatedAct);
}
}
return {true, ret};
}
std::optional<json> validateRule(std::string ruleSetName, const json &rule)
{
json ret = json::object();
if (!(
cast(ret, rule, "rule_id", identValidate(&json::is_string))
&& cast(ret, rule, "enabled", identValidate(&json::is_boolean))
&& cast(ret, rule, "default", identValidate(&json::is_boolean))
&& (!(ruleSetName == "override" || ruleSetName == "underride")
|| (!rule.contains("conditions") && ((ret["conditions"] = json::array()), true))
|| cast(ret, rule, "conditions", validateConditions))
&& cast(ret, rule, "actions", validateActions)
)) {
return std::nullopt;
}
return ret;
}
Event validatePushRules(const Event &e)
{
json ret{
{"type", "m.push_rules"},
{"content", {
{"global", {
{"override", {}},
{"content", {}},
{"room", {}},
{"sender", {}},
{"underride", {}},
}},
}},
};
const auto content = e.content().get();
for (const auto &ruleSetName : ruleSets) {
auto rules = getInJson(content, std::array<std::string, 2>{"global", ruleSetName});
if (rules.has_value() && rules.value().is_array()) {
for (const auto &rule : rules.value()) {
auto validated = validateRule(ruleSetName, rule);
if (validated.has_value()) {
ret["content"]["global"][ruleSetName].push_back(validated.value());
}
}
}
}
return Event(ret);
}
+ PushRulesDescPrivate::PushRulesDescPrivate(const Event &e)
+ : pushRulesEvent(validatePushRules(e))
+ {}
+
std::optional<std::pair<std::string, json>> PushRulesDescPrivate::matchRule(const Event &e, const RoomModel &room) const
{
for (const auto &ruleSetName : ruleSets) {
auto matched = matchRuleSet(ruleSetName, e, room);
if (matched.has_value()) {
return matched;
}
}
return std::nullopt;
}
std::optional<std::pair<std::string, json>> PushRulesDescPrivate::matchRuleSet(std::string ruleSetName, const Event &e, const RoomModel &room) const
{
auto rules = pushRulesEvent.content().get().at("global").at(ruleSetName);
for (const auto &rule : rules) {
if (matchP(ruleSetName, rule, e, room)) {
return std::make_pair(ruleSetName, rule);
}
}
return std::nullopt;
}
bool PushRulesDescPrivate::matchP(std::string ruleSetName, const json &rule, const Event &e, const RoomModel &room) const
{
if (!rule.at("enabled").template get<bool>()) {
return false;
}
if (ruleSetName == "override" || ruleSetName == "underride") {
return std::all_of(
rule.at("conditions").begin(),
rule.at("conditions").end(),
[&e](const auto &condition) {
if (condition.is_object()) {
if (condition.at("kind") == "event_property_is") {
auto path = splitPath(condition.at("key"));
auto valueInEvent = getInJson(e.originalJson().get(), path);
if (!valueInEvent.has_value() || !isNonCompoundCanonicalJsonValue(valueInEvent.value())) {
return false;
}
return valueInEvent.value() == condition.at("value");
} else if (condition.at("kind") == "event_match") {
auto path = splitPath(condition.at("key"));
auto valueInEvent = getInJson(e.originalJson().get(), path);
if (!valueInEvent.has_value() || !valueInEvent.value().is_string()) {
return false;
}
auto valueStr = valueInEvent.value().template get<std::string>();
auto patternStr = condition.at("pattern").template get<std::string>();
return matchGlob(valueStr, patternStr);
} else if (condition.at("kind") == "event_property_contains") {
auto path = splitPath(condition.at("key"));
auto valueInEvent = getInJson(e.originalJson().get(), path);
if (!valueInEvent.has_value() || !valueInEvent.value().is_array()) {
return false;
}
auto vals = valueInEvent.value();
return std::any_of(vals.begin(), vals.end(), [cond=condition.at("value")](const auto &val) {
return isNonCompoundCanonicalJsonValue(val) && val == cond;
});
}
}
return false;
}
);
}
return false;
}
PushAction PushRulesDescPrivate::handleRule(std::string ruleSetName, const json &rule, const Event &e, const RoomModel &room) const
{
auto actions = rule.at("actions");
if (std::find(actions.begin(), actions.end(), "notify") != actions.end()) {
return {true};
}
return {false};
}
PushRulesDesc::PushRulesDesc()
: m_d()
{
}
PushRulesDesc::PushRulesDesc(const Event &pushRulesEvent)
: m_d(new PushRulesDescPrivate{pushRulesEvent})
{
}
PushRulesDesc::~PushRulesDesc() = default;
KAZV_DEFINE_COPYABLE_UNIQUE_PTR(PushRulesDesc, m_d);
bool PushRulesDesc::valid() const
{
return !!m_d;
}
PushAction PushRulesDesc::handle(const Event &e, const RoomModel &room) const
{
auto ruleOpt = m_d->matchRule(e, room);
if (ruleOpt.has_value()) {
auto [ruleSetName, rule] = ruleOpt.value();
return m_d->handleRule(ruleSetName, rule, e, room);
}
return {false};
}
}
diff --git a/src/tests/client/push-rules-desc-test.cpp b/src/tests/client/push-rules-desc-test.cpp
index 7abf383..bc9cf86 100644
--- a/src/tests/client/push-rules-desc-test.cpp
+++ b/src/tests/client/push-rules-desc-test.cpp
@@ -1,579 +1,610 @@
/*
* 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 <catch2/catch_test_macros.hpp>
#include <push-rules-desc-p.hpp>
#include <factory.hpp>
using namespace Kazv;
using namespace Kazv::Factory;
// adapted from https://spec.matrix.org/v1.8/client-server-api/#default-override-rules
static auto masterRule = R"({
"rule_id": ".m.rule.master",
"default": true,
"enabled": true,
"conditions": [],
"actions": []
})"_json;
static auto suppressNoticesRule = R"({
"rule_id": ".m.rule.suppress_notices",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "event_match",
"key": "content.msgtype",
"pattern": "m.notice"
}
],
"actions": []
})"_json;
static auto inviteForMeRule = R"({
"rule_id": ".m.rule.invite_for_me",
"default": true,
"enabled": true,
"conditions": [
{
"key": "type",
"kind": "event_match",
"pattern": "m.room.member"
},
{
"key": "content.membership",
"kind": "event_match",
"pattern": "invite"
},
{
"key": "state_key",
"kind": "event_match",
"pattern": "@foo:example.com"
}
],
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
}
]
})"_json;
static auto memberEventRule = R"({
"rule_id": ".m.rule.member_event",
"default": true,
"enabled": true,
"conditions": [
{
"key": "type",
"kind": "event_match",
"pattern": "m.room.member"
}
],
"actions": []
})"_json;
static auto isUserMentionRule = R"({
"rule_id": ".m.rule.is_user_mention",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "event_property_contains",
"key": "content.m\\.mentions.user_ids",
"value": "@foo:example.com"
}
],
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
},
{
"set_tweak": "highlight"
}
]
})"_json;
static auto callRule = R"({
"rule_id": ".m.rule.call",
"default": true,
"enabled": true,
"conditions": [
{
"key": "type",
"kind": "event_match",
"pattern": "m.call.invite"
}
],
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "ring"
}
]
})"_json;
static auto encryptedOneToOneRule = R"({
"rule_id": ".m.rule.encrypted_room_one_to_one",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "room_member_count",
"is": "2"
},
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.encrypted"
}
],
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
}
]
})"_json;
static auto oneToOneRule = R"({
"rule_id": ".m.rule.room_one_to_one",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "room_member_count",
"is": "2"
},
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.message"
}
],
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
}
]
})"_json;
static auto messageRule = R"({
"rule_id": ".m.rule.message",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.message"
}
],
"actions": [
"notify"
]
})"_json;
static auto encryptedRule = R"({
"rule_id": ".m.rule.encrypted",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.encrypted"
}
],
"actions": [
"notify"
]
})"_json;
static auto catchAllRule = R"({
"rule_id": "moe.kazv.mxc.catch_all",
"default": true,
"enabled": true,
"conditions": [],
"actions": ["notify"]
})"_json;
static auto emptyRulesContent = json{
{"global", {
{"override", {}},
{"content", {}},
{"room", {}},
{"sender", {}},
{"underride", {}},
}},
};
static Event makePushRulesEvent(const json &pushRulesContent)
{
return makeEvent(
withEventContent(pushRulesContent)
| withEventType("m.push_rules")
);
}
TEST_CASE("splitPath()", "[client][push-rules]")
{
REQUIRE(splitPath("content.body") == std::vector<std::string>{"content", "body"});
REQUIRE(splitPath("content.m\\.relates_to") == std::vector<std::string>{"content", "m.relates_to"});
REQUIRE(splitPath("content.m\\\\foo") == std::vector<std::string>{"content", "m\\foo"});
REQUIRE(splitPath("content.m\\x") == std::vector<std::string>{"content", "m\\x"});
}
TEST_CASE("matchGlob()", "[client][push-rules]")
{
REQUIRE(matchGlob("m.notice", "m.notice"));
REQUIRE(!matchGlob("m.notice", "m.+notice"));
REQUIRE(!matchGlob("m.notice", "m.(notice)"));
REQUIRE(!matchGlob("mxnotice", "m.notice"));
REQUIRE(!matchGlob("m.notice", "^m.notice"));
REQUIRE(!matchGlob("m.notice", "m.notice$"));
REQUIRE(!matchGlob("m.notice", "m.notice|m.notice"));
REQUIRE(!matchGlob("m.notice", "m.notic[e]"));
REQUIRE(!matchGlob("m.notice", "m.notic\\e"));
REQUIRE(matchGlob("m\nnotice", "m?notice"));
REQUIRE(matchGlob("Lunch plans", "lunc?*"));
REQUIRE(matchGlob("LUNCH", "lunc?*"));
REQUIRE(!matchGlob(" lunch", "lunc?*"));
REQUIRE(!matchGlob("lunc", "lunc?*"));
}
TEST_CASE("validateRule()", "[client][push-rules]")
{
json rule = masterRule;
SECTION("minimum push rule")
{
auto validated = validateRule("override", rule);
REQUIRE(validated.has_value());
REQUIRE(validated.value() == rule);
}
SECTION("strips extra properties")
{
rule["something"] = "else";
auto validated = validateRule("override", rule);
REQUIRE(validated.has_value());
REQUIRE(validated.value() == masterRule);
}
SECTION("must have rule_id")
{
rule.erase("rule_id");
auto validated = validateRule("override", rule);
REQUIRE(!validated.has_value());
}
SECTION("must have default")
{
rule.erase("default");
auto validated = validateRule("override", rule);
REQUIRE(!validated.has_value());
}
SECTION("must have enabled")
{
rule.erase("enabled");
auto validated = validateRule("override", rule);
REQUIRE(!validated.has_value());
}
SECTION("must have conditions array")
{
rule["conditions"] = json::object();
auto validated = validateRule("override", rule);
REQUIRE(!validated.has_value());
}
SECTION("add empty array if conditions not present")
{
rule.erase("conditions");
auto validated = validateRule("override", rule);
REQUIRE(validated.has_value());
REQUIRE(validated.value() == masterRule);
}
SECTION("reject if there is unknown condition")
{
rule["conditions"] = json::array({{{"kind", "moe.kazv.mxc.unknown-condition"}}});
auto validated = validateRule("override", rule);
REQUIRE(!validated.has_value());
}
SECTION("event_match condition")
{
rule["conditions"] = json::array({{
{"kind", "event_match"},
{"key", "some"},
{"pattern", "patt*n"},
}});
auto validated = validateRule("override", rule);
REQUIRE(validated.has_value());
REQUIRE(validated.value() == rule);
auto ruleWithExtraProp = rule;
ruleWithExtraProp["conditions"][0]["some"] = "thing";
validated = validateRule("override", ruleWithExtraProp);
REQUIRE(validated.has_value());
REQUIRE(validated.value() == rule);
}
}
TEST_CASE("validatePushRules()", "[client][push-rules]")
{
json content = {
{"global", {
{"override", {masterRule, suppressNoticesRule, isUserMentionRule}},
{"content", {}},
{"room", {}},
{"sender", {}},
{"underride", {}},
}},
};
auto e = Event{json{
{"type", "m.push_rules"},
{"content", content},
}};
SECTION("nothing unrecognized")
{
auto validated = validatePushRules(e);
REQUIRE(validated == e);
}
SECTION("extra param in content")
{
content["something"] = "else";
auto extra = Event{json{
{"type", "m.push_rules"},
{"content", content},
}};
auto validated = validatePushRules(extra);
REQUIRE(validated == e);
}
SECTION("extra param in global")
{
content["global"]["something"] = "else";
auto extra = Event{json{
{"type", "m.push_rules"},
{"content", content},
}};
auto validated = validatePushRules(extra);
REQUIRE(validated == e);
}
SECTION("extra param in rule")
{
content["global"]["override"][0]["something"] = "else";
auto extra = Event{json{
{"type", "m.push_rules"},
{"content", content},
}};
auto validated = validatePushRules(extra);
REQUIRE(validated == e);
}
SECTION("extra param in condition")
{
content["global"]["override"][1]["conditions"][0]["something"] = "else";
auto extra = Event{json{
{"type", "m.push_rules"},
{"content", content},
}};
auto validated = validatePushRules(extra);
REQUIRE(validated == e);
}
SECTION("unrecognized condition")
{
content["global"]["override"][1]["conditions"].push_back(json::object({{"kind", "moe.kazv.mxc.unknown-condition"}}));
auto extra = Event{json{
{"type", "m.push_rules"},
{"content", content},
}};
content["global"]["override"].erase(1);
auto expected = Event{json{
{"type", "m.push_rules"},
{"content", content},
}};
auto validated = validatePushRules(extra);
REQUIRE(validated == expected);
}
SECTION("unrecognized action")
{
content["global"]["override"][1]["actions"].push_back("moe.kazv.mxc.unknown-action");
auto extra = Event{json{
{"type", "m.push_rules"},
{"content", content},
}};
auto validated = validatePushRules(extra);
REQUIRE(validated == e);
}
}
TEST_CASE("PushRulesDescPrivate::matchP()", "[client][push-rules]")
{
- PushRulesDescPrivate rp;
+ PushRulesDescPrivate rp{Event()};
auto room = makeRoom();
SECTION("master rule")
{
REQUIRE(rp.matchP("override", masterRule, makeEvent(), room));
}
SECTION("suppress notices rule")
{
auto notice = makeEvent(withEventContent(json{{"msgtype", "m.notice"}}));
REQUIRE(rp.matchP("override", suppressNoticesRule, notice, room));
auto notNotice = makeEvent(withEventContent(json{{"msgtype", "m.noticexxx"}}));
REQUIRE(!rp.matchP("override", suppressNoticesRule, notNotice, room));
}
SECTION("invite for me rule")
{
auto invite = makeEvent(
withEventContent(json{{"membership", "invite"}})
| withEventType("m.room.member")
| withStateKey("@foo:example.com")
);
REQUIRE(rp.matchP("override", inviteForMeRule, invite, room));
auto inviteForOthers = makeEvent(
withEventContent(json{{"membership", "invite"}})
| withEventType("m.room.member")
| withStateKey("@foo:example.com.tw")
);
REQUIRE(!rp.matchP("override", inviteForMeRule, inviteForOthers, room));
}
SECTION("member event rule")
{
auto member = makeEvent(
withEventContent(json{{"membership", "join"}})
| withEventType("m.room.member")
| withStateKey("@foo:example.com")
);
REQUIRE(rp.matchP("override", memberEventRule, member, room));
}
SECTION("is user mention rule")
{
auto mentionedEvent = makeEvent(
withEventContent(json{
{"m.mentions", {
{"user_ids", {"@foo:example.com"}}
}}
})
);
auto notMentionedEvent = makeEvent(
withEventContent(json{
{"m.mentions", {
{"user_ids", {"@foo:example.com.tw"}}
}}
})
);
REQUIRE(rp.matchP("override", isUserMentionRule, mentionedEvent, room));
REQUIRE(!rp.matchP("override", isUserMentionRule, notMentionedEvent, room));
}
}
TEST_CASE("Push rules works with predefined rules", "[client][push-rules]")
{
auto rulesContent = emptyRulesContent;
auto room = makeRoom();
SECTION("master rule")
{
rulesContent["global"]["override"].push_back(masterRule);
rulesContent["global"]["underride"].push_back(catchAllRule);
auto pushRules = PushRulesDesc(makePushRulesEvent(rulesContent));
auto result = pushRules.handle(makeEvent(), room);
REQUIRE(!result.shouldNotify);
}
SECTION("suppress notices rule")
{
rulesContent["global"]["override"].push_back(suppressNoticesRule);
rulesContent["global"]["underride"].push_back(catchAllRule);
auto pushRules = PushRulesDesc(makePushRulesEvent(rulesContent));
auto result = pushRules.handle(makeEvent(withEventContent(json{{"msgtype", "m.notice"}})), room);
REQUIRE(!result.shouldNotify);
}
SECTION("invite for me rule")
{
rulesContent["global"]["override"].push_back(inviteForMeRule);
auto pushRules = PushRulesDesc(makePushRulesEvent(rulesContent));
auto result = pushRules.handle(makeEvent(
withEventContent(json{{"membership", "invite"}})
| withEventType("m.room.member")
| withStateKey("@foo:example.com")
), room);
REQUIRE(result.shouldNotify);
}
SECTION("member event rule")
{
rulesContent["global"]["override"].push_back(memberEventRule);
rulesContent["global"]["underride"].push_back(catchAllRule);
auto pushRules = PushRulesDesc(makePushRulesEvent(rulesContent));
auto result = pushRules.handle(makeEvent(
withEventContent(json{{"membership", "invite"}})
| withEventType("m.room.member")
| withStateKey("@foo:example.com")
), room);
REQUIRE(!result.shouldNotify);
}
SECTION("is user mention rule")
{
auto mentionedEvent = makeEvent(
withEventContent(json{
{"m.mentions", {
{"user_ids", {"@foo:example.com"}}
}}
})
);
auto notMentionedEvent = makeEvent(
withEventContent(json{
{"m.mentions", {
{"user_ids", {"@foo:example.com.tw"}}
}}
})
);
rulesContent["global"]["override"].push_back(isUserMentionRule);
auto pushRules = PushRulesDesc(makePushRulesEvent(rulesContent));
REQUIRE(pushRules.handle(mentionedEvent, room).shouldNotify);
REQUIRE(!pushRules.handle(notMentionedEvent, room).shouldNotify);
}
}
+
+TEST_CASE("Push rules would not crash with broken rules", "[client][push-rules]")
+{
+ auto rulesContent = emptyRulesContent;
+ auto room = makeRoom();
+ REQUIRE(!PushRulesDesc(Event()).handle(makeEvent(), room).shouldNotify);
+
+ SECTION("rule missing required param")
+ {
+ auto rule = catchAllRule;
+ rule.erase("rule_id");
+ rulesContent["global"]["override"].push_back(rule);
+ REQUIRE(!PushRulesDesc(makePushRulesEvent(rulesContent)).handle(makeEvent(), room).shouldNotify);
+ }
+
+ SECTION("rule containing invalid condition")
+ {
+ auto rule = catchAllRule;
+ rule["conditions"].push_back("something");
+ rulesContent["global"]["override"].push_back(rule);
+ REQUIRE(!PushRulesDesc(makePushRulesEvent(rulesContent)).handle(makeEvent(), room).shouldNotify);
+ }
+
+ SECTION("rule containing unknown action")
+ {
+ auto rule = catchAllRule;
+ rule["actions"].push_back("something");
+ rulesContent["global"]["override"].push_back(rule);
+ REQUIRE(PushRulesDesc(makePushRulesEvent(rulesContent)).handle(makeEvent(), room).shouldNotify);
+ }
+}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jul 19, 11:43 PM (1 d, 11 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1695390
Default Alt Text
(30 KB)

Event Timeline