f009e54181
This tweaks push rule evaluation: 1. to be more strict around pattern matching and to not match empty patterns 3. to bail if we come across a `dont_notify`, since cycles after that are wasted 4. refactors `ActionsToTweaks` to make a bit more sense
131 lines
3.5 KiB
Go
131 lines
3.5 KiB
Go
package pushrules
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// ActionsToTweaks converts a list of actions into a primary action
|
|
// kind and a tweaks map. Returns a nil map if it would have been
|
|
// empty.
|
|
func ActionsToTweaks(as []*Action) (ActionKind, map[string]interface{}, error) {
|
|
var kind ActionKind
|
|
var tweaks map[string]interface{}
|
|
|
|
for _, a := range as {
|
|
switch a.Kind {
|
|
case DontNotifyAction:
|
|
// Don't bother processing any further
|
|
return DontNotifyAction, nil, nil
|
|
|
|
case SetTweakAction:
|
|
if tweaks == nil {
|
|
tweaks = map[string]interface{}{}
|
|
}
|
|
tweaks[string(a.Tweak)] = a.Value
|
|
|
|
default:
|
|
if kind != UnknownAction {
|
|
return UnknownAction, nil, fmt.Errorf("got multiple primary actions: already had %q, got %s", kind, a.Kind)
|
|
}
|
|
kind = a.Kind
|
|
}
|
|
}
|
|
|
|
return kind, tweaks, nil
|
|
}
|
|
|
|
// BoolTweakOr returns the named tweak as a boolean, and returns `def`
|
|
// on failure.
|
|
func BoolTweakOr(tweaks map[string]interface{}, key TweakKey, def bool) bool {
|
|
v, ok := tweaks[string(key)]
|
|
if !ok {
|
|
return def
|
|
}
|
|
b, ok := v.(bool)
|
|
if !ok {
|
|
return def
|
|
}
|
|
return b
|
|
}
|
|
|
|
// globToRegexp converts a Matrix glob-style pattern to a Regular expression.
|
|
func globToRegexp(pattern string) (*regexp.Regexp, error) {
|
|
// TODO: It's unclear which glob characters are supported. The only
|
|
// place this is discussed is for the unrelated "m.policy.rule.*"
|
|
// events. Assuming, the same: /[*?]/
|
|
if !strings.ContainsAny(pattern, "*?") {
|
|
pattern = "*" + pattern + "*"
|
|
}
|
|
|
|
// The defined syntax doesn't allow escaping the glob wildcard
|
|
// characters, which makes this a straight-forward
|
|
// replace-after-quote.
|
|
pattern = globNonMetaRegexp.ReplaceAllStringFunc(pattern, regexp.QuoteMeta)
|
|
pattern = strings.Replace(pattern, "*", ".*", -1)
|
|
pattern = strings.Replace(pattern, "?", ".", -1)
|
|
return regexp.Compile("^(" + pattern + ")$")
|
|
}
|
|
|
|
// globNonMetaRegexp are the characters that are not considered glob
|
|
// meta-characters (i.e. may need escaping).
|
|
var globNonMetaRegexp = regexp.MustCompile("[^*?]+")
|
|
|
|
// lookupMapPath traverses a hierarchical map structure, like the one
|
|
// produced by json.Unmarshal, to return the leaf value. Traversing
|
|
// arrays/slices is not supported, only objects/maps.
|
|
func lookupMapPath(path []string, m map[string]interface{}) (interface{}, error) {
|
|
if len(path) == 0 {
|
|
return nil, fmt.Errorf("empty path")
|
|
}
|
|
|
|
var v interface{} = m
|
|
for i, key := range path {
|
|
m, ok := v.(map[string]interface{})
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected an object for path %q, but got %T", strings.Join(path[:i+1], "."), v)
|
|
}
|
|
|
|
v, ok = m[key]
|
|
if !ok {
|
|
return nil, fmt.Errorf("path not found: %s", strings.Join(path[:i+1], "."))
|
|
}
|
|
}
|
|
|
|
return v, nil
|
|
}
|
|
|
|
// parseRoomMemberCountCondition parses a string like "2", "==2", "<2"
|
|
// into a function that checks if the argument to it fulfils the
|
|
// condition.
|
|
func parseRoomMemberCountCondition(s string) (func(int) bool, error) {
|
|
var b int
|
|
var cmp = func(a int) bool { return a == b }
|
|
switch {
|
|
case strings.HasPrefix(s, "<="):
|
|
cmp = func(a int) bool { return a <= b }
|
|
s = s[2:]
|
|
case strings.HasPrefix(s, ">="):
|
|
cmp = func(a int) bool { return a >= b }
|
|
s = s[2:]
|
|
case strings.HasPrefix(s, "<"):
|
|
cmp = func(a int) bool { return a < b }
|
|
s = s[1:]
|
|
case strings.HasPrefix(s, ">"):
|
|
cmp = func(a int) bool { return a > b }
|
|
s = s[1:]
|
|
case strings.HasPrefix(s, "=="):
|
|
// Same cmp as the default.
|
|
s = s[2:]
|
|
}
|
|
|
|
v, err := strconv.ParseInt(s, 10, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b = int(v)
|
|
return cmp, nil
|
|
}
|