///|
pub(all) enum Action {
Keep
Discard
FileInto(String)
Redirect(String)
} derive(Eq, Debug)
///|
pub(all) struct Plan {
actions : Array[Action]
trace : Array[TraceEvent]
implicit_keep : Bool
steps : Int
comparisons : Int
} derive(Eq, Debug)
///|
priv struct Delivery {
actions : Array[Action]
mut implicit_keep : Bool
mut discarded : Bool
}
///|
fn Delivery::add(
self : Delivery,
action : Action,
rt : Runtime,
span : Span,
) -> Unit raise SieveError {
if !self.actions.any(existing => same_delivery(existing, action)) {
if self.actions.length() >= rt.budget.limits.actions {
fail("limit.actions", "delivery action limit exceeded", span)
}
self.actions.push(action)
}
}
///|
fn safe_destination(
destination : String,
kind : String,
span : Span,
) -> Unit raise SieveError {
if destination.is_empty() || !single_line(destination) {
fail(
"runtime.destination", "destination must be nonempty and single-line", span,
)
}
if kind == "redirect" && parse_mailbox(destination) is None {
fail(
"runtime.redirect", "redirect requires a supported bare addr-spec", span,
)
}
}
///|
fn Runtime::execute(
self : Runtime,
body : Array[Statement],
delivery : Delivery,
) -> Bool raise SieveError {
for statement in body {
match statement {
Branch(branches, otherwise, span) => {
self.budget.step(span)
let mut selected = false
for branch in branches {
if self.condition(branch.condition) {
selected = true
if self.execute(branch.body, delivery) {
return true
}
break
}
}
if !selected && self.execute(otherwise, delivery) {
return true
}
}
Command(name, args, span) => {
self.budget.step(span)
let a = split_args(args, span)
match name {
"require" => ()
"set" => self.set_variable(a, span)
"stop" => {
self.record("control", "stop", span, None)
return true
}
"keep" => {
delivery.add(Keep, self, span)
delivery.implicit_keep = false
self.record("action", "keep", span, None)
}
"discard" => {
delivery.discarded = true
delivery.implicit_keep = false
self.record("action", "discard", span, None)
}
"fileinto" | "redirect" => {
let destination = self.expand(single_arg(a.values[0], span), span)
safe_destination(destination, name, span)
let action = if name == "fileinto" {
FileInto(destination)
} else {
Redirect(destination)
}
delivery.add(action, self, span)
if !a.flags.contains("copy") {
delivery.implicit_keep = false
}
self.record("action", name, span, None)
}
_ =>
fail(
"runtime.command", "unsupported command reached interpreter", span,
)
}
}
}
}
false
}
///|
pub fn Program::run(
self : Program,
message : Message,
options? : ExecutionOptions = ExecutionOptions::default(),
) -> Plan raise SieveError {
options.check()
let checked = Message::new(
message.headers,
message.size,
envelope_from=message.envelope_from,
envelope_to=message.envelope_to,
limits=self.limits,
)
let rt : Runtime = {
message: checked,
budget: new_budget(self.limits),
trace: [],
variables: Map([]),
captures: Array::make(10, ""),
variables_enabled: self.capabilities.contains("variables"),
options,
}
let delivery : Delivery = {
actions: [],
implicit_keep: true,
discarded: false,
}
ignore(rt.execute(self.syntax.statements, delivery))
if delivery.implicit_keep {
delivery.add(Keep, rt, origin())
}
if delivery.actions.is_empty() && delivery.discarded {
delivery.add(Discard, rt, origin())
}
{
actions: delivery.actions,
trace: rt.trace,
implicit_keep: delivery.implicit_keep,
steps: rt.budget.steps,
comparisons: rt.budget.comparisons,
}
}