Skip to main content

sieve/runtime/
mod.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
5 */
6
7pub mod actions;
8pub mod context;
9pub mod eval;
10pub mod expression;
11pub mod tests;
12pub mod variables;
13
14use self::eval::ToString;
15use crate::{
16    ExternalId, Function, FunctionMap, Input, Metadata, Runtime, Script, Sieve,
17    compiler::{
18        Number,
19        grammar::{Capability, Invalid, expr::parser::ID_EXTERNAL},
20    },
21};
22use ahash::{AHashMap, AHashSet};
23use mail_parser::HeaderName;
24#[cfg(not(test))]
25use mail_parser::{Encoding, Message, MessageParser, MessagePart, PartType};
26use std::{
27    borrow::Cow,
28    fmt::Display,
29    hash::Hash,
30    ops::Deref,
31    sync::{Arc, OnceLock},
32};
33
34#[cfg(not(test))]
35use crate::Context;
36
37#[derive(Debug, Clone)]
38#[cfg_attr(
39    any(test, feature = "serde"),
40    derive(serde::Serialize, serde::Deserialize)
41)]
42pub enum Variable {
43    String(Arc<str>),
44    Integer(i64),
45    Float(f64),
46    Array(Arc<[Variable]>),
47}
48
49#[derive(Debug)]
50pub enum RuntimeError {
51    TooManyIncludes,
52    InvalidInstruction(Invalid),
53    ScriptErrorMessage(String),
54    CapabilityNotAllowed(Capability),
55    CapabilityNotSupported(String),
56    CPULimitReached,
57}
58
59static EMPTY_STRING: OnceLock<Arc<str>> = OnceLock::new();
60static EMPTY_ARRAY: OnceLock<Arc<[Variable]>> = OnceLock::new();
61
62pub(crate) fn empty_string() -> Arc<str> {
63    EMPTY_STRING.get_or_init(|| Arc::from("")).clone()
64}
65
66fn empty_array() -> Arc<[Variable]> {
67    EMPTY_ARRAY.get_or_init(|| Arc::from([])).clone()
68}
69
70impl Default for Variable {
71    fn default() -> Self {
72        Variable::String(empty_string())
73    }
74}
75
76impl Variable {
77    pub fn to_string(&self) -> Cow<'_, str> {
78        match self {
79            Variable::String(s) => Cow::Borrowed(s.as_ref()),
80            Variable::Integer(n) => Cow::Owned(n.to_string()),
81            Variable::Float(n) => Cow::Owned(n.to_string()),
82            Variable::Array(l) => Cow::Owned(l.to_string()),
83        }
84    }
85
86    pub fn to_number(&self) -> Number {
87        self.to_number_checked()
88            .unwrap_or(Number::Float(f64::INFINITY))
89    }
90
91    pub fn to_number_checked(&self) -> Option<Number> {
92        let s = match self {
93            Variable::Integer(n) => return Number::Integer(*n).into(),
94            Variable::Float(n) => return Number::Float(*n).into(),
95            Variable::String(s) if !s.is_empty() => s.as_ref(),
96            _ => return None,
97        };
98
99        if !s.contains('.') {
100            s.parse::<i64>().map(Number::Integer).ok()
101        } else {
102            s.parse::<f64>().map(Number::Float).ok()
103        }
104    }
105
106    pub fn to_integer(&self) -> i64 {
107        match self {
108            Variable::Integer(n) => *n,
109            Variable::Float(n) => *n as i64,
110            Variable::String(s) if !s.is_empty() => s.parse::<i64>().unwrap_or(0),
111            _ => 0,
112        }
113    }
114
115    pub fn to_usize(&self) -> usize {
116        match self {
117            Variable::Integer(n) => *n as usize,
118            Variable::Float(n) => *n as usize,
119            Variable::String(s) if !s.is_empty() => s.parse::<usize>().unwrap_or(0),
120            _ => 0,
121        }
122    }
123
124    pub fn len(&self) -> usize {
125        match self {
126            Variable::String(s) => s.len(),
127            Variable::Integer(_) | Variable::Float(_) => 2,
128            Variable::Array(l) => l.iter().map(|v| v.len() + 2).sum(),
129        }
130    }
131
132    pub fn is_empty(&self) -> bool {
133        match self {
134            Variable::String(s) => s.is_empty(),
135            _ => false,
136        }
137    }
138
139    pub fn as_array(&self) -> Option<&[Variable]> {
140        match self {
141            Variable::Array(l) => Some(l),
142            _ => None,
143        }
144    }
145
146    pub fn into_array(self) -> Arc<[Variable]> {
147        match self {
148            Variable::Array(l) => l,
149            v if !v.is_empty() => Arc::from([v]),
150            _ => empty_array(),
151        }
152    }
153
154    pub fn to_array(&self) -> Arc<[Variable]> {
155        match self {
156            Variable::Array(l) => l.clone(),
157            v if !v.is_empty() => Arc::from([v.clone()]),
158            _ => empty_array(),
159        }
160    }
161
162    pub fn into_string_array(self) -> Vec<String> {
163        match self {
164            Variable::Array(l) => l.iter().map(|i| i.to_string().into_owned()).collect(),
165            v if !v.is_empty() => vec![v.to_string().into_owned()],
166            _ => vec![],
167        }
168    }
169
170    pub fn to_string_array(&self) -> Vec<Cow<'_, str>> {
171        match self {
172            Variable::Array(l) => l.iter().map(|i| i.to_string()).collect(),
173            v if !v.is_empty() => vec![v.to_string()],
174            _ => vec![],
175        }
176    }
177}
178
179impl From<String> for Variable {
180    fn from(s: String) -> Self {
181        Variable::String(s.into())
182    }
183}
184
185impl<'x> From<&'x String> for Variable {
186    fn from(s: &'x String) -> Self {
187        Variable::String(s.as_str().into())
188    }
189}
190
191impl<'x> From<&'x str> for Variable {
192    fn from(s: &'x str) -> Self {
193        Variable::String(s.into())
194    }
195}
196
197impl<'x> From<Cow<'x, str>> for Variable {
198    fn from(s: Cow<'x, str>) -> Self {
199        match s {
200            Cow::Borrowed(s) => Variable::String(s.into()),
201            Cow::Owned(s) => Variable::String(s.into()),
202        }
203    }
204}
205
206impl From<Vec<Variable>> for Variable {
207    fn from(l: Vec<Variable>) -> Self {
208        Variable::Array(l.into())
209    }
210}
211
212impl From<Number> for Variable {
213    fn from(n: Number) -> Self {
214        match n {
215            Number::Integer(n) => Variable::Integer(n),
216            Number::Float(n) => Variable::Float(n),
217        }
218    }
219}
220
221impl From<usize> for Variable {
222    fn from(n: usize) -> Self {
223        Variable::Integer(n as i64)
224    }
225}
226
227impl From<i64> for Variable {
228    fn from(n: i64) -> Self {
229        Variable::Integer(n)
230    }
231}
232
233impl From<u64> for Variable {
234    fn from(n: u64) -> Self {
235        Variable::Integer(n as i64)
236    }
237}
238
239impl From<f64> for Variable {
240    fn from(n: f64) -> Self {
241        Variable::Float(n)
242    }
243}
244
245impl From<i32> for Variable {
246    fn from(n: i32) -> Self {
247        Variable::Integer(n as i64)
248    }
249}
250
251impl From<u32> for Variable {
252    fn from(n: u32) -> Self {
253        Variable::Integer(n as i64)
254    }
255}
256
257impl From<bool> for Variable {
258    fn from(b: bool) -> Self {
259        Variable::Integer(i64::from(b))
260    }
261}
262
263impl PartialEq for Number {
264    fn eq(&self, other: &Self) -> bool {
265        match (self, other) {
266            (Self::Integer(a), Self::Integer(b)) => a == b,
267            (Self::Float(a), Self::Float(b)) => a == b,
268            (Self::Integer(a), Self::Float(b)) => (*a as f64) == *b,
269            (Self::Float(a), Self::Integer(b)) => *a == (*b as f64),
270        }
271    }
272}
273
274impl Eq for Number {}
275
276impl PartialOrd for Number {
277    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
278        let (a, b) = match (self, other) {
279            (Number::Integer(a), Number::Integer(b)) => return a.partial_cmp(b),
280            (Number::Float(a), Number::Float(b)) => (*a, *b),
281            (Number::Integer(a), Number::Float(b)) => (*a as f64, *b),
282            (Number::Float(a), Number::Integer(b)) => (*a, *b as f64),
283        };
284        a.partial_cmp(&b)
285    }
286}
287
288impl self::eval::ToString for [Variable] {
289    fn to_string(&self) -> String {
290        let mut result = String::with_capacity(self.len() * 10);
291        for item in self {
292            if !result.is_empty() {
293                result.push_str("\r\n");
294            }
295            match item {
296                Variable::String(v) => result.push_str(v),
297                Variable::Integer(v) => result.push_str(&v.to_string()),
298                Variable::Float(v) => result.push_str(&v.to_string()),
299                Variable::Array(_) => {}
300            }
301        }
302        result
303    }
304}
305
306impl Hash for Variable {
307    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
308        match self {
309            Variable::String(s) => s.hash(state),
310            Variable::Integer(n) => n.hash(state),
311            Variable::Float(n) => n.to_bits().hash(state),
312            Variable::Array(l) => l.hash(state),
313        }
314    }
315}
316
317#[cfg(not(test))]
318impl Runtime {
319    pub fn filter<'z: 'x, 'x>(&'z self, raw_message: &'x [u8]) -> Context<'x> {
320        Context::new(
321            self,
322            MessageParser::new()
323                .parse(raw_message)
324                .unwrap_or_else(|| Message {
325                    parts: vec![MessagePart {
326                        headers: vec![],
327                        is_encoding_problem: false,
328                        body: PartType::Text("".into()),
329                        encoding: Encoding::None,
330                        offset_header: 0,
331                        offset_body: 0,
332                        offset_end: 0,
333                    }],
334                    raw_message: b""[..].into(),
335                    ..Default::default()
336                }),
337        )
338    }
339
340    pub fn filter_parsed<'z: 'x, 'x>(&'z self, message: Message<'x>) -> Context<'x> {
341        Context::new(self, message)
342    }
343}
344
345impl Default for Runtime {
346    fn default() -> Self {
347        Self::new()
348    }
349}
350
351impl Runtime {
352    pub fn new() -> Self {
353        #[allow(unused_mut)]
354        let mut allowed_capabilities = AHashSet::from_iter(Capability::all().iter().cloned());
355
356        #[cfg(test)]
357        allowed_capabilities.insert(Capability::Other("vnd.stalwart.testsuite".to_string()));
358
359        Runtime {
360            allowed_capabilities,
361            environment: AHashMap::from_iter([
362                ("name".into(), "Stalwart Sieve".into()),
363                ("version".into(), env!("CARGO_PKG_VERSION").into()),
364            ]),
365            metadata: Vec::new(),
366            include_scripts: AHashMap::new(),
367            max_nested_includes: 3,
368            cpu_limit: 5000,
369            max_variable_size: 4096,
370            max_redirects: 1,
371            max_received_headers: 10,
372            protected_headers: vec![
373                HeaderName::Other("Original-Subject".into()),
374                HeaderName::Other("Original-From".into()),
375            ],
376            valid_notification_uris: AHashSet::new(),
377            valid_ext_lists: AHashSet::new(),
378            vacation_use_orig_rcpt: false,
379            vacation_default_subject: "Automated reply".into(),
380            vacation_subject_prefix: "Auto: ".into(),
381            max_header_size: 1024,
382            max_out_messages: 3,
383            default_vacation_expiry: 30 * 86400,
384            default_duplicate_expiry: 7 * 86400,
385            local_hostname: "localhost".into(),
386            functions: Vec::new(),
387        }
388    }
389
390    pub fn set_cpu_limit(&mut self, size: usize) {
391        self.cpu_limit = size;
392    }
393
394    pub fn with_cpu_limit(mut self, size: usize) -> Self {
395        self.cpu_limit = size;
396        self
397    }
398
399    pub fn set_max_nested_includes(&mut self, size: usize) {
400        self.max_nested_includes = size;
401    }
402
403    pub fn with_max_nested_includes(mut self, size: usize) -> Self {
404        self.max_nested_includes = size;
405        self
406    }
407
408    pub fn set_max_redirects(&mut self, size: usize) {
409        self.max_redirects = size;
410    }
411
412    pub fn with_max_redirects(mut self, size: usize) -> Self {
413        self.max_redirects = size;
414        self
415    }
416
417    pub fn set_max_out_messages(&mut self, size: usize) {
418        self.max_out_messages = size;
419    }
420
421    pub fn with_max_out_messages(mut self, size: usize) -> Self {
422        self.max_out_messages = size;
423        self
424    }
425
426    pub fn set_max_received_headers(&mut self, size: usize) {
427        self.max_received_headers = size;
428    }
429
430    pub fn with_max_received_headers(mut self, size: usize) -> Self {
431        self.max_received_headers = size;
432        self
433    }
434
435    pub fn set_max_variable_size(&mut self, size: usize) {
436        self.max_variable_size = size;
437    }
438
439    pub fn with_max_variable_size(mut self, size: usize) -> Self {
440        self.max_variable_size = size;
441        self
442    }
443
444    pub fn set_max_header_size(&mut self, size: usize) {
445        self.max_header_size = size;
446    }
447
448    pub fn with_max_header_size(mut self, size: usize) -> Self {
449        self.max_header_size = size;
450        self
451    }
452
453    pub fn set_default_vacation_expiry(&mut self, expiry: u64) {
454        self.default_vacation_expiry = expiry;
455    }
456
457    pub fn with_default_vacation_expiry(mut self, expiry: u64) -> Self {
458        self.default_vacation_expiry = expiry;
459        self
460    }
461
462    pub fn set_default_duplicate_expiry(&mut self, expiry: u64) {
463        self.default_duplicate_expiry = expiry;
464    }
465
466    pub fn with_default_duplicate_expiry(mut self, expiry: u64) -> Self {
467        self.default_duplicate_expiry = expiry;
468        self
469    }
470
471    pub fn set_capability(&mut self, capability: impl Into<Capability>) {
472        self.allowed_capabilities.insert(capability.into());
473    }
474
475    pub fn with_capability(mut self, capability: impl Into<Capability>) -> Self {
476        self.set_capability(capability);
477        self
478    }
479
480    pub fn unset_capability(&mut self, capability: impl Into<Capability>) {
481        self.allowed_capabilities.remove(&capability.into());
482    }
483
484    pub fn without_capability(mut self, capability: impl Into<Capability>) -> Self {
485        self.unset_capability(capability);
486        self
487    }
488
489    pub fn without_capabilities(
490        mut self,
491        capabilities: impl IntoIterator<Item = impl Into<Capability>>,
492    ) -> Self {
493        for capability in capabilities {
494            self.allowed_capabilities.remove(&capability.into());
495        }
496        self
497    }
498
499    pub fn set_protected_header(&mut self, header_name: impl Into<Cow<'static, str>>) {
500        if let Some(header_name) = HeaderName::parse(header_name) {
501            self.protected_headers.push(header_name);
502        }
503    }
504
505    pub fn with_protected_header(mut self, header_name: impl Into<Cow<'static, str>>) -> Self {
506        self.set_protected_header(header_name);
507        self
508    }
509
510    pub fn with_protected_headers(
511        mut self,
512        header_names: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
513    ) -> Self {
514        self.protected_headers = header_names
515            .into_iter()
516            .filter_map(HeaderName::parse)
517            .collect();
518        self
519    }
520
521    pub fn set_env_variable(
522        &mut self,
523        name: impl Into<Cow<'static, str>>,
524        value: impl Into<Variable>,
525    ) {
526        self.environment.insert(name.into(), value.into());
527    }
528
529    pub fn with_env_variable(
530        mut self,
531        name: impl Into<Cow<'static, str>>,
532        value: impl Into<Cow<'static, str>>,
533    ) -> Self {
534        self.set_env_variable(name.into(), value.into());
535        self
536    }
537
538    pub fn set_medatata(
539        &mut self,
540        name: impl Into<Metadata<String>>,
541        value: impl Into<Cow<'static, str>>,
542    ) {
543        self.metadata.push((name.into(), value.into()));
544    }
545
546    pub fn with_metadata(
547        mut self,
548        name: impl Into<Metadata<String>>,
549        value: impl Into<Cow<'static, str>>,
550    ) -> Self {
551        self.set_medatata(name, value);
552        self
553    }
554
555    pub fn set_valid_notification_uri(&mut self, uri: impl Into<Cow<'static, str>>) {
556        self.valid_notification_uris.insert(uri.into());
557    }
558
559    pub fn with_valid_notification_uri(mut self, uri: impl Into<Cow<'static, str>>) -> Self {
560        self.valid_notification_uris.insert(uri.into());
561        self
562    }
563
564    pub fn with_valid_notification_uris(
565        mut self,
566        uris: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
567    ) -> Self {
568        self.valid_notification_uris = uris.into_iter().map(Into::into).collect();
569        self
570    }
571
572    pub fn set_valid_ext_list(&mut self, name: impl Into<Cow<'static, str>>) {
573        self.valid_ext_lists.insert(name.into());
574    }
575
576    pub fn with_valid_ext_list(mut self, name: impl Into<Cow<'static, str>>) -> Self {
577        self.set_valid_ext_list(name);
578        self
579    }
580
581    pub fn set_vacation_use_orig_rcpt(&mut self, value: bool) {
582        self.vacation_use_orig_rcpt = value;
583    }
584
585    pub fn with_valid_ext_lists(
586        mut self,
587        lists: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
588    ) -> Self {
589        self.valid_ext_lists = lists.into_iter().map(Into::into).collect();
590        self
591    }
592
593    pub fn with_vacation_use_orig_rcpt(mut self, value: bool) -> Self {
594        self.set_vacation_use_orig_rcpt(value);
595        self
596    }
597
598    pub fn set_vacation_default_subject(&mut self, value: impl Into<Cow<'static, str>>) {
599        self.vacation_default_subject = value.into();
600    }
601
602    pub fn with_vacation_default_subject(mut self, value: impl Into<Cow<'static, str>>) -> Self {
603        self.set_vacation_default_subject(value);
604        self
605    }
606
607    pub fn set_vacation_subject_prefix(&mut self, value: impl Into<Cow<'static, str>>) {
608        self.vacation_subject_prefix = value.into();
609    }
610
611    pub fn with_vacation_subject_prefix(mut self, value: impl Into<Cow<'static, str>>) -> Self {
612        self.set_vacation_subject_prefix(value);
613        self
614    }
615
616    pub fn set_local_hostname(&mut self, value: impl Into<Cow<'static, str>>) {
617        self.local_hostname = value.into();
618    }
619
620    pub fn with_local_hostname(mut self, value: impl Into<Cow<'static, str>>) -> Self {
621        self.set_local_hostname(value);
622        self
623    }
624
625    pub fn with_functions(mut self, fnc_map: &mut FunctionMap) -> Self {
626        self.functions = std::mem::take(&mut fnc_map.functions);
627        self
628    }
629
630    pub fn set_functions(&mut self, fnc_map: &mut FunctionMap) {
631        self.functions = std::mem::take(&mut fnc_map.functions);
632    }
633}
634
635impl FunctionMap {
636    pub fn new() -> Self {
637        FunctionMap {
638            map: Default::default(),
639            functions: Default::default(),
640        }
641    }
642
643    pub fn with_function(self, name: impl Into<String>, fnc: Function) -> Self {
644        self.with_function_args(name, fnc, 1)
645    }
646
647    pub fn with_function_no_args(self, name: impl Into<String>, fnc: Function) -> Self {
648        self.with_function_args(name, fnc, 0)
649    }
650
651    pub fn with_function_args(
652        mut self,
653        name: impl Into<String>,
654        fnc: Function,
655        num_args: u32,
656    ) -> Self {
657        self.map
658            .insert(name.into(), (self.functions.len() as u32, num_args));
659        self.functions.push(fnc);
660        self
661    }
662
663    pub fn with_external_function(
664        mut self,
665        name: impl Into<String>,
666        id: ExternalId,
667        num_args: u32,
668    ) -> Self {
669        self.set_external_function(name, id, num_args);
670        self
671    }
672
673    pub fn set_external_function(
674        &mut self,
675        name: impl Into<String>,
676        id: ExternalId,
677        num_args: u32,
678    ) {
679        self.map.insert(name.into(), (ID_EXTERNAL - id, num_args));
680    }
681}
682
683impl Input {
684    pub fn script(name: impl Into<Script>, script: impl Into<Arc<Sieve>>) -> Self {
685        Input::Script {
686            name: name.into(),
687            script: script.into(),
688        }
689    }
690
691    pub fn success() -> Self {
692        Input::True
693    }
694
695    pub fn fail() -> Self {
696        Input::False
697    }
698
699    pub fn result(result: Variable) -> Self {
700        Input::FncResult(result)
701    }
702}
703
704impl From<bool> for Input {
705    fn from(value: bool) -> Self {
706        if value { Input::True } else { Input::False }
707    }
708}
709
710impl From<Variable> for Input {
711    fn from(value: Variable) -> Self {
712        Input::FncResult(value)
713    }
714}
715
716impl Deref for Script {
717    type Target = String;
718
719    fn deref(&self) -> &Self::Target {
720        match self {
721            Script::Personal(name) | Script::Global(name) => name,
722        }
723    }
724}
725
726impl AsRef<str> for Script {
727    fn as_ref(&self) -> &str {
728        match self {
729            Script::Personal(name) | Script::Global(name) => name.as_str(),
730        }
731    }
732}
733
734impl AsRef<String> for Script {
735    fn as_ref(&self) -> &String {
736        match self {
737            Script::Personal(name) | Script::Global(name) => name,
738        }
739    }
740}
741
742impl Script {
743    pub fn into_string(self) -> String {
744        match self {
745            Script::Personal(name) | Script::Global(name) => name,
746        }
747    }
748
749    pub fn as_str(&self) -> &String {
750        match self {
751            Script::Personal(name) | Script::Global(name) => name,
752        }
753    }
754}
755
756impl Display for Script {
757    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
758        f.write_str(self.as_str())
759    }
760}
761
762impl From<String> for Script {
763    fn from(name: String) -> Self {
764        Script::Personal(name)
765    }
766}
767
768impl From<&str> for Script {
769    fn from(name: &str) -> Self {
770        Script::Personal(name.to_string())
771    }
772}
773
774impl<T> Metadata<T> {
775    pub fn server(annotation: impl Into<T>) -> Self {
776        Metadata::Server {
777            annotation: annotation.into(),
778        }
779    }
780
781    pub fn mailbox(name: impl Into<T>, annotation: impl Into<T>) -> Self {
782        Metadata::Mailbox {
783            name: name.into(),
784            annotation: annotation.into(),
785        }
786    }
787}
788
789impl From<String> for Metadata<String> {
790    fn from(annotation: String) -> Self {
791        Metadata::Server { annotation }
792    }
793}
794
795impl From<&'_ str> for Metadata<String> {
796    fn from(annotation: &'_ str) -> Self {
797        Metadata::Server {
798            annotation: annotation.to_string(),
799        }
800    }
801}
802
803impl From<(String, String)> for Metadata<String> {
804    fn from((name, annotation): (String, String)) -> Self {
805        Metadata::Mailbox { name, annotation }
806    }
807}
808
809impl From<(&'_ str, &'_ str)> for Metadata<String> {
810    fn from((name, annotation): (&'_ str, &'_ str)) -> Self {
811        Metadata::Mailbox {
812            name: name.to_string(),
813            annotation: annotation.to_string(),
814        }
815    }
816}