1pub 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}