1use self::{
8 grammar::{AddressPart, Capability},
9 lexer::{StringConstant, tokenizer::TokenInfo},
10};
11use crate::{
12 Compiler, Envelope, FunctionMap,
13 runtime::{RuntimeError, tests::glob::CompiledGlob},
14};
15use ahash::AHashMap;
16use arc_swap::ArcSwap;
17use mail_parser::HeaderName;
18use std::{borrow::Cow, fmt::Display, sync::Arc};
19
20pub mod grammar;
21pub mod lexer;
22
23#[derive(Debug)]
24pub struct CompileError {
25 line_num: usize,
26 line_pos: usize,
27 error_type: ErrorType,
28}
29
30#[derive(Debug)]
31pub enum ErrorType {
32 InvalidCharacter(u8),
33 InvalidNumber(String),
34 InvalidMatchVariable(usize),
35 InvalidUnicodeSequence(u32),
36 InvalidNamespace(String),
37 InvalidRegex(String),
38 InvalidExpression(String),
39 InvalidUtf8String,
40 InvalidHeaderName,
41 InvalidArguments,
42 InvalidAddress,
43 InvalidURI,
44 InvalidEnvelope(String),
45 UnterminatedString,
46 UnterminatedComment,
47 UnterminatedMultiline,
48 UnterminatedBlock,
49 ScriptTooLong,
50 StringTooLong,
51 VariableTooLong,
52 VariableIsLocal(String),
53 HeaderTooLong,
54 ExpectedConstantString,
55 UnexpectedToken {
56 expected: Cow<'static, str>,
57 found: String,
58 },
59 UnexpectedEOF,
60 TooManyNestedBlocks,
61 TooManyNestedTests,
62 TooManyNestedForEveryParts,
63 TooManyIncludes,
64 LabelAlreadyDefined(String),
65 LabelUndefined(String),
66 BreakOutsideLoop,
67 ContinueOutsideLoop,
68 UnsupportedComparator(String),
69 DuplicatedParameter,
70 UndeclaredCapability(Capability),
71 MissingTag(Cow<'static, str>),
72}
73
74impl Default for Compiler {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
81#[cfg_attr(
82 any(test, feature = "serde"),
83 derive(serde::Serialize, serde::Deserialize)
84)]
85#[cfg_attr(
86 feature = "rkyv",
87 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
88)]
89#[cfg_attr(
90 feature = "rkyv",
91 rkyv(serialize_bounds(
92 __S: rkyv::ser::Writer + rkyv::ser::Allocator,
93 __S::Error: rkyv::rancor::Source,
94 ))
95)]
96#[cfg_attr(
97 feature = "rkyv",
98 rkyv(deserialize_bounds(__D::Error: rkyv::rancor::Source))
99)]
100#[cfg_attr(
101 feature = "rkyv",
102 rkyv(bytecheck(
103 bounds(
104 __C: rkyv::validation::ArchiveContext,
105 )
106 ))
107)]
108#[repr(u8)]
109pub(crate) enum Value {
110 Text(ConstantId) = 0,
111 Number(Number) = 1,
112 Variable(VariableType) = 2,
113 Regex(Regex) = 3,
114 Glob(Glob) = 4,
115 Header(HeaderName<'static>) = 5,
116 List(#[cfg_attr(feature = "rkyv", rkyv(omit_bounds))] Box<[Value]>) = 6,
117}
118
119#[derive(Debug)]
120pub(crate) enum RawValue {
121 Text(String),
122 Number(Number),
123 Value(Value),
124}
125
126impl From<StringConstant> for RawValue {
127 fn from(value: StringConstant) -> Self {
128 match value {
129 StringConstant::String(text) => RawValue::Text(text),
130 StringConstant::Number(number) => RawValue::Number(number),
131 }
132 }
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
136#[cfg_attr(
137 any(test, feature = "serde"),
138 derive(serde::Serialize, serde::Deserialize)
139)]
140#[cfg_attr(
141 feature = "rkyv",
142 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
143)]
144#[repr(transparent)]
145pub struct ConstantId(u32);
146
147impl ConstantId {
148 pub(crate) const UNRESOLVED: ConstantId = ConstantId(u32::MAX);
149
150 #[inline(always)]
151 pub(crate) fn new(index: usize) -> Self {
152 ConstantId(index as u32)
153 }
154
155 #[inline(always)]
156 pub(crate) fn index(&self) -> usize {
157 self.0 as usize
158 }
159}
160
161#[derive(Debug, Clone)]
162#[cfg_attr(
163 feature = "rkyv",
164 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
165)]
166#[cfg_attr(
167 any(test, feature = "serde"),
168 derive(serde::Serialize, serde::Deserialize)
169)]
170pub struct Regex {
171 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Skip))]
172 #[cfg_attr(any(test, feature = "serde"), serde(skip, default))]
173 pub regex: LazyRegex,
174 pub expr: String,
175}
176
177#[derive(Debug, Clone)]
178pub struct LazyRegex(pub Arc<ArcSwap<Option<fancy_regex::Regex>>>);
179
180#[derive(Debug, Clone)]
181#[cfg_attr(
182 feature = "rkyv",
183 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
184)]
185#[cfg_attr(
186 any(test, feature = "serde"),
187 derive(serde::Serialize, serde::Deserialize)
188)]
189pub struct Glob {
190 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Skip))]
191 #[cfg_attr(any(test, feature = "serde"), serde(skip, default))]
192 pub glob: LazyGlob,
193 pub expr: String,
194}
195
196#[derive(Debug, Clone)]
197pub struct LazyGlob(pub(crate) Arc<ArcSwap<Option<CompiledGlob>>>);
198
199#[derive(Debug, Clone, PartialEq, Eq)]
200#[cfg_attr(
201 any(test, feature = "serde"),
202 derive(serde::Serialize, serde::Deserialize)
203)]
204#[cfg_attr(
205 feature = "rkyv",
206 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
207)]
208#[repr(u8)]
209pub enum VariableType {
210 Local(u16) = 0,
211 Match(u8) = 1,
212 Global(String) = 2,
213 Environment(String) = 3,
214 Envelope(Envelope) = 4,
215 Header(HeaderVariable<'static>) = 5,
216 Part(MessagePart) = 6,
217}
218
219#[derive(Debug, Clone, PartialEq, Eq)]
220#[cfg_attr(
221 any(test, feature = "serde"),
222 derive(serde::Serialize, serde::Deserialize)
223)]
224#[cfg_attr(
225 feature = "rkyv",
226 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
227)]
228pub struct Transform {
229 pub variable: Box<VariableType>,
230 pub functions: Box<[u32]>,
231}
232
233#[derive(Debug, Clone, PartialEq, Eq)]
234#[cfg_attr(
235 any(test, feature = "serde"),
236 derive(serde::Serialize, serde::Deserialize)
237)]
238#[cfg_attr(
239 feature = "rkyv",
240 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
241)]
242pub struct HeaderVariable<'x> {
243 pub name: Box<[HeaderName<'x>]>,
244 pub part: HeaderPart,
245 pub index_hdr: i32,
246 pub index_part: i32,
247}
248
249#[derive(Debug, Clone, PartialEq, Eq)]
250#[cfg_attr(
251 any(test, feature = "serde"),
252 derive(serde::Serialize, serde::Deserialize)
253)]
254#[cfg_attr(
255 feature = "rkyv",
256 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
257)]
258#[repr(u8)]
259pub enum MessagePart {
260 TextBody(bool) = 0,
261 HtmlBody(bool) = 1,
262 Contents = 2,
263 Raw = 3,
264}
265
266#[derive(Debug, Clone, PartialEq, Eq)]
267#[cfg_attr(
268 any(test, feature = "serde"),
269 derive(serde::Serialize, serde::Deserialize)
270)]
271#[cfg_attr(
272 feature = "rkyv",
273 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
274)]
275#[repr(u8)]
276pub enum HeaderPart {
277 Text = 0,
278 Date = 1,
279 Id = 2,
280 Address(AddressPart) = 3,
281 ContentType(ContentTypePart) = 4,
282 Received(ReceivedPart) = 5,
283 Raw = 6,
284 RawName = 7,
285 Exists = 8,
286}
287
288#[derive(Debug, Clone, PartialEq, Eq)]
289#[cfg_attr(
290 any(test, feature = "serde"),
291 derive(serde::Serialize, serde::Deserialize)
292)]
293#[cfg_attr(
294 feature = "rkyv",
295 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
296)]
297#[repr(u8)]
298pub enum ContentTypePart {
299 Type = 0,
300 Subtype = 1,
301 Attribute(String) = 2,
302}
303
304#[derive(Debug, Clone, PartialEq, Eq)]
305#[cfg_attr(
306 any(test, feature = "serde"),
307 derive(serde::Serialize, serde::Deserialize)
308)]
309#[cfg_attr(
310 feature = "rkyv",
311 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
312)]
313#[repr(u8)]
314pub enum ReceivedPart {
315 From(ReceivedHostname) = 0,
316 FromIp = 1,
317 FromIpRev = 2,
318 By(ReceivedHostname) = 3,
319 For = 4,
320 With = 5,
321 TlsVersion = 6,
322 TlsCipher = 7,
323 Id = 8,
324 Ident = 9,
325 Via = 10,
326 Date = 11,
327 DateRaw = 12,
328}
329
330#[derive(Debug, Clone, PartialEq, Eq)]
331#[cfg_attr(
332 any(test, feature = "serde"),
333 derive(serde::Serialize, serde::Deserialize)
334)]
335#[cfg_attr(
336 feature = "rkyv",
337 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
338)]
339#[repr(u8)]
340pub enum ReceivedHostname {
341 Name = 0,
342 Ip = 1,
343 Any = 2,
344}
345
346#[derive(Debug, Clone, Copy)]
347#[cfg_attr(
348 any(test, feature = "serde"),
349 derive(serde::Serialize, serde::Deserialize)
350)]
351#[cfg_attr(
352 feature = "rkyv",
353 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
354)]
355#[repr(u8)]
356pub enum Number {
357 Integer(i64) = 0,
358 Float(f64) = 1,
359}
360
361impl Number {
362 #[cfg(test)]
363 pub fn to_float(&self) -> f64 {
364 match self {
365 Number::Integer(i) => *i as f64,
366 Number::Float(fl) => *fl,
367 }
368 }
369}
370
371impl From<Number> for usize {
372 fn from(value: Number) -> Self {
373 match value {
374 Number::Integer(i) => i as usize,
375 Number::Float(fl) => fl as usize,
376 }
377 }
378}
379
380impl Display for Number {
381 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
382 match self {
383 Number::Integer(i) => i.fmt(f),
384 Number::Float(fl) => fl.fmt(f),
385 }
386 }
387}
388
389impl Compiler {
390 pub const VERSION: u32 = 3;
391
392 pub fn new() -> Self {
393 Compiler {
394 max_script_size: 1024 * 1024,
395 max_string_size: 4096,
396 max_variable_name_size: 32,
397 max_nested_blocks: 15,
398 max_nested_tests: 15,
399 max_nested_foreverypart: 3,
400 max_match_variables: 30,
401 max_local_variables: 128,
402 max_header_size: 1024,
403 max_includes: 6,
404 functions: AHashMap::new(),
405 no_capability_check: false,
406 }
407 }
408
409 pub fn set_max_header_size(&mut self, size: usize) {
410 self.max_header_size = size;
411 }
412
413 pub fn with_max_header_size(mut self, size: usize) -> Self {
414 self.max_header_size = size;
415 self
416 }
417
418 pub fn set_max_includes(&mut self, size: usize) {
419 self.max_includes = size;
420 }
421
422 pub fn with_max_includes(mut self, size: usize) -> Self {
423 self.max_includes = size;
424 self
425 }
426
427 pub fn set_max_nested_blocks(&mut self, size: usize) {
428 self.max_nested_blocks = size;
429 }
430
431 pub fn with_max_nested_blocks(mut self, size: usize) -> Self {
432 self.max_nested_blocks = size;
433 self
434 }
435
436 pub fn set_max_nested_tests(&mut self, size: usize) {
437 self.max_nested_tests = size;
438 }
439
440 pub fn with_max_nested_tests(mut self, size: usize) -> Self {
441 self.max_nested_tests = size;
442 self
443 }
444
445 pub fn set_max_nested_foreverypart(&mut self, size: usize) {
446 self.max_nested_foreverypart = size;
447 }
448
449 pub fn with_max_nested_foreverypart(mut self, size: usize) -> Self {
450 self.max_nested_foreverypart = size;
451 self
452 }
453
454 pub fn set_max_script_size(&mut self, size: usize) {
455 self.max_script_size = size;
456 }
457
458 pub fn with_max_script_size(mut self, size: usize) -> Self {
459 self.max_script_size = size;
460 self
461 }
462
463 pub fn set_max_string_size(&mut self, size: usize) {
464 self.max_string_size = size;
465 }
466
467 pub fn with_max_string_size(mut self, size: usize) -> Self {
468 self.max_string_size = size;
469 self
470 }
471
472 pub fn set_max_variable_name_size(&mut self, size: usize) {
473 self.max_variable_name_size = size;
474 }
475
476 pub fn with_max_variable_name_size(mut self, size: usize) -> Self {
477 self.max_variable_name_size = size;
478 self
479 }
480
481 pub fn set_max_match_variables(&mut self, size: usize) {
482 self.max_match_variables = size;
483 }
484
485 pub fn with_max_match_variables(mut self, size: usize) -> Self {
486 self.max_match_variables = size;
487 self
488 }
489
490 pub fn set_max_local_variables(&mut self, size: usize) {
491 self.max_local_variables = size;
492 }
493
494 pub fn with_max_local_variables(mut self, size: usize) -> Self {
495 self.max_local_variables = size;
496 self
497 }
498
499 pub fn register_functions(mut self, fnc_map: &mut FunctionMap) -> Self {
500 self.functions = std::mem::take(&mut fnc_map.map);
501 self
502 }
503
504 pub fn with_no_capability_check(mut self, value: bool) -> Self {
505 self.no_capability_check = value;
506 self
507 }
508
509 pub fn set_no_capability_check(&mut self, value: bool) {
510 self.no_capability_check = value;
511 }
512}
513
514impl CompileError {
515 pub fn line_num(&self) -> usize {
516 self.line_num
517 }
518
519 pub fn line_pos(&self) -> usize {
520 self.line_pos
521 }
522
523 pub fn error_type(&self) -> &ErrorType {
524 &self.error_type
525 }
526}
527
528impl PartialEq for Regex {
529 fn eq(&self, other: &Self) -> bool {
530 self.expr == other.expr
531 }
532}
533
534impl Eq for Regex {}
535
536impl TokenInfo {
537 pub fn expected(self, expected: impl Into<Cow<'static, str>>) -> CompileError {
538 CompileError {
539 line_num: self.line_num,
540 line_pos: self.line_pos,
541 error_type: ErrorType::UnexpectedToken {
542 expected: expected.into(),
543 found: self.token.to_string(),
544 },
545 }
546 }
547
548 pub fn missing_tag(self, tag: impl Into<Cow<'static, str>>) -> CompileError {
549 CompileError {
550 line_num: self.line_num,
551 line_pos: self.line_pos,
552 error_type: ErrorType::MissingTag(tag.into()),
553 }
554 }
555
556 pub fn custom(self, error_type: ErrorType) -> CompileError {
557 CompileError {
558 line_num: self.line_num,
559 line_pos: self.line_pos,
560 error_type,
561 }
562 }
563}
564
565impl Default for LazyRegex {
566 fn default() -> Self {
567 Self(Arc::new(ArcSwap::new(Arc::new(None))))
568 }
569}
570
571impl Default for LazyGlob {
572 fn default() -> Self {
573 Self(Arc::new(ArcSwap::new(Arc::new(None))))
574 }
575}
576
577impl Glob {
578 pub fn new(expr: String, to_lower: bool) -> Self {
579 let compiled = CompiledGlob::compile(&expr, to_lower);
580 Self {
581 expr,
582 glob: LazyGlob(Arc::new(ArcSwap::new(Arc::new(Some(compiled))))),
583 }
584 }
585}
586
587impl PartialEq for Glob {
588 fn eq(&self, other: &Self) -> bool {
589 self.expr == other.expr
590 }
591}
592
593impl Eq for Glob {}
594
595impl Regex {
596 pub fn new(expr: String, regex: fancy_regex::Regex) -> Self {
597 Self {
598 expr,
599 regex: LazyRegex(Arc::new(ArcSwap::new(Arc::new(Some(regex))))),
600 }
601 }
602}
603
604impl Display for CompileError {
605 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
606 match &self.error_type() {
607 ErrorType::InvalidCharacter(value) => {
608 write!(f, "Invalid character {:?}", char::from(*value))
609 }
610 ErrorType::InvalidNumber(value) => write!(f, "Invalid number {value:?}"),
611 ErrorType::InvalidMatchVariable(value) => {
612 write!(f, "Match variable {value} out of range")
613 }
614 ErrorType::InvalidUnicodeSequence(value) => {
615 write!(f, "Invalid Unicode sequence {value:04x}")
616 }
617 ErrorType::InvalidNamespace(value) => write!(f, "Invalid namespace {value:?}"),
618 ErrorType::InvalidRegex(value) => write!(f, "Invalid regular expression {value:?}"),
619 ErrorType::InvalidExpression(value) => write!(f, "Invalid expression {value}"),
620 ErrorType::InvalidUtf8String => write!(f, "Invalid UTF-8 string"),
621 ErrorType::InvalidHeaderName => write!(f, "Invalid header name"),
622 ErrorType::InvalidArguments => write!(f, "Invalid Arguments"),
623 ErrorType::InvalidAddress => write!(f, "Invalid Address"),
624 ErrorType::InvalidURI => write!(f, "Invalid URI"),
625 ErrorType::InvalidEnvelope(value) => write!(f, "Invalid envelope {value:?}"),
626 ErrorType::UnterminatedString => write!(f, "Unterminated string"),
627 ErrorType::UnterminatedComment => write!(f, "Unterminated comment"),
628 ErrorType::UnterminatedMultiline => write!(f, "Unterminated multi-line string"),
629 ErrorType::UnterminatedBlock => write!(f, "Unterminated block"),
630 ErrorType::ScriptTooLong => write!(f, "Sieve script is too large"),
631 ErrorType::StringTooLong => write!(f, "String is too long"),
632 ErrorType::VariableTooLong => write!(f, "Variable name is too long"),
633 ErrorType::VariableIsLocal(value) => {
634 write!(f, "Variable {value:?} was already defined as local")
635 }
636 ErrorType::HeaderTooLong => write!(f, "Header value is too long"),
637 ErrorType::ExpectedConstantString => write!(f, "Expected a constant string"),
638 ErrorType::UnexpectedToken { expected, found } => {
639 write!(f, "Expected token {expected:?} but found {found:?}")
640 }
641 ErrorType::UnexpectedEOF => write!(f, "Unexpected end of file"),
642 ErrorType::TooManyNestedBlocks => write!(f, "Too many nested blocks"),
643 ErrorType::TooManyNestedTests => write!(f, "Too many nested tests"),
644 ErrorType::TooManyNestedForEveryParts => {
645 write!(f, "Too many nested foreverypart blocks")
646 }
647 ErrorType::TooManyIncludes => write!(f, "Too many includes"),
648 ErrorType::LabelAlreadyDefined(value) => write!(f, "Label {value:?} already defined"),
649 ErrorType::LabelUndefined(value) => write!(f, "Label {value:?} does not exist"),
650 ErrorType::BreakOutsideLoop => write!(f, "Break used outside of foreverypart loop"),
651 ErrorType::ContinueOutsideLoop => write!(f, "Continue used outside of while loop"),
652 ErrorType::UnsupportedComparator(value) => {
653 write!(f, "Comparator {value:?} is not supported")
654 }
655 ErrorType::DuplicatedParameter => write!(f, "Duplicated argument"),
656 ErrorType::UndeclaredCapability(value) => {
657 write!(f, "Undeclared capability '{value}'")
658 }
659 ErrorType::MissingTag(value) => write!(f, "Missing tag {value:?}"),
660 }?;
661
662 write!(
663 f,
664 " at line {}, column {}.",
665 self.line_num(),
666 self.line_pos()
667 )
668 }
669}
670
671impl Display for RuntimeError {
672 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
673 match self {
674 RuntimeError::TooManyIncludes => write!(f, ""),
675 RuntimeError::InvalidInstruction(value) => write!(
676 f,
677 "Script executed invalid instruction {:?} at line {}, column {}.",
678 value.name(),
679 value.line_pos(),
680 value.line_num()
681 ),
682 RuntimeError::ScriptErrorMessage(value) => {
683 write!(f, "Script reported error {value:?}.")
684 }
685 RuntimeError::CapabilityNotAllowed(value) => {
686 write!(f, "Capability '{value}' has been disabled.")
687 }
688 RuntimeError::CapabilityNotSupported(value) => {
689 write!(f, "Capability '{value}' not supported.")
690 }
691 RuntimeError::CPULimitReached => write!(
692 f,
693 "Script exceeded the maximum number of instructions allowed to execute."
694 ),
695 }
696 }
697}
698
699#[cfg(test)]
700mod tests {
701 use std::{fs, path::PathBuf};
702
703 use crate::Compiler;
704
705 #[test]
706 fn parse_rfc() {
707 let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
708 test_dir.push("tests");
709 test_dir.push("rfcs");
710 let mut tests_run = 0;
711
712 let compiler = Compiler::new().with_max_nested_foreverypart(10);
713
714 for file_name in fs::read_dir(&test_dir).unwrap() {
715 let mut file_name = file_name.unwrap().path();
716 if file_name.extension().is_some_and(|e| e == "sieve") {
717 println!("Parsing {}", file_name.display());
718
719 let script = fs::read(&file_name).unwrap();
731 file_name.set_extension("json");
732 let expected_result = fs::read(&file_name).unwrap();
733
734 tests_run += 1;
735
736 let sieve = compiler.compile(&script).unwrap();
737
738 #[cfg(feature = "rkyv")]
739 assert_eq!(
740 crate::Sieve::from_bytes(&sieve.to_bytes().unwrap()).unwrap(),
741 sieve,
742 "rkyv round trip altered {}",
743 file_name.display()
744 );
745
746 let json_sieve = serde_json::to_string_pretty(
747 &sieve
748 .instructions
749 .into_iter()
750 .enumerate()
751 .collect::<Vec<_>>(),
752 )
753 .unwrap();
754
755 if json_sieve.as_bytes() != expected_result {
756 file_name.set_extension("failed");
757 fs::write(&file_name, json_sieve.as_bytes()).unwrap();
758 panic!("Test failed, parsed sieve saved to {}", file_name.display());
759 }
760 }
761 }
762
763 assert!(
764 tests_run > 0,
765 "Did not find any tests to run in folder {}.",
766 test_dir.display()
767 );
768 }
769}