///|
pub(all) struct TraceEvent {
kind : String
label : String
span : Span
matched : Bool?
} derive(Eq, Debug)
///|
priv struct Runtime {
message : Message
budget : Budget
trace : Array[TraceEvent]
variables : Map[String, String]
captures : Array[String]
variables_enabled : Bool
options : ExecutionOptions
}
///|
fn Runtime::record(
self : Runtime,
kind : String,
label : String,
span : Span,
matched : Bool?,
) -> Unit raise SieveError {
if self.trace.length() >= self.budget.limits.steps {
fail("limit.trace", "trace event limit exceeded", span)
}
self.trace.push({ kind, label, span, matched })
}
///|
fn mode_from(a : Arguments) -> MatchType {
if a.flags.contains("contains") {
Contains
} else if a.flags.contains("matches") {
Matches
} else {
Is
}
}
///|
fn comparator_from(a : Arguments) -> Comparator {
if a.options.get("comparator") == Some("i;octet") {
Octet
} else {
AsciiCasemap
}
}
///|
fn address_part(mailbox : Mailbox, a : Arguments) -> String {
if a.flags.contains("localpart") {
mailbox.localpart
} else if a.flags.contains("domain") {
mailbox.domain
} else {
mailbox.localpart + "@" + mailbox.domain
}
}
///|
fn Runtime::test_values(
self : Runtime,
name : String,
fields : Array[String],
a : Arguments,
span : Span,
) -> Array[String] raise SieveError {
let values : Array[String] = []
for field in fields {
self.budget.step(span)
self.budget.compare(self.message.headers.length() + 1, span)
let raw = if name == "envelope" {
if ascii_lower(field) == "from" {
[self.message.envelope_from]
} else {
self.message.envelope_to
}
} else {
self.message.header_values(field)
}
if name == "header" {
for value in raw {
self.budget.compare(value.length() + 1, span)
values.push(value)
}
} else {
for value in raw {
self.budget.compare(value.length() + 1, span)
if value.is_empty() && name == "envelope" {
if !a.flags.contains("count") {
values.push("")
}
} else {
for mailbox in extract_mailboxes(value, limits=self.budget.limits) {
if a.flags.contains("count") {
values.push(mailbox.address)
continue
}
match selected_address_part(mailbox, a, self.options) {
Some(value) => values.push(value)
None => ()
}
}
}
}
}
}
values
}
///|
fn Runtime::predicate(
self : Runtime,
name : String,
a : Arguments,
span : Span,
) -> Bool raise SieveError {
match name {
"true" => true
"false" => false
"exists" => {
for field in self.expanded_strings(a.values[0], span) {
self.budget.step(span)
self.budget.compare(self.message.headers.length() + 1, span)
if self.message.header_values(field).is_empty() {
return false
}
}
true
}
"size" => {
let n = match a.values[0] {
Quantity(n) => n
_ => 0
}
if a.flags.contains("over") {
self.message.size > n
} else {
self.message.size < n
}
}
"header" | "address" | "envelope" | "string" => {
let fields = self.expanded_strings(a.values[0], span)
let keys = self.expanded_strings(a.values[1], span)
let values = if name == "string" {
if a.flags.contains("count") {
fields.filter(value => !value.is_empty())
} else {
fields
}
} else {
self.test_values(name, fields, a, span)
}
self.match_collection(values, keys, a, span)
}
_ => {
fail("runtime.test", "unsupported predicate reached evaluator", span)
false
}
}
}
///|
fn Runtime::condition(self : Runtime, expr : Test) -> Bool raise SieveError {
match expr {
Not(child, span) => {
self.budget.step(span)
let result = !self.condition(child)
self.record("test", "not", span, Some(result))
result
}
AnyOf(children, span) => {
self.budget.step(span)
let mut result = false
for child in children {
if self.condition(child) {
result = true
break
}
}
self.record("test", "anyof", span, Some(result))
result
}
AllOf(children, span) => {
self.budget.step(span)
let mut result = true
for child in children {
if !self.condition(child) {
result = false
break
}
}
self.record("test", "allof", span, Some(result))
result
}
Call(name, args, span) => {
self.budget.step(span)
let result = self.predicate(name, split_args(args, span), span)
self.record("test", name, span, Some(result))
result
}
}
}
///|
pub fn test_expression(
source : String,
message : Message,
capabilities? : Array[String] = [],
limits? : Limits = Limits::default(),
options? : ExecutionOptions = ExecutionOptions::default(),
) -> (Bool, Array[TraceEvent]) raise SieveError {
options.check()
let parser : Parser = { tokens: tokenize(source, limits~), pos: 0, limits }
let expr = parser.test_expr(0)
if parser.current().kind != End {
fail(
"parse.trailing",
"unexpected input after condition",
parser.current().span,
)
}
for cap in capabilities {
if !supported_capabilities().contains(cap) {
fail("check.capability", "unsupported capability", origin())
}
}
check_test(expr, capabilities)
let checked = Message::new(
message.headers,
message.size,
envelope_from=message.envelope_from,
envelope_to=message.envelope_to,
limits~,
)
let rt : Runtime = {
message: checked,
budget: new_budget(limits),
trace: [],
variables: Map([]),
captures: Array::make(10, ""),
variables_enabled: capabilities.contains("variables"),
options,
}
let result = rt.condition(expr)
(result, rt.trace)
}