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