1pub mod actions;
8mod arena;
9pub mod context;
10pub mod eval;
11pub mod expression;
12pub mod handler;
13pub mod tests;
14pub mod variable;
15pub mod variables;
16
17pub use arena::Arena;
18pub use handler::{
19 Action, Handler, Input, Mailbox, MessageSource, Recipient, Reply, Script, Status,
20};
21pub use variable::Variable;
22
23use crate::{
24 ExternalId, Function, FunctionMap, Metadata, Runtime, Sieve,
25 bytecode::Corrupt,
26 compiler::{
27 Number,
28 grammar::{Capability, expr::parser::ID_EXTERNAL},
29 },
30};
31use ahash::{AHashMap, AHashSet};
32use mail_parser::HeaderName;
33use mail_parser::{Encoding, Message, MessageParser, MessagePart, PartType};
34use std::borrow::Cow;
35
36use crate::Context;
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum RuntimeError {
40 TooManyIncludes,
41 ScriptNotFound(String),
42 InvalidInstruction {
43 name: String,
44 line_num: u32,
45 line_pos: u32,
46 },
47 ScriptErrorMessage(String),
48 CapabilityNotAllowed(Capability),
49 CapabilityNotSupported(String),
50 CPULimitReached,
51 MemoryLimitReached,
52 InvalidBytecode,
53 AwaitingInput,
54}
55
56impl From<Corrupt> for RuntimeError {
57 fn from(_: Corrupt) -> Self {
58 RuntimeError::InvalidBytecode
59 }
60}
61
62impl Number {
63 pub fn is_non_zero(&self) -> bool {
64 match self {
65 Number::Integer(n) => *n != 0,
66 Number::Float(n) => *n != 0.0,
67 }
68 }
69}
70
71impl Default for Number {
72 fn default() -> Self {
73 Number::Integer(0)
74 }
75}
76
77impl From<bool> for Number {
78 #[inline(always)]
79 fn from(b: bool) -> Self {
80 Number::Integer(i64::from(b))
81 }
82}
83
84impl From<i64> for Number {
85 #[inline(always)]
86 fn from(n: i64) -> Self {
87 Number::Integer(n)
88 }
89}
90
91impl From<f64> for Number {
92 #[inline(always)]
93 fn from(n: f64) -> Self {
94 Number::Float(n)
95 }
96}
97
98impl From<i32> for Number {
99 #[inline(always)]
100 fn from(n: i32) -> Self {
101 Number::Integer(n as i64)
102 }
103}
104
105impl PartialEq for Number {
106 fn eq(&self, other: &Self) -> bool {
107 match (self, other) {
108 (Self::Integer(a), Self::Integer(b)) => a == b,
109 (Self::Float(a), Self::Float(b)) => a == b,
110 (Self::Integer(a), Self::Float(b)) => (*a as f64) == *b,
111 (Self::Float(a), Self::Integer(b)) => *a == (*b as f64),
112 }
113 }
114}
115
116impl Eq for Number {}
117
118impl PartialOrd for Number {
119 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
120 let (a, b) = match (self, other) {
121 (Number::Integer(a), Number::Integer(b)) => return a.partial_cmp(b),
122 (Number::Float(a), Number::Float(b)) => (*a, *b),
123 (Number::Integer(a), Number::Float(b)) => (*a as f64, *b),
124 (Number::Float(a), Number::Integer(b)) => (*a, *b as f64),
125 };
126 a.partial_cmp(&b)
127 }
128}
129
130impl Runtime {
131 pub fn filter<'z: 'x, 'x>(
132 &'z self,
133 raw_message: &'x [u8],
134 script: &'x Sieve<'x>,
135 arena: &'x mut Arena,
136 ) -> Context<'x> {
137 Context::new(
138 self,
139 MessageParser::new()
140 .parse(raw_message)
141 .unwrap_or_else(|| Message {
142 parts: vec![MessagePart {
143 headers: vec![],
144 is_encoding_problem: false,
145 body: PartType::Text("".into()),
146 encoding: Encoding::None,
147 offset_header: 0,
148 offset_body: 0,
149 offset_end: 0,
150 }],
151 raw_message: b""[..].into(),
152 ..Default::default()
153 }),
154 script,
155 arena,
156 )
157 }
158
159 pub fn filter_parsed<'z: 'x, 'x>(
160 &'z self,
161 message: Message<'x>,
162 script: &'x Sieve<'x>,
163 arena: &'x mut Arena,
164 ) -> Context<'x> {
165 Context::new(self, message, script, arena)
166 }
167}
168
169impl Default for Runtime {
170 fn default() -> Self {
171 Self::new()
172 }
173}
174
175impl Runtime {
176 pub fn new() -> Self {
177 #[allow(unused_mut)]
178 let mut allowed_capabilities = AHashSet::from_iter(Capability::all().iter().cloned());
179
180 #[cfg(test)]
181 allowed_capabilities.insert(Capability::Other("vnd.stalwart.testsuite".to_string()));
182
183 Runtime {
184 allowed_capabilities,
185 environment: AHashMap::from_iter([
186 ("name".into(), "Stalwart Sieve".into()),
187 ("version".into(), env!("CARGO_PKG_VERSION").into()),
188 ]),
189 metadata: Vec::new(),
190 include_scripts: AHashMap::new(),
191 max_nested_includes: 3,
192 cpu_limit: 5000,
193 memory_limit: 32 * 1024 * 1024,
194 max_variable_size: 4096,
195 max_redirects: 1,
196 max_received_headers: 10,
197 protected_headers: vec![
198 HeaderName::Other("Original-Subject".into()),
199 HeaderName::Other("Original-From".into()),
200 ],
201 valid_notification_uris: AHashSet::new(),
202 valid_ext_lists: AHashSet::new(),
203 vacation_use_orig_rcpt: false,
204 vacation_default_subject: "Automated reply".into(),
205 vacation_subject_prefix: "Auto: ".into(),
206 max_header_size: 1024,
207 max_out_messages: 3,
208 default_vacation_expiry: 30 * 86400,
209 default_duplicate_expiry: 7 * 86400,
210 local_hostname: "localhost".into(),
211 functions: Vec::new(),
212 }
213 }
214
215 pub fn set_cpu_limit(&mut self, size: usize) {
216 self.cpu_limit = size;
217 }
218
219 pub fn with_cpu_limit(mut self, size: usize) -> Self {
220 self.cpu_limit = size;
221 self
222 }
223
224 pub fn set_memory_limit(&mut self, size: usize) {
225 self.memory_limit = size;
226 }
227
228 pub fn with_memory_limit(mut self, size: usize) -> Self {
229 self.memory_limit = size;
230 self
231 }
232
233 pub fn set_max_nested_includes(&mut self, size: usize) {
234 self.max_nested_includes = size;
235 }
236
237 pub fn with_max_nested_includes(mut self, size: usize) -> Self {
238 self.max_nested_includes = size;
239 self
240 }
241
242 pub fn set_max_redirects(&mut self, size: usize) {
243 self.max_redirects = size;
244 }
245
246 pub fn with_max_redirects(mut self, size: usize) -> Self {
247 self.max_redirects = size;
248 self
249 }
250
251 pub fn set_max_out_messages(&mut self, size: usize) {
252 self.max_out_messages = size;
253 }
254
255 pub fn with_max_out_messages(mut self, size: usize) -> Self {
256 self.max_out_messages = size;
257 self
258 }
259
260 pub fn set_max_received_headers(&mut self, size: usize) {
261 self.max_received_headers = size;
262 }
263
264 pub fn with_max_received_headers(mut self, size: usize) -> Self {
265 self.max_received_headers = size;
266 self
267 }
268
269 pub fn set_max_variable_size(&mut self, size: usize) {
270 self.max_variable_size = size;
271 }
272
273 pub fn with_max_variable_size(mut self, size: usize) -> Self {
274 self.max_variable_size = size;
275 self
276 }
277
278 pub fn set_max_header_size(&mut self, size: usize) {
279 self.max_header_size = size;
280 }
281
282 pub fn with_max_header_size(mut self, size: usize) -> Self {
283 self.max_header_size = size;
284 self
285 }
286
287 pub fn set_default_vacation_expiry(&mut self, expiry: u64) {
288 self.default_vacation_expiry = expiry;
289 }
290
291 pub fn with_default_vacation_expiry(mut self, expiry: u64) -> Self {
292 self.default_vacation_expiry = expiry;
293 self
294 }
295
296 pub fn set_default_duplicate_expiry(&mut self, expiry: u64) {
297 self.default_duplicate_expiry = expiry;
298 }
299
300 pub fn with_default_duplicate_expiry(mut self, expiry: u64) -> Self {
301 self.default_duplicate_expiry = expiry;
302 self
303 }
304
305 pub fn set_capability(&mut self, capability: impl Into<Capability>) {
306 self.allowed_capabilities.insert(capability.into());
307 }
308
309 pub fn with_capability(mut self, capability: impl Into<Capability>) -> Self {
310 self.set_capability(capability);
311 self
312 }
313
314 pub fn unset_capability(&mut self, capability: impl Into<Capability>) {
315 self.allowed_capabilities.remove(&capability.into());
316 }
317
318 pub fn without_capability(mut self, capability: impl Into<Capability>) -> Self {
319 self.unset_capability(capability);
320 self
321 }
322
323 pub fn without_capabilities(
324 mut self,
325 capabilities: impl IntoIterator<Item = impl Into<Capability>>,
326 ) -> Self {
327 for capability in capabilities {
328 self.allowed_capabilities.remove(&capability.into());
329 }
330 self
331 }
332
333 pub fn set_protected_header(&mut self, header_name: impl Into<Cow<'static, str>>) {
334 if let Some(header_name) = HeaderName::parse(header_name) {
335 self.protected_headers.push(header_name);
336 }
337 }
338
339 pub fn with_protected_header(mut self, header_name: impl Into<Cow<'static, str>>) -> Self {
340 self.set_protected_header(header_name);
341 self
342 }
343
344 pub fn with_protected_headers(
345 mut self,
346 header_names: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
347 ) -> Self {
348 self.protected_headers = header_names
349 .into_iter()
350 .filter_map(HeaderName::parse)
351 .collect();
352 self
353 }
354
355 pub fn set_env_variable(
356 &mut self,
357 name: impl Into<Cow<'static, str>>,
358 value: impl Into<Variable<'static>>,
359 ) {
360 self.environment.insert(name.into(), value.into());
361 }
362
363 pub fn with_env_variable(
364 mut self,
365 name: impl Into<Cow<'static, str>>,
366 value: impl Into<Variable<'static>>,
367 ) -> Self {
368 self.set_env_variable(name, value);
369 self
370 }
371
372 pub fn set_medatata(
373 &mut self,
374 name: impl Into<Metadata<String>>,
375 value: impl Into<Cow<'static, str>>,
376 ) {
377 self.metadata.push((name.into(), value.into()));
378 }
379
380 pub fn with_metadata(
381 mut self,
382 name: impl Into<Metadata<String>>,
383 value: impl Into<Cow<'static, str>>,
384 ) -> Self {
385 self.set_medatata(name, value);
386 self
387 }
388
389 pub fn set_valid_notification_uri(&mut self, uri: impl Into<Cow<'static, str>>) {
390 self.valid_notification_uris.insert(uri.into());
391 }
392
393 pub fn with_valid_notification_uri(mut self, uri: impl Into<Cow<'static, str>>) -> Self {
394 self.valid_notification_uris.insert(uri.into());
395 self
396 }
397
398 pub fn with_valid_notification_uris(
399 mut self,
400 uris: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
401 ) -> Self {
402 self.valid_notification_uris = uris.into_iter().map(Into::into).collect();
403 self
404 }
405
406 pub fn set_valid_ext_list(&mut self, name: impl Into<Cow<'static, str>>) {
407 self.valid_ext_lists.insert(name.into());
408 }
409
410 pub fn with_valid_ext_list(mut self, name: impl Into<Cow<'static, str>>) -> Self {
411 self.set_valid_ext_list(name);
412 self
413 }
414
415 pub fn set_vacation_use_orig_rcpt(&mut self, value: bool) {
416 self.vacation_use_orig_rcpt = value;
417 }
418
419 pub fn with_valid_ext_lists(
420 mut self,
421 lists: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
422 ) -> Self {
423 self.valid_ext_lists = lists.into_iter().map(Into::into).collect();
424 self
425 }
426
427 pub fn with_vacation_use_orig_rcpt(mut self, value: bool) -> Self {
428 self.set_vacation_use_orig_rcpt(value);
429 self
430 }
431
432 pub fn set_vacation_default_subject(&mut self, value: impl Into<Cow<'static, str>>) {
433 self.vacation_default_subject = value.into();
434 }
435
436 pub fn with_vacation_default_subject(mut self, value: impl Into<Cow<'static, str>>) -> Self {
437 self.set_vacation_default_subject(value);
438 self
439 }
440
441 pub fn set_vacation_subject_prefix(&mut self, value: impl Into<Cow<'static, str>>) {
442 self.vacation_subject_prefix = value.into();
443 }
444
445 pub fn with_vacation_subject_prefix(mut self, value: impl Into<Cow<'static, str>>) -> Self {
446 self.set_vacation_subject_prefix(value);
447 self
448 }
449
450 pub fn set_local_hostname(&mut self, value: impl Into<Cow<'static, str>>) {
451 self.local_hostname = value.into();
452 }
453
454 pub fn with_local_hostname(mut self, value: impl Into<Cow<'static, str>>) -> Self {
455 self.set_local_hostname(value);
456 self
457 }
458
459 pub fn with_functions(mut self, fnc_map: &mut FunctionMap) -> Self {
460 self.functions = std::mem::take(&mut fnc_map.functions);
461 self
462 }
463
464 pub fn set_functions(&mut self, fnc_map: &mut FunctionMap) {
465 self.functions = std::mem::take(&mut fnc_map.functions);
466 }
467
468 pub fn set_include_script(&mut self, name: impl Into<String>, script: Sieve<'static>) {
469 self.include_scripts.insert(name.into(), script);
470 }
471
472 pub fn with_include_script(mut self, name: impl Into<String>, script: Sieve<'static>) -> Self {
473 self.set_include_script(name, script);
474 self
475 }
476
477 pub fn include_script(&self, name: &str) -> Option<&Sieve<'static>> {
478 self.include_scripts.get(name)
479 }
480}
481
482impl FunctionMap {
483 pub fn new() -> Self {
484 FunctionMap {
485 map: Default::default(),
486 functions: Default::default(),
487 }
488 }
489
490 pub fn with_function(self, name: impl Into<String>, fnc: Function) -> Self {
491 self.with_function_args(name, fnc, 1)
492 }
493
494 pub fn with_function_no_args(self, name: impl Into<String>, fnc: Function) -> Self {
495 self.with_function_args(name, fnc, 0)
496 }
497
498 pub fn with_function_args(
499 mut self,
500 name: impl Into<String>,
501 fnc: Function,
502 num_args: u32,
503 ) -> Self {
504 self.map
505 .insert(name.into(), (self.functions.len() as u32, num_args));
506 self.functions.push(fnc);
507 self
508 }
509
510 pub fn with_external_function(
511 mut self,
512 name: impl Into<String>,
513 id: ExternalId,
514 num_args: u32,
515 ) -> Self {
516 self.set_external_function(name, id, num_args);
517 self
518 }
519
520 pub fn set_external_function(
521 &mut self,
522 name: impl Into<String>,
523 id: ExternalId,
524 num_args: u32,
525 ) {
526 self.map.insert(name.into(), (ID_EXTERNAL - id, num_args));
527 }
528}
529
530impl<T> Metadata<T> {
531 pub fn server(annotation: impl Into<T>) -> Self {
532 Metadata::Server {
533 annotation: annotation.into(),
534 }
535 }
536
537 pub fn mailbox(name: impl Into<T>, annotation: impl Into<T>) -> Self {
538 Metadata::Mailbox {
539 name: name.into(),
540 annotation: annotation.into(),
541 }
542 }
543}
544
545impl From<String> for Metadata<String> {
546 fn from(annotation: String) -> Self {
547 Metadata::Server { annotation }
548 }
549}
550
551impl From<&'_ str> for Metadata<String> {
552 fn from(annotation: &'_ str) -> Self {
553 Metadata::Server {
554 annotation: annotation.to_string(),
555 }
556 }
557}
558
559impl From<(String, String)> for Metadata<String> {
560 fn from((name, annotation): (String, String)) -> Self {
561 Metadata::Mailbox { name, annotation }
562 }
563}
564
565impl From<(&'_ str, &'_ str)> for Metadata<String> {
566 fn from((name, annotation): (&'_ str, &'_ str)) -> Self {
567 Metadata::Mailbox {
568 name: name.to_string(),
569 annotation: annotation.to_string(),
570 }
571 }
572}