1use std::fmt::Display;
25
26use phf::phf_map;
27use serde::{Deserialize, Serialize};
28
29use self::{expr::Expression, instruction::CompilerState};
30
31use super::{
32 lexer::{tokenizer::TokenInfo, word::Word, Token},
33 CompileError, ErrorType, Regex, Value,
34};
35
36pub mod actions;
37pub mod expr;
38pub mod instruction;
39pub mod test;
40pub mod tests;
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
43pub enum Capability {
44 Envelope,
45 EnvelopeDsn,
46 EnvelopeDeliverBy,
47 FileInto,
48 EncodedCharacter,
49 Comparator(Comparator),
50 Other(String),
51 Body,
52 Convert,
53 Copy,
54 Relational,
55 Date,
56 Index,
57 Duplicate,
58 Variables,
59 EditHeader,
60 ForEveryPart,
61 Mime,
62 Replace,
63 Enclose,
64 ExtractText,
65 Enotify,
66 RedirectDsn,
67 RedirectDeliverBy,
68 Environment,
69 Reject,
70 Ereject,
71 ExtLists,
72 SubAddress,
73 Vacation,
74 VacationSeconds,
75 Fcc,
76 Mailbox,
77 MailboxId,
78 MboxMetadata,
79 ServerMetadata,
80 SpecialUse,
81 Imap4Flags,
82 Ihave,
83 ImapSieve,
84 Include,
85 Regex,
86 SpamTest,
87 SpamTestPlus,
88 VirusTest,
89
90 Expressions,
92 While,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
96pub enum AddressPart {
97 LocalPart,
98 Domain,
99 All,
100 User,
101 Detail,
102 Name,
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
106pub enum MatchType {
107 Is,
108 Contains,
109 Matches(u64),
110 Regex(u64),
111 Value(RelationalMatch),
112 Count(RelationalMatch),
113 List,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
117pub(crate) enum RelationalMatch {
118 Gt,
119 Ge,
120 Lt,
121 Le,
122 Eq,
123 Ne,
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
127pub enum Comparator {
128 Elbonia,
129 Octet,
130 AsciiCaseMap,
131 AsciiNumeric,
132 Other(String),
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
136pub struct Clear {
137 pub local_vars_idx: u32,
138 pub local_vars_num: u32,
139 pub match_vars: u64,
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
143pub struct Invalid {
144 pub(crate) name: String,
145 pub(crate) line_num: usize,
146 pub(crate) line_pos: usize,
147}
148
149#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
150pub(crate) struct While {
151 pub expr: Vec<Expression>,
152 pub jz_pos: usize,
153}
154
155impl<'x> CompilerState<'x> {
156 #[inline(always)]
157 pub fn expect_instruction_end(&mut self) -> Result<(), CompileError> {
158 self.tokens.expect_token(Token::Semicolon)
159 }
160
161 pub fn ignore_instruction(&mut self) -> Result<(), CompileError> {
162 let mut curly_count = 0;
164 loop {
165 let token_info = self.tokens.unwrap_next()?;
166 match token_info.token {
167 Token::Semicolon if curly_count == 0 => {
168 break;
169 }
170 Token::CurlyOpen => {
171 curly_count += 1;
172 }
173 Token::CurlyClose => match curly_count {
174 0 => {
175 return Err(token_info.expected("instruction"));
176 }
177 1 => {
178 break;
179 }
180 _ => curly_count -= 1,
181 },
182 _ => (),
183 }
184 }
185
186 Ok(())
187 }
188
189 pub fn ignore_test(&mut self) -> Result<(), CompileError> {
190 let mut d_count = 0;
191 while let Some(token_info) = self.tokens.peek() {
192 match token_info?.token {
193 Token::ParenthesisOpen => {
194 d_count += 1;
195 }
196 Token::ParenthesisClose => {
197 if d_count == 0 {
198 break;
199 } else {
200 d_count -= 1;
201 }
202 }
203 Token::Comma => {
204 if d_count == 0 {
205 break;
206 }
207 }
208 Token::CurlyOpen => {
209 break;
210 }
211 _ => (),
212 }
213 self.tokens.next();
214 }
215
216 Ok(())
217 }
218
219 pub fn parse_match_type(&mut self, word: Word) -> Result<MatchType, CompileError> {
220 match word {
221 Word::Is => Ok(MatchType::Is),
222 Word::Contains => Ok(MatchType::Contains),
223 Word::Matches => {
224 self.block.match_test_pos.push(self.instructions.len());
225 Ok(MatchType::Matches(0))
226 }
227 Word::Regex => {
228 self.block.match_test_pos.push(self.instructions.len());
229 Ok(MatchType::Regex(0))
230 }
231 Word::List => Ok(MatchType::List),
232 _ => {
233 let token_info = self.tokens.unwrap_next()?;
234 if let Token::StringConstant(text) = &token_info.token {
235 if let Some(relational) = RELATIONAL.get(text.to_string().as_ref()) {
236 return Ok(if word == Word::Value {
237 MatchType::Value(*relational)
238 } else {
239 MatchType::Count(*relational)
240 });
241 }
242 }
243 Err(token_info.expected("relational match"))
244 }
245 }
246 }
247
248 pub(crate) fn parse_comparator(&mut self) -> Result<Comparator, CompileError> {
249 let comparator = self.tokens.expect_static_string()?;
250 Ok(if let Some(comparator) = COMPARATOR.get(&comparator) {
251 comparator.clone()
252 } else {
253 Comparator::Other(comparator)
254 })
255 }
256
257 pub(crate) fn parse_static_strings(&mut self) -> Result<Vec<String>, CompileError> {
258 let token_info = self.tokens.unwrap_next()?;
259 match token_info.token {
260 Token::BracketOpen => {
261 let mut strings = Vec::new();
262 loop {
263 let token_info = self.tokens.unwrap_next()?;
264 match token_info.token {
265 Token::StringConstant(string) => {
266 strings.push(string.into_string());
267 }
268 Token::Comma => (),
269 Token::BracketClose if !strings.is_empty() => break,
270 _ => return Err(token_info.expected("constant string")),
271 }
272 }
273 Ok(strings)
274 }
275 Token::StringConstant(string) => Ok(vec![string.into_string()]),
276 _ => Err(token_info.expected("'[' or constant string")),
277 }
278 }
279
280 pub fn parse_string(&mut self) -> Result<Value, CompileError> {
281 let next_token = self.tokens.unwrap_next()?;
282 match next_token.token {
283 Token::StringConstant(s) => Ok(Value::from(s)),
284 Token::StringVariable(s) => {
285 self.tokenize_string(&s, true)
286 .map_err(|error_type| CompileError {
287 line_num: next_token.line_num,
288 line_pos: next_token.line_pos,
289 error_type,
290 })
291 }
292 Token::BracketOpen => {
293 let mut items = self.parse_string_list(false)?;
294 match items.pop() {
295 Some(s) if items.is_empty() => Ok(s),
296 _ => Err(next_token.expected("string")),
297 }
298 }
299 _ => Err(next_token.expected("string")),
300 }
301 }
302
303 pub(crate) fn parse_strings(&mut self, allow_empty: bool) -> Result<Vec<Value>, CompileError> {
304 let token_info = self.tokens.unwrap_next()?;
305 match token_info.token {
306 Token::BracketOpen => self.parse_string_list(allow_empty),
307 Token::StringConstant(s) => Ok(vec![Value::from(s)]),
308 Token::StringVariable(s) => {
309 self.tokenize_string(&s, true)
310 .map(|s| vec![s])
311 .map_err(|error_type| CompileError {
312 line_num: token_info.line_num,
313 line_pos: token_info.line_pos,
314 error_type,
315 })
316 }
317 _ => Err(token_info.expected("'[' or string")),
318 }
319 }
320
321 pub(crate) fn parse_string_token(
322 &mut self,
323 token_info: TokenInfo,
324 ) -> Result<Value, CompileError> {
325 match token_info.token {
326 Token::StringConstant(s) => Ok(Value::from(s)),
327 Token::StringVariable(s) => {
328 self.tokenize_string(&s, true)
329 .map_err(|error_type| CompileError {
330 line_num: token_info.line_num,
331 line_pos: token_info.line_pos,
332 error_type,
333 })
334 }
335 _ => Err(token_info.expected("string")),
336 }
337 }
338
339 pub(crate) fn parse_strings_token(
340 &mut self,
341 token_info: TokenInfo,
342 ) -> Result<Vec<Value>, CompileError> {
343 match token_info.token {
344 Token::StringConstant(s) => Ok(vec![Value::from(s)]),
345 Token::StringVariable(s) => {
346 self.tokenize_string(&s, true)
347 .map(|s| vec![s])
348 .map_err(|error_type| CompileError {
349 line_num: token_info.line_num,
350 line_pos: token_info.line_pos,
351 error_type,
352 })
353 }
354 Token::BracketOpen => self.parse_string_list(false),
355 _ => Err(token_info.expected("string")),
356 }
357 }
358
359 pub(crate) fn parse_string_list(
360 &mut self,
361 allow_empty: bool,
362 ) -> Result<Vec<Value>, CompileError> {
363 let mut strings = Vec::new();
364 loop {
365 let token_info = self.tokens.unwrap_next()?;
366 match token_info.token {
367 Token::StringConstant(s) => {
368 strings.push(Value::from(s));
369 }
370 Token::StringVariable(s) => {
371 strings.push(self.tokenize_string(&s, true).map_err(|error_type| {
372 CompileError {
373 line_num: token_info.line_num,
374 line_pos: token_info.line_pos,
375 error_type,
376 }
377 })?);
378 }
379 Token::Comma => (),
380 Token::BracketClose if !strings.is_empty() || allow_empty => break,
381 _ => return Err(token_info.expected("string or string list")),
382 }
383 }
384 Ok(strings)
385 }
386
387 #[inline(always)]
388 pub(crate) fn has_capability(&self, capability: &Capability) -> bool {
389 [&self.block]
390 .into_iter()
391 .chain(self.block_stack.iter())
392 .any(|b| b.capabilities.contains(capability))
393 || (capability != &Capability::Ihave && self.compiler.no_capability_check)
394 }
395
396 #[inline(always)]
397 pub(crate) fn reset_param_check(&mut self) {
398 self.param_check.fill(false);
399 }
400
401 #[inline(always)]
402 pub(crate) fn validate_argument(
403 &mut self,
404 arg_num: usize,
405 capability: Option<Capability>,
406 line_num: usize,
407 line_pos: usize,
408 ) -> Result<(), CompileError> {
409 if arg_num > 0 {
410 if let Some(param) = self.param_check.get_mut(arg_num - 1) {
411 if !*param {
412 *param = true;
413 } else {
414 return Err(CompileError {
415 line_num,
416 line_pos,
417 error_type: ErrorType::DuplicatedParameter,
418 });
419 }
420 } else {
421 #[cfg(test)]
422 panic!("Argument out of range {arg_num}");
423 }
424 }
425 if let Some(capability) = capability {
426 if !self.has_capability(&capability) {
427 return Err(CompileError {
428 line_num,
429 line_pos,
430 error_type: ErrorType::UndeclaredCapability(capability),
431 });
432 }
433 }
434
435 Ok(())
436 }
437
438 pub(crate) fn validate_match(
439 &mut self,
440 match_type: &MatchType,
441 key_list: &mut [Value],
442 ) -> Result<(), CompileError> {
443 if matches!(match_type, MatchType::Regex(_)) {
444 for key in key_list {
445 if let Value::Text(expr) = key {
446 match fancy_regex::Regex::new(expr) {
447 Ok(regex) => {
448 *key = Value::Regex(Regex {
449 regex,
450 expr: expr.to_string(),
451 });
452 }
453 Err(err) => {
454 return Err(self
455 .tokens
456 .unwrap_next()?
457 .custom(ErrorType::InvalidRegex(format!("{expr}: {err}"))));
458 }
459 }
460 }
461 }
462 }
463 Ok(())
464 }
465}
466
467impl Capability {
468 pub fn parse(capability: &str) -> Capability {
469 if let Some(capability) = CAPABILITIES.get(capability) {
470 capability.clone()
471 } else if let Some(comparator) = capability.strip_prefix("comparator-") {
472 Capability::Comparator(Comparator::Other(comparator.to_string()))
473 } else {
474 Capability::Other(capability.to_string())
475 }
476 }
477
478 pub fn all() -> &'static [Capability] {
479 &[
480 Capability::Envelope,
481 Capability::EnvelopeDsn,
482 Capability::EnvelopeDeliverBy,
483 Capability::FileInto,
484 Capability::EncodedCharacter,
485 Capability::Comparator(Comparator::Elbonia),
486 Capability::Comparator(Comparator::AsciiCaseMap),
487 Capability::Comparator(Comparator::AsciiNumeric),
488 Capability::Comparator(Comparator::Octet),
489 Capability::Body,
490 Capability::Convert,
491 Capability::Copy,
492 Capability::Relational,
493 Capability::Date,
494 Capability::Index,
495 Capability::Duplicate,
496 Capability::Variables,
497 Capability::EditHeader,
498 Capability::ForEveryPart,
499 Capability::Mime,
500 Capability::Replace,
501 Capability::Enclose,
502 Capability::ExtractText,
503 Capability::Enotify,
504 Capability::RedirectDsn,
505 Capability::RedirectDeliverBy,
506 Capability::Environment,
507 Capability::Reject,
508 Capability::Ereject,
509 Capability::ExtLists,
510 Capability::SubAddress,
511 Capability::Vacation,
512 Capability::VacationSeconds,
513 Capability::Fcc,
514 Capability::Mailbox,
515 Capability::MailboxId,
516 Capability::MboxMetadata,
517 Capability::ServerMetadata,
518 Capability::SpecialUse,
519 Capability::Imap4Flags,
520 Capability::Ihave,
521 Capability::ImapSieve,
522 Capability::Include,
523 Capability::Regex,
524 Capability::SpamTest,
525 Capability::SpamTestPlus,
526 Capability::VirusTest,
527 ]
528 }
529}
530
531static RELATIONAL: phf::Map<&'static str, RelationalMatch> = phf_map! {
532 "gt" => RelationalMatch::Gt,
533 "ge" => RelationalMatch::Ge,
534 "lt" => RelationalMatch::Lt,
535 "le" => RelationalMatch::Le,
536 "eq" => RelationalMatch::Eq,
537 "ne" => RelationalMatch::Ne,
538};
539
540static COMPARATOR: phf::Map<&'static str, Comparator> = phf_map! {
541 "i;octet" => Comparator::Octet,
542 "i;ascii-casemap" => Comparator::AsciiCaseMap,
543 "i;ascii-numeric" => Comparator::AsciiNumeric,
544};
545
546impl Invalid {
547 pub fn name(&self) -> &str {
548 &self.name
549 }
550
551 pub fn line_num(&self) -> usize {
552 self.line_num
553 }
554
555 pub fn line_pos(&self) -> usize {
556 self.line_pos
557 }
558}
559
560impl From<&str> for Capability {
561 fn from(value: &str) -> Self {
562 Capability::parse(value)
563 }
564}
565
566impl From<String> for Capability {
567 fn from(value: String) -> Self {
568 Capability::parse(&value)
569 }
570}
571
572impl Display for Capability {
573 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
574 match self {
575 Capability::Envelope => f.write_str("envelope"),
576 Capability::EnvelopeDsn => f.write_str("envelope-dsn"),
577 Capability::EnvelopeDeliverBy => f.write_str("envelope-deliverby"),
578 Capability::FileInto => f.write_str("fileinto"),
579 Capability::EncodedCharacter => f.write_str("encoded-character"),
580 Capability::Comparator(Comparator::Elbonia) => f.write_str("comparator-elbonia"),
581 Capability::Comparator(Comparator::Octet) => f.write_str("comparator-i;octet"),
582 Capability::Comparator(Comparator::AsciiCaseMap) => {
583 f.write_str("comparator-i;ascii-casemap")
584 }
585 Capability::Comparator(Comparator::AsciiNumeric) => {
586 f.write_str("comparator-i;ascii-numeric")
587 }
588 Capability::Comparator(Comparator::Other(comparator)) => f.write_str(comparator),
589 Capability::Body => f.write_str("body"),
590 Capability::Convert => f.write_str("convert"),
591 Capability::Copy => f.write_str("copy"),
592 Capability::Relational => f.write_str("relational"),
593 Capability::Date => f.write_str("date"),
594 Capability::Index => f.write_str("index"),
595 Capability::Duplicate => f.write_str("duplicate"),
596 Capability::Variables => f.write_str("variables"),
597 Capability::EditHeader => f.write_str("editheader"),
598 Capability::ForEveryPart => f.write_str("foreverypart"),
599 Capability::Mime => f.write_str("mime"),
600 Capability::Replace => f.write_str("replace"),
601 Capability::Enclose => f.write_str("enclose"),
602 Capability::ExtractText => f.write_str("extracttext"),
603 Capability::Enotify => f.write_str("enotify"),
604 Capability::RedirectDsn => f.write_str("redirect-dsn"),
605 Capability::RedirectDeliverBy => f.write_str("redirect-deliverby"),
606 Capability::Environment => f.write_str("environment"),
607 Capability::Reject => f.write_str("reject"),
608 Capability::Ereject => f.write_str("ereject"),
609 Capability::ExtLists => f.write_str("extlists"),
610 Capability::SubAddress => f.write_str("subaddress"),
611 Capability::Vacation => f.write_str("vacation"),
612 Capability::VacationSeconds => f.write_str("vacation-seconds"),
613 Capability::Fcc => f.write_str("fcc"),
614 Capability::Mailbox => f.write_str("mailbox"),
615 Capability::MailboxId => f.write_str("mailboxid"),
616 Capability::MboxMetadata => f.write_str("mboxmetadata"),
617 Capability::ServerMetadata => f.write_str("servermetadata"),
618 Capability::SpecialUse => f.write_str("special-use"),
619 Capability::Imap4Flags => f.write_str("imap4flags"),
620 Capability::Ihave => f.write_str("ihave"),
621 Capability::ImapSieve => f.write_str("imapsieve"),
622 Capability::Include => f.write_str("include"),
623 Capability::Regex => f.write_str("regex"),
624 Capability::SpamTest => f.write_str("spamtest"),
625 Capability::SpamTestPlus => f.write_str("spamtestplus"),
626 Capability::VirusTest => f.write_str("virustest"),
627 Capability::While => f.write_str("vnd.stalwart.while"),
628 Capability::Expressions => f.write_str("vnd.stalwart.expressions"),
629 Capability::Other(capability) => f.write_str(capability),
630 }
631 }
632}
633
634static CAPABILITIES: phf::Map<&'static str, Capability> = phf_map! {
635 "envelope" => Capability::Envelope,
636 "envelope-dsn" => Capability::EnvelopeDsn,
637 "envelope-deliverby" => Capability::EnvelopeDeliverBy,
638 "fileinto" => Capability::FileInto,
639 "encoded-character" => Capability::EncodedCharacter,
640 "comparator-elbonia" => Capability::Comparator(Comparator::Elbonia),
641 "comparator-i;octet" => Capability::Comparator(Comparator::Octet),
642 "comparator-i;ascii-casemap" => Capability::Comparator(Comparator::AsciiCaseMap),
643 "comparator-i;ascii-numeric" => Capability::Comparator(Comparator::AsciiNumeric),
644 "body" => Capability::Body,
645 "convert" => Capability::Convert,
646 "copy" => Capability::Copy,
647 "relational" => Capability::Relational,
648 "date" => Capability::Date,
649 "index" => Capability::Index,
650 "duplicate" => Capability::Duplicate,
651 "variables" => Capability::Variables,
652 "editheader" => Capability::EditHeader,
653 "foreverypart" => Capability::ForEveryPart,
654 "mime" => Capability::Mime,
655 "replace" => Capability::Replace,
656 "enclose" => Capability::Enclose,
657 "extracttext" => Capability::ExtractText,
658 "enotify" => Capability::Enotify,
659 "redirect-dsn" => Capability::RedirectDsn,
660 "redirect-deliverby" => Capability::RedirectDeliverBy,
661 "environment" => Capability::Environment,
662 "reject" => Capability::Reject,
663 "ereject" => Capability::Ereject,
664 "extlists" => Capability::ExtLists,
665 "subaddress" => Capability::SubAddress,
666 "vacation" => Capability::Vacation,
667 "vacation-seconds" => Capability::VacationSeconds,
668 "fcc" => Capability::Fcc,
669 "mailbox" => Capability::Mailbox,
670 "mailboxid" => Capability::MailboxId,
671 "mboxmetadata" => Capability::MboxMetadata,
672 "servermetadata" => Capability::ServerMetadata,
673 "special-use" => Capability::SpecialUse,
674 "imap4flags" => Capability::Imap4Flags,
675 "ihave" => Capability::Ihave,
676 "imapsieve" => Capability::ImapSieve,
677 "include" => Capability::Include,
678 "regex" => Capability::Regex,
679 "spamtest" => Capability::SpamTest,
680 "spamtestplus" => Capability::SpamTestPlus,
681 "virustest" => Capability::VirusTest,
682
683 "vnd.stalwart.while" => Capability::While,
685 "vnd.stalwart.expressions" => Capability::Expressions,
686};