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