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