Implement the current spec for event match conditions

This fixes that people randomly get pinged on every reply to a user
names @roomba:server.tld.

fixes #2541

Signed-off-by: Nicolas Werner <nicolas.werner@hotmail.de>
This commit is contained in:
Nicolas Werner 2022-07-04 21:07:20 +02:00
parent 2c444527bd
commit 9df8009ae3
No known key found for this signature in database
GPG Key ID: C8D75E610773F2D9
3 changed files with 31 additions and 21 deletions

View File

@ -31,18 +31,14 @@ class EventMatchCondition(
* The glob-style pattern to match against. Patterns with no special glob characters should
* be treated as having asterisks prepended and appended when testing the condition.
*/
val pattern: String,
/**
* true to match only words. In this case pattern will not be considered as a glob
*/
val wordsOnly: Boolean
val pattern: String
) : Condition {
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveEventMatchCondition(event, this)
}
override fun technicalDescription() = "'$key' matches '$pattern', words only '$wordsOnly'"
override fun technicalDescription() = "'$key' matches '$pattern'"
fun isSatisfied(event: Event): Boolean {
// TODO encrypted events?
@ -50,21 +46,20 @@ class EventMatchCondition(
?: return false
val value = extractField(rawJson, key) ?: return false
// Patterns with no special glob characters should be treated as having asterisks prepended
// and appended when testing the condition.
// The match is performed case-insensitively, and must match the entire value of
// the event field given by `key` (though see below regarding `content.body`). The
// exact meaning of "case insensitive" is defined by the implementation of the
// homeserver.
//
// As a special case, if `key` is `content.body`, then `pattern` must instead
// match any substring of the value of the property which starts and ends at a
// word boundary.
return try {
if (wordsOnly) {
if (key == 'content.body') {
value.caseInsensitiveFind(pattern)
} else {
val modPattern = if (pattern.hasSpecialGlobChar()) {
// Regex.containsMatchIn() is way faster without leading and trailing
// stars, that don't make any difference for the evaluation result
pattern.removePrefix("*").removeSuffix("*").simpleGlobToRegExp()
} else {
pattern.simpleGlobToRegExp()
}
val regex = Regex(modPattern, RegexOption.DOT_MATCHES_ALL)
regex.containsMatchIn(value)
val regex = Regex(pattern.simpleGlobToRegExp(), setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE))
regex.matches(value)
}
} catch (e: Throwable) {
// e.g PatternSyntaxException

View File

@ -63,7 +63,7 @@ data class PushCondition(
return when (Kind.fromString(kind)) {
Kind.EventMatch -> {
if (key != null && pattern != null) {
EventMatchCondition(key, pattern, rule.ruleId == RuleIds.RULE_ID_CONTAIN_USER_NAME)
EventMatchCondition(key, pattern)
} else {
Timber.e("Malformed Event match condition")
null

View File

@ -67,7 +67,7 @@ class PushRulesConditionTest : MatrixTest {
)
assert(condition.isSatisfied(simpleTextEvent))
assert(!condition.isSatisfied(simpleRoomMemberEvent))
assertFalse(condition.isSatisfied(simpleRoomMemberEvent))
}
@Test
@ -98,7 +98,7 @@ class PushRulesConditionTest : MatrixTest {
val condition = EventMatchCondition("content.body", "cake", false)
assert(condition.isSatisfied(createSimpleTextEvent("How was the cake?")))
assert(condition.isSatisfied(createSimpleTextEvent("Howwasthecake?")))
assertFalse(condition.isSatisfied(createSimpleTextEvent("Howwasthecake?")))
}
@Test
@ -124,6 +124,21 @@ class PushRulesConditionTest : MatrixTest {
assert(condition.isSatisfied(createSimpleTextEvent("BEN")))
}
@Test
fun test_eventmatch_at_room_condition() {
val condition = EventMatchCondition("content.body", "@room", true)
assertFalse(condition.isSatisfied(createSimpleTextEvent("@roomba")))
assertFalse(condition.isSatisfied(createSimpleTextEvent("room benoit")))
assertFalse(condition.isSatisfied(createSimpleTextEvent("abc@roomba")))
assert(condition.isSatisfied(createSimpleTextEvent("@room")))
assert(condition.isSatisfied(createSimpleTextEvent("@room, ben")))
assert(condition.isSatisfied(createSimpleTextEvent("@ROOM")))
assert(condition.isSatisfied(createSimpleTextEvent("Use:@room")))
assert(condition.isSatisfied(createSimpleTextEvent("Don't ping @room!")))
}
@Test
fun test_notice_condition() {
val conditionEqual = EventMatchCondition("content.msgtype", "m.notice", false)