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