Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F7889306
push-rules-desc.cpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
11 KB
Referenced Files
None
Subscribers
None
push-rules-desc.cpp
View Options
/*
* 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
<charconv>
#include
<boost/algorithm/string/regex.hpp>
#include
<immer/array.hpp>
#include
<event.hpp>
#include
<json-utils.hpp>
#include
<room/room-model.hpp>
#include
<validator.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
));
}
static
bool
isConditionKind
(
const
json
&
kind
)
{
return
kind
==
"event_match"
||
kind
==
"event_property_is"
||
kind
==
"event_property_contains"
||
kind
==
"room_member_count"
;
}
static
const
auto
memberCountIsRegex
=
boost
::
regex
(
"(>|==|<|>=|<=)?([0-9]+)"
);
static
bool
isMemberCountIsValid
(
const
json
&
is
)
{
auto
isStr
=
is
.
template
get
<
std
::
string
>
();
return
is
.
is_string
()
&&
boost
::
regex_match
(
isStr
,
memberCountIsRegex
);
}
static
std
::
function
<
bool
(
int
)
>
parseMemberCountIs
(
const
json
&
is
)
{
boost
::
smatch
m
;
auto
isStr
=
is
.
template
get
<
std
::
string
>
();
auto
res
=
boost
::
regex_match
(
isStr
,
m
,
memberCountIsRegex
);
auto
targetStr
=
m
[
2
].
str
();
int
target
;
std
::
from_chars
(
targetStr
.
data
(),
targetStr
.
data
()
+
targetStr
.
size
(),
target
);
if
(
m
[
1
]
==
""
||
m
[
1
]
==
"=="
)
{
return
[
target
](
auto
memberCount
)
{
return
memberCount
==
target
;
};
}
else
if
(
m
[
1
]
==
">"
)
{
return
[
target
](
auto
memberCount
)
{
return
memberCount
>
target
;
};
}
else
if
(
m
[
1
]
==
"<"
)
{
return
[
target
](
auto
memberCount
)
{
return
memberCount
<
target
;
};
}
else
if
(
m
[
1
]
==
">="
)
{
return
[
target
](
auto
memberCount
)
{
return
memberCount
>=
target
;
};
}
else
if
(
m
[
1
]
==
"<="
)
{
return
[
target
](
auto
memberCount
)
{
return
memberCount
<=
target
;
};
}
else
{
// shouldn't be here
return
[](
auto
)
{
return
false
;
};
}
}
static
std
::
pair
<
bool
,
json
>
validateCondition
(
const
json
&
cond
)
{
json
validatedCond
=
json
::
object
();
if
(
!
(
cond
.
is_object
()
&&
cast
(
validatedCond
,
cond
,
"kind"
,
identValidate
(
&
isConditionKind
))
))
{
return
{
false
,
json
()};
}
if
(
validatedCond
[
"kind"
]
==
"event_match"
)
{
if
(
!
(
cast
(
validatedCond
,
cond
,
"key"
,
identValidate
(
&
json
::
is_string
))
&&
cast
(
validatedCond
,
cond
,
"pattern"
,
identValidate
(
&
json
::
is_string
))
))
{
return
{
false
,
json
()};
}
}
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
,
json
()};
}
}
else
if
(
validatedCond
[
"kind"
]
==
"room_member_count"
)
{
if
(
!
(
cast
(
validatedCond
,
cond
,
"is"
,
identValidate
(
&
isMemberCountIsValid
))))
{
return
{
false
,
json
()};
}
}
else
{
return
{
false
,
json
()};
}
return
{
true
,
validatedCond
};
}
static
std
::
pair
<
bool
,
json
>
validateAction
(
const
json
&
act
)
{
if
(
act
==
"notify"
)
{
return
{
true
,
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
));
}
return
{
true
,
validatedAct
};
}
return
{
false
,
json
()};
}
std
::
pair
<
bool
,
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"
)
||
castArray
(
ret
,
makeDefaultValue
(
rule
,
"conditions"
,
json
::
array
()),
"conditions"
,
validateCondition
,
CastArrayStrategy
::
FailAll
))
&&
castArray
(
ret
,
rule
,
"actions"
,
validateAction
,
CastArrayStrategy
::
IgnoreInvalid
)
))
{
return
{
false
,
json
()};
}
return
{
true
,
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
});
castArray
(
ret
[
"content"
],
content
,
nlohmann
::
json_pointer
<
std
::
string
>
(
"/global/"
+
ruleSetName
),
[
ruleSetName
](
const
json
&
rule
)
{
return
validateRule
(
ruleSetName
,
rule
);
},
CastArrayStrategy
::
IgnoreInvalid
);
}
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
,
&
room
](
const
auto
&
condition
)
mutable
{
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
;
});
}
else
if
(
condition
.
at
(
"kind"
)
==
"room_member_count"
)
{
auto
is
=
condition
.
at
(
"is"
);
return
parseMemberCountIs
(
is
)(
room
.
joinedMemberCount
);
}
}
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
};
}
}
File Metadata
Details
Attached
Mime Type
text/x-c++
Expires
Thu, Oct 2, 2:28 AM (20 h, 54 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
471558
Default Alt Text
push-rules-desc.cpp (11 KB)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline
Log In to Comment