sieve/runtime/
context.rs

1/*
2 * Copyright (c) 2020-2023, Stalwart Labs Ltd.
3 *
4 * This file is part of the Stalwart Sieve Interpreter.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, either version 3 of
9 * the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 * in the LICENSE file at the top-level directory of this distribution.
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 *
19 * You can be released from the requirements of the AGPLv3 license by
20 * purchasing a commercial license. Please contact licensing@stalw.art
21 * for more details.
22*/
23
24use std::{borrow::Cow, sync::Arc, time::SystemTime};
25
26use ahash::AHashMap;
27use mail_parser::Message;
28
29use crate::{
30    compiler::grammar::{instruction::Instruction, Capability},
31    Context, Envelope, Event, Input, Metadata, Runtime, Sieve, SpamStatus, VirusStatus,
32    MAX_LOCAL_VARIABLES, MAX_MATCH_VARIABLES,
33};
34
35use super::{
36    actions::action_include::IncludeResult,
37    tests::{test_envelope::parse_envelope_address, TestResult},
38    RuntimeError, Variable,
39};
40
41#[derive(Clone, Debug)]
42pub(crate) struct ScriptStack {
43    pub(crate) script: Arc<Sieve>,
44    pub(crate) prev_pos: usize,
45    pub(crate) prev_vars_local: Vec<Variable>,
46    pub(crate) prev_vars_match: Vec<Variable>,
47}
48
49impl<'x> Context<'x> {
50    #[cfg(not(test))]
51    pub(crate) fn new(runtime: &'x Runtime, message: Message<'x>) -> Self {
52        Context {
53            #[cfg(test)]
54            runtime: runtime.clone(),
55            #[cfg(not(test))]
56            runtime,
57            message,
58            part: 0,
59            part_iter: Vec::new().into_iter(),
60            part_iter_stack: Vec::new(),
61            pos: usize::MAX,
62            test_result: false,
63            script_cache: AHashMap::new(),
64            script_stack: Vec::with_capacity(0),
65            vars_global: AHashMap::new(),
66            vars_env: AHashMap::new(),
67            vars_local: Vec::with_capacity(0),
68            vars_match: Vec::with_capacity(0),
69            expr_stack: Vec::with_capacity(16),
70            expr_pos: 0,
71            envelope: Vec::new(),
72            metadata: Vec::new(),
73            message_size: usize::MAX,
74            final_event: Event::Keep {
75                flags: Vec::with_capacity(0),
76                message_id: 0,
77            }
78            .into(),
79            queued_events: vec![].into_iter(),
80            has_changes: false,
81            user_address: "".into(),
82            user_full_name: "".into(),
83            current_time: SystemTime::now()
84                .duration_since(SystemTime::UNIX_EPOCH)
85                .map(|d| d.as_secs())
86                .unwrap_or(0) as i64,
87            num_redirects: 0,
88            num_instructions: 0,
89            num_out_messages: 0,
90            last_message_id: 0,
91            main_message_id: 0,
92            virus_status: VirusStatus::Unknown,
93            spam_status: SpamStatus::Unknown,
94        }
95    }
96
97    #[allow(clippy::while_let_on_iterator)]
98    pub fn run(&mut self, input: Input) -> Option<Result<Event, RuntimeError>> {
99        match input {
100            Input::True => self.test_result ^= true,
101            Input::False => self.test_result ^= false,
102            Input::FncResult(result) => {
103                self.expr_stack.push(result);
104            }
105            Input::Script { name, script } => {
106                let num_vars = script.num_vars;
107                let num_match_vars = script.num_match_vars;
108
109                if num_match_vars <= MAX_MATCH_VARIABLES && num_vars <= MAX_LOCAL_VARIABLES {
110                    if self.message_size == usize::MAX {
111                        self.message_size = self.message.raw_message.len();
112                    }
113
114                    self.script_cache.insert(name, script.clone());
115                    self.script_stack.push(ScriptStack {
116                        script,
117                        prev_pos: self.pos,
118                        prev_vars_local: std::mem::replace(
119                            &mut self.vars_local,
120                            vec![Variable::default(); num_vars],
121                        ),
122                        prev_vars_match: std::mem::replace(
123                            &mut self.vars_match,
124                            vec![Variable::default(); num_match_vars],
125                        ),
126                    });
127                    self.pos = 0;
128                    self.test_result = false;
129                }
130            }
131        }
132
133        // Return any queued events
134        if let Some(event) = self.queued_events.next() {
135            return Some(Ok(event));
136        }
137
138        let mut current_script = self.script_stack.last()?.script.clone();
139        let mut iter = current_script.instructions.get(self.pos..)?.iter();
140
141        'outer: loop {
142            while let Some(instruction) = iter.next() {
143                self.num_instructions += 1;
144                if self.num_instructions > self.runtime.cpu_limit {
145                    self.finish_loop();
146                    return Some(Err(RuntimeError::CPULimitReached));
147                }
148                self.pos += 1;
149
150                match instruction {
151                    Instruction::Jz(jmp_pos) => {
152                        if !self.test_result {
153                            debug_assert!(*jmp_pos > self.pos - 1);
154                            self.pos = *jmp_pos;
155                            iter = current_script.instructions.get(self.pos..)?.iter();
156                            continue;
157                        }
158                    }
159                    Instruction::Jnz(jmp_pos) => {
160                        if self.test_result {
161                            debug_assert!(*jmp_pos > self.pos - 1);
162                            self.pos = *jmp_pos;
163                            iter = current_script.instructions.get(self.pos..)?.iter();
164                            continue;
165                        }
166                    }
167                    Instruction::Jmp(jmp_pos) => {
168                        debug_assert_ne!(*jmp_pos, self.pos - 1);
169                        self.pos = *jmp_pos;
170                        iter = current_script.instructions.get(self.pos..)?.iter();
171                        continue;
172                    }
173                    Instruction::Test(test) => match test.exec(self) {
174                        TestResult::Bool(result) => {
175                            self.test_result = result;
176                        }
177                        TestResult::Event { event, is_not } => {
178                            self.test_result = is_not;
179                            return Some(Ok(event));
180                        }
181                        TestResult::Error(err) => {
182                            self.finish_loop();
183                            return Some(Err(err));
184                        }
185                    },
186                    Instruction::Eval(expr) => match self.eval_expression(expr) {
187                        Ok(result) => {
188                            self.test_result = result.to_bool();
189                        }
190                        Err(event) => {
191                            return Some(Ok(event));
192                        }
193                    },
194                    Instruction::Clear(clear) => {
195                        if clear.local_vars_num > 0 {
196                            if let Some(local_vars) = self.vars_local.get_mut(
197                                clear.local_vars_idx as usize
198                                    ..(clear.local_vars_idx + clear.local_vars_num) as usize,
199                            ) {
200                                for local_var in local_vars.iter_mut() {
201                                    if !local_var.is_empty() {
202                                        *local_var = Variable::default();
203                                    }
204                                }
205                            } else {
206                                debug_assert!(false, "Failed to clear local variables: {clear:?}");
207                            }
208                        }
209                        if clear.match_vars != 0 {
210                            self.clear_match_variables(clear.match_vars);
211                        }
212                    }
213                    Instruction::Keep(keep) => {
214                        let next_event = self.build_message_id();
215                        self.final_event = Event::Keep {
216                            flags: self.get_local_or_global_flags(&keep.flags),
217                            message_id: self.main_message_id,
218                        }
219                        .into();
220                        if let Some(next_event) = next_event {
221                            return Some(Ok(next_event));
222                        }
223                    }
224                    Instruction::FileInto(fi) => {
225                        fi.exec(self);
226                        if let Some(event) = self.queued_events.next() {
227                            return Some(Ok(event));
228                        }
229                    }
230                    Instruction::Redirect(redirect) => {
231                        redirect.exec(self);
232                        if let Some(event) = self.queued_events.next() {
233                            return Some(Ok(event));
234                        }
235                    }
236                    Instruction::Discard => {
237                        self.final_event = Event::Discard.into();
238                    }
239                    Instruction::Stop => {
240                        self.script_stack.clear();
241                        break 'outer;
242                    }
243                    Instruction::Reject(reject) => {
244                        self.final_event = None;
245                        return Some(Ok(Event::Reject {
246                            extended: reject.ereject,
247                            reason: self.eval_value(&reject.reason).to_string().into_owned(),
248                        }));
249                    }
250                    Instruction::ForEveryPart(fep) => {
251                        if let Some(next_part) = self.part_iter.next() {
252                            self.part = next_part;
253                        } else if let Some((prev_part, prev_part_iter)) = self.part_iter_stack.pop()
254                        {
255                            debug_assert!(fep.jz_pos > self.pos - 1);
256                            self.part_iter = prev_part_iter;
257                            self.part = prev_part;
258                            self.pos = fep.jz_pos;
259                            iter = current_script.instructions.get(self.pos..)?.iter();
260                            continue;
261                        } else {
262                            self.part = 0;
263                            #[cfg(test)]
264                            panic!("ForEveryPart executed without items on stack.");
265                        }
266                    }
267                    Instruction::ForEveryPartPush => {
268                        let part_iter = self
269                            .find_nested_parts_ids(self.part_iter_stack.is_empty())
270                            .into_iter();
271                        self.part_iter_stack
272                            .push((self.part, std::mem::replace(&mut self.part_iter, part_iter)));
273                    }
274                    Instruction::ForEveryPartPop(num_pops) => {
275                        debug_assert!(
276                            *num_pops > 0 && *num_pops <= self.part_iter_stack.len(),
277                            "Pop out of range: {} with {} items.",
278                            num_pops,
279                            self.part_iter_stack.len()
280                        );
281                        for _ in 0..*num_pops {
282                            if let Some((prev_part, prev_part_iter)) = self.part_iter_stack.pop() {
283                                self.part_iter = prev_part_iter;
284                                self.part = prev_part;
285                            } else {
286                                break;
287                            }
288                        }
289                    }
290                    Instruction::While(while_) => match self.eval_expression(&while_.expr) {
291                        Ok(result) => {
292                            if !result.to_bool() {
293                                debug_assert!(while_.jz_pos > self.pos - 1);
294                                self.pos = while_.jz_pos;
295                                iter = current_script.instructions.get(self.pos..)?.iter();
296                                continue;
297                            }
298                        }
299                        Err(event) => {
300                            return Some(Ok(event));
301                        }
302                    },
303                    Instruction::Let(let_) => match self.eval_expression(&let_.expr) {
304                        Ok(result) => {
305                            self.set_variable(&let_.name, result);
306                        }
307                        Err(event) => {
308                            return Some(Ok(event));
309                        }
310                    },
311
312                    Instruction::Replace(replace) => replace.exec(self),
313                    Instruction::Enclose(enclose) => enclose.exec(self),
314                    Instruction::ExtractText(extract) => {
315                        extract.exec(self);
316                        if let Some(event) = self.queued_events.next() {
317                            return Some(Ok(event));
318                        }
319                    }
320                    Instruction::AddHeader(add_header) => add_header.exec(self),
321                    Instruction::DeleteHeader(delete_header) => delete_header.exec(self),
322                    Instruction::Set(set) => {
323                        set.exec(self);
324                        if let Some(event) = self.queued_events.next() {
325                            return Some(Ok(event));
326                        }
327                    }
328                    Instruction::Notify(notify) => {
329                        notify.exec(self);
330                        if let Some(event) = self.queued_events.next() {
331                            return Some(Ok(event));
332                        }
333                    }
334                    Instruction::Vacation(vacation) => {
335                        vacation.exec(self);
336                        if let Some(event) = self.queued_events.next() {
337                            return Some(Ok(event));
338                        }
339                    }
340                    Instruction::EditFlags(flags) => flags.exec(self),
341                    Instruction::Include(include) => match include.exec(self) {
342                        IncludeResult::Cached(script) => {
343                            self.script_stack.push(ScriptStack {
344                                script: script.clone(),
345                                prev_pos: self.pos,
346                                prev_vars_local: std::mem::replace(
347                                    &mut self.vars_local,
348                                    vec![Variable::default(); script.num_vars],
349                                ),
350                                prev_vars_match: std::mem::replace(
351                                    &mut self.vars_match,
352                                    vec![Variable::default(); script.num_match_vars],
353                                ),
354                            });
355                            self.pos = 0;
356                            current_script = script;
357                            iter = current_script.instructions.iter();
358                            continue;
359                        }
360                        IncludeResult::Event(event) => {
361                            return Some(Ok(event));
362                        }
363                        IncludeResult::Error(err) => {
364                            self.finish_loop();
365                            return Some(Err(err));
366                        }
367                        IncludeResult::None => (),
368                    },
369                    Instruction::Convert(convert) => {
370                        convert.exec(self);
371                    }
372                    Instruction::Return => {
373                        break;
374                    }
375                    Instruction::Require(capabilities) => {
376                        for capability in capabilities {
377                            if !self.runtime.allowed_capabilities.contains(capability) {
378                                self.finish_loop();
379                                return Some(Err(
380                                    if let Capability::Other(not_supported) = capability {
381                                        RuntimeError::CapabilityNotSupported(not_supported.clone())
382                                    } else {
383                                        RuntimeError::CapabilityNotAllowed(capability.clone())
384                                    },
385                                ));
386                            }
387                        }
388                    }
389                    Instruction::Error(err) => {
390                        self.finish_loop();
391                        return Some(Err(RuntimeError::ScriptErrorMessage(
392                            self.eval_value(&err.message).to_string().into_owned(),
393                        )));
394                    }
395                    Instruction::Invalid(invalid) => {
396                        self.finish_loop();
397                        return Some(Err(RuntimeError::InvalidInstruction(invalid.clone())));
398                    }
399                    #[cfg(test)]
400                    Instruction::TestCmd(arguments) => {
401                        return Some(Ok(Event::Function {
402                            id: u32::MAX,
403                            arguments: arguments
404                                .iter()
405                                .map(|s| self.eval_value(s).to_owned())
406                                .collect(),
407                        }));
408                    }
409                }
410            }
411
412            if let Some(prev_script) = self.script_stack.pop() {
413                self.pos = prev_script.prev_pos;
414                self.vars_local = prev_script.prev_vars_local;
415                self.vars_match = prev_script.prev_vars_match;
416            }
417
418            if let Some(script_stack) = self.script_stack.last() {
419                current_script = script_stack.script.clone();
420                iter = current_script.instructions.get(self.pos..)?.iter();
421            } else {
422                break;
423            }
424        }
425
426        match self.final_event.take() {
427            Some(Event::Keep {
428                mut flags,
429                message_id,
430            }) => {
431                let create_event = if self.has_changes {
432                    self.build_message_id()
433                } else {
434                    None
435                };
436
437                let global_flags = self.get_global_flags();
438                if flags.is_empty() && !global_flags.is_empty() {
439                    flags = global_flags;
440                }
441                if let Some(create_event) = create_event {
442                    self.queued_events = vec![
443                        create_event,
444                        Event::Keep {
445                            flags,
446                            message_id: self.main_message_id,
447                        },
448                    ]
449                    .into_iter();
450                    self.queued_events.next().map(Ok)
451                } else {
452                    Some(Ok(Event::Keep { flags, message_id }))
453                }
454            }
455            Some(event) => Some(Ok(event)),
456            _ => None,
457        }
458    }
459
460    pub(crate) fn finish_loop(&mut self) {
461        self.script_stack.clear();
462        if let Some(event) = self.final_event.take() {
463            self.queued_events = if let Event::Keep {
464                mut flags,
465                message_id,
466            } = event
467            {
468                let global_flags = self.get_global_flags();
469                if flags.is_empty() && !global_flags.is_empty() {
470                    flags = global_flags;
471                }
472
473                if self.has_changes {
474                    if let Some(event) = self.build_message_id() {
475                        vec![
476                            event,
477                            Event::Keep {
478                                flags,
479                                message_id: self.main_message_id,
480                            },
481                        ]
482                    } else {
483                        vec![Event::Keep { flags, message_id }]
484                    }
485                } else {
486                    vec![Event::Keep { flags, message_id }]
487                }
488            } else {
489                vec![event]
490            }
491            .into_iter();
492        }
493    }
494
495    pub fn set_envelope(
496        &mut self,
497        envelope: impl TryInto<Envelope>,
498        value: impl Into<Cow<'x, str>>,
499    ) {
500        if let Ok(envelope) = envelope.try_into() {
501            if matches!(&envelope, Envelope::From | Envelope::To) {
502                let value: Cow<str> = value.into();
503                if let Some(value) = parse_envelope_address(value.as_ref()) {
504                    self.envelope.push((envelope, value.to_string().into()));
505                }
506            } else {
507                self.envelope.push((envelope, Variable::from(value.into())));
508            }
509        }
510    }
511
512    pub fn with_vars_env(mut self, vars_env: AHashMap<Cow<'static, str>, Variable>) -> Self {
513        self.vars_env = vars_env;
514        self
515    }
516
517    pub fn with_envelope_list(mut self, envelope: Vec<(Envelope, Variable)>) -> Self {
518        self.envelope = envelope;
519        self
520    }
521
522    pub fn with_envelope(
523        mut self,
524        envelope: impl TryInto<Envelope>,
525        value: impl Into<Cow<'x, str>>,
526    ) -> Self {
527        self.set_envelope(envelope, value);
528        self
529    }
530
531    pub fn clear_envelope(&mut self) {
532        self.envelope.clear()
533    }
534
535    pub fn set_user_address(&mut self, from: impl Into<Cow<'x, str>>) {
536        self.user_address = from.into();
537    }
538
539    pub fn with_user_address(mut self, from: impl Into<Cow<'x, str>>) -> Self {
540        self.set_user_address(from);
541        self
542    }
543
544    pub fn set_user_full_name(&mut self, name: &str) {
545        let mut name_ = String::with_capacity(name.len());
546        for ch in name.chars() {
547            if ['\"', '\\'].contains(&ch) {
548                name_.push('\\');
549            }
550            name_.push(ch);
551        }
552        self.user_full_name = name_.into();
553    }
554
555    pub fn with_user_full_name(mut self, name: &str) -> Self {
556        self.set_user_full_name(name);
557        self
558    }
559
560    pub fn set_env_variable(
561        &mut self,
562        name: impl Into<Cow<'static, str>>,
563        value: impl Into<Variable>,
564    ) {
565        self.vars_env.insert(name.into(), value.into());
566    }
567
568    pub fn with_env_variable(
569        mut self,
570        name: impl Into<Cow<'static, str>>,
571        value: impl Into<Variable>,
572    ) -> Self {
573        self.set_env_variable(name, value);
574        self
575    }
576
577    pub fn set_global_variable(
578        &mut self,
579        name: impl Into<Cow<'static, str>>,
580        value: impl Into<Variable>,
581    ) {
582        self.vars_env.insert(name.into(), value.into());
583    }
584
585    pub fn with_global_variable(
586        mut self,
587        name: impl Into<Cow<'static, str>>,
588        value: impl Into<Variable>,
589    ) -> Self {
590        self.set_global_variable(name, value);
591        self
592    }
593
594    pub fn set_medatata(
595        &mut self,
596        name: impl Into<Metadata<String>>,
597        value: impl Into<Cow<'x, str>>,
598    ) {
599        self.metadata.push((name.into(), value.into()));
600    }
601
602    pub fn with_metadata(
603        mut self,
604        name: impl Into<Metadata<String>>,
605        value: impl Into<Cow<'x, str>>,
606    ) -> Self {
607        self.set_medatata(name, value);
608        self
609    }
610
611    pub fn set_spam_status(&mut self, status: impl Into<SpamStatus>) {
612        self.spam_status = status.into();
613    }
614
615    pub fn with_spam_status(mut self, status: impl Into<SpamStatus>) -> Self {
616        self.set_spam_status(status);
617        self
618    }
619
620    pub fn set_virus_status(&mut self, status: impl Into<VirusStatus>) {
621        self.virus_status = status.into();
622    }
623
624    pub fn with_virus_status(mut self, status: impl Into<VirusStatus>) -> Self {
625        self.set_virus_status(status);
626        self
627    }
628
629    pub fn take_message(&mut self) -> Message<'x> {
630        std::mem::take(&mut self.message)
631    }
632
633    pub fn has_message_changed(&self) -> bool {
634        self.main_message_id > 0
635    }
636
637    pub(crate) fn user_from_field(&self) -> String {
638        if !self.user_full_name.is_empty() {
639            format!("\"{}\" <{}>", self.user_full_name, self.user_address)
640        } else {
641            self.user_address.to_string()
642        }
643    }
644
645    pub fn global_variable_names(&self) -> impl Iterator<Item = &str> {
646        self.vars_global.keys().map(|k| k.as_ref())
647    }
648
649    pub fn global_variable(&self, name: &str) -> Option<&Variable> {
650        self.vars_global.get(name)
651    }
652
653    pub fn message(&self) -> &Message<'x> {
654        &self.message
655    }
656
657    pub fn part(&self) -> usize {
658        self.part
659    }
660}
661
662#[cfg(test)]
663impl<'x> Context<'x> {
664    pub(crate) fn new(runtime: &'x Runtime, message: Message<'x>) -> Self {
665        Context {
666            runtime: runtime.clone(),
667            message,
668            part: 0,
669            part_iter: Vec::new().into_iter(),
670            part_iter_stack: Vec::new(),
671            pos: usize::MAX,
672            test_result: false,
673            script_cache: AHashMap::new(),
674            script_stack: Vec::with_capacity(0),
675            vars_global: AHashMap::new(),
676            vars_env: AHashMap::new(),
677            vars_local: Vec::with_capacity(0),
678            vars_match: Vec::with_capacity(0),
679            expr_stack: Vec::with_capacity(16),
680            expr_pos: 0,
681            envelope: Vec::new(),
682            metadata: Vec::new(),
683            message_size: usize::MAX,
684            final_event: Event::Keep {
685                flags: Vec::with_capacity(0),
686                message_id: 0,
687            }
688            .into(),
689            queued_events: vec![].into_iter(),
690            has_changes: false,
691            user_address: "".into(),
692            user_full_name: "".into(),
693            current_time: SystemTime::now()
694                .duration_since(SystemTime::UNIX_EPOCH)
695                .map(|d| d.as_secs())
696                .unwrap_or(0) as i64,
697            num_redirects: 0,
698            num_instructions: 0,
699            num_out_messages: 0,
700            last_message_id: 0,
701            main_message_id: 0,
702            virus_status: VirusStatus::Unknown,
703            spam_status: SpamStatus::Unknown,
704        }
705    }
706}