sieve/compiler/grammar/
test.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
5 */
6
7use crate::compiler::{
8    lexer::{tokenizer::TokenInfo, word::Word, Token},
9    CompileError, ErrorType,
10};
11
12use super::{
13    actions::{action_convert::Convert, action_vacation::TestVacation},
14    expr::{parser::ExpressionParser, tokenizer::Tokenizer, Expression, UnaryOperator},
15    instruction::{CompilerState, Instruction},
16    tests::{
17        test_address::TestAddress,
18        test_body::TestBody,
19        test_date::{TestCurrentDate, TestDate},
20        test_duplicate::TestDuplicate,
21        test_envelope::TestEnvelope,
22        test_exists::TestExists,
23        test_extlists::TestValidExtList,
24        test_hasflag::TestHasFlag,
25        test_header::TestHeader,
26        test_ihave::TestIhave,
27        test_mailbox::{TestMailboxExists, TestMetadata, TestMetadataExists},
28        test_mailboxid::TestMailboxIdExists,
29        test_notify::{TestNotifyMethodCapability, TestValidNotifyMethod},
30        test_size::TestSize,
31        test_spamtest::{TestSpamTest, TestVirusTest},
32        test_specialuse::TestSpecialUseExists,
33        test_string::TestString,
34    },
35    Capability, Invalid,
36};
37
38#[allow(clippy::enum_variant_names)]
39#[derive(Debug, Clone, PartialEq, Eq)]
40#[cfg_attr(
41    any(test, feature = "serde"),
42    derive(serde::Serialize, serde::Deserialize)
43)]
44#[cfg_attr(
45    feature = "rkyv",
46    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
47)]
48pub(crate) enum Test {
49    True,
50    False,
51    Address(TestAddress),
52    Envelope(TestEnvelope),
53    Exists(TestExists),
54    Header(TestHeader),
55    Size(TestSize),
56    Invalid(Invalid),
57
58    // RFC 5173
59    Body(TestBody),
60
61    // RFC 6558
62    Convert(Convert),
63
64    // RFC 5260
65    Date(TestDate),
66    CurrentDate(TestCurrentDate),
67
68    // RFC 7352
69    Duplicate(TestDuplicate),
70
71    // RFC 5229 & RFC 5183
72    String(TestString),
73    Environment(TestString),
74
75    // RFC 5435
76    NotifyMethodCapability(TestNotifyMethodCapability),
77    ValidNotifyMethod(TestValidNotifyMethod),
78
79    // RFC 6134
80    ValidExtList(TestValidExtList),
81
82    // RFC 5463
83    Ihave(TestIhave),
84
85    // RFC 5232
86    HasFlag(TestHasFlag),
87
88    // RFC 5490
89    MailboxExists(TestMailboxExists),
90    Metadata(TestMetadata),
91    MetadataExists(TestMetadataExists),
92
93    // RFC 9042
94    MailboxIdExists(TestMailboxIdExists),
95
96    // RFC 5235
97    SpamTest(TestSpamTest),
98    VirusTest(TestVirusTest),
99
100    // RFC 8579
101    SpecialUseExists(TestSpecialUseExists),
102
103    // RFC 5230
104    Vacation(TestVacation),
105
106    // Only test
107    #[cfg(test)]
108    TestCmd {
109        arguments: Vec<crate::compiler::Value>,
110        is_not: bool,
111    },
112}
113
114#[derive(Debug)]
115struct Block {
116    is_all: bool,
117    is_not: bool,
118    p_count: u32,
119    jmps: Vec<usize>,
120}
121
122impl CompilerState<'_> {
123    pub(crate) fn parse_test(&mut self) -> Result<(), CompileError> {
124        let mut block_stack: Vec<Block> = Vec::new();
125        let mut block = Block {
126            is_all: false,
127            is_not: false,
128            p_count: 0,
129            jmps: Vec::new(),
130        };
131        let mut is_not = false;
132
133        loop {
134            let token_info = self.tokens.unwrap_next()?;
135            self.reset_param_check();
136            let test: Instruction =
137                match token_info.token {
138                    Token::Comma
139                        if !block_stack.is_empty()
140                            && matches!(
141                                self.instructions.last(),
142                                Some(Instruction::Test(_) | Instruction::Eval(_))
143                            )
144                            && matches!(
145                                self.tokens.peek(),
146                                Some(Ok(TokenInfo {
147                                    token: Token::Identifier(_) | Token::Unknown(_),
148                                    ..
149                                }))
150                            ) =>
151                    {
152                        is_not = block.is_not;
153                        block.jmps.push(self.instructions.len());
154                        self.instructions.push(if block.is_all {
155                            Instruction::Jz(usize::MAX)
156                        } else {
157                            Instruction::Jnz(usize::MAX)
158                        });
159                        continue;
160                    }
161                    Token::ParenthesisOpen => {
162                        block.p_count += 1;
163                        continue;
164                    }
165                    Token::ParenthesisClose => {
166                        if block.p_count > 0 {
167                            block.p_count -= 1;
168                            continue;
169                        } else if let Some(prev_block) = block_stack.pop() {
170                            let cur_pos = self.instructions.len();
171                            for jmp_pos in block.jmps {
172                                if let Instruction::Jnz(jmp_pos) | Instruction::Jz(jmp_pos) =
173                                    &mut self.instructions[jmp_pos]
174                                {
175                                    *jmp_pos = cur_pos;
176                                } else {
177                                    debug_assert!(false, "This should not have happened")
178                                }
179                            }
180
181                            block = prev_block;
182                            is_not = block.is_not;
183                            if block_stack.is_empty() {
184                                break;
185                            } else {
186                                continue;
187                            }
188                        } else {
189                            return Err(token_info.expected("test name"));
190                        }
191                    }
192                    Token::Identifier(Word::Not) => {
193                        if !matches!(
194                            self.tokens.peek(),
195                            Some(Ok(TokenInfo {
196                                token: Token::Identifier(_) | Token::Unknown(_),
197                                ..
198                            }))
199                        ) {
200                            return Err(token_info.expected("test name"));
201                        }
202                        is_not = !is_not;
203                        continue;
204                    }
205                    Token::Identifier(word @ (Word::AnyOf | Word::AllOf)) => {
206                        if block_stack.len() < self.tokens.compiler.max_nested_tests {
207                            self.tokens.expect_token(Token::ParenthesisOpen)?;
208                            block_stack.push(block);
209                            let (is_all, block_is_not) = if word == Word::AllOf {
210                                if !is_not {
211                                    (true, false)
212                                } else {
213                                    (false, true)
214                                }
215                            } else if !is_not {
216                                (false, false)
217                            } else {
218                                (true, true)
219                            };
220                            block = Block {
221                                is_all,
222                                is_not: block_is_not,
223                                p_count: 0,
224                                jmps: Vec::new(),
225                            };
226                            is_not = block_is_not;
227                            continue;
228                        } else {
229                            return Err(CompileError {
230                                line_num: token_info.line_num,
231                                line_pos: token_info.line_pos,
232                                error_type: ErrorType::TooManyNestedTests,
233                            });
234                        }
235                    }
236                    Token::Identifier(Word::True) => if !is_not {
237                        Test::True
238                    } else {
239                        is_not = false;
240                        Test::False
241                    }
242                    .into(),
243                    Token::Identifier(Word::False) => if !is_not {
244                        Test::False
245                    } else {
246                        is_not = false;
247                        Test::True
248                    }
249                    .into(),
250                    Token::Identifier(Word::Address) => self.parse_test_address()?.into(),
251                    Token::Identifier(Word::Envelope) => {
252                        self.validate_argument(
253                            0,
254                            Capability::Envelope.into(),
255                            token_info.line_num,
256                            token_info.line_pos,
257                        )?;
258                        self.parse_test_envelope()?.into()
259                    }
260                    Token::Identifier(Word::Header) => self.parse_test_header()?.into(),
261                    Token::Identifier(Word::Size) => self.parse_test_size()?.into(),
262                    Token::Identifier(Word::Exists) => self.parse_test_exists()?.into(),
263
264                    // RFC 5173
265                    Token::Identifier(Word::Body) => {
266                        self.validate_argument(
267                            0,
268                            Capability::Body.into(),
269                            token_info.line_num,
270                            token_info.line_pos,
271                        )?;
272                        self.parse_test_body()?.into()
273                    }
274
275                    // RFC 6558
276                    Token::Identifier(Word::Convert) => {
277                        self.validate_argument(
278                            0,
279                            Capability::Convert.into(),
280                            token_info.line_num,
281                            token_info.line_pos,
282                        )?;
283                        self.parse_test_convert()?.into()
284                    }
285
286                    // RFC 5260
287                    Token::Identifier(Word::Date) => {
288                        self.validate_argument(
289                            0,
290                            Capability::Date.into(),
291                            token_info.line_num,
292                            token_info.line_pos,
293                        )?;
294                        self.parse_test_date()?.into()
295                    }
296                    Token::Identifier(Word::CurrentDate) => {
297                        self.validate_argument(
298                            0,
299                            Capability::Date.into(),
300                            token_info.line_num,
301                            token_info.line_pos,
302                        )?;
303                        self.parse_test_currentdate()?.into()
304                    }
305
306                    // RFC 7352
307                    Token::Identifier(Word::Duplicate) => {
308                        self.validate_argument(
309                            0,
310                            Capability::Duplicate.into(),
311                            token_info.line_num,
312                            token_info.line_pos,
313                        )?;
314                        self.parse_test_duplicate()?.into()
315                    }
316
317                    // RFC 5229
318                    Token::Identifier(Word::String) => {
319                        self.validate_argument(
320                            0,
321                            Capability::Variables.into(),
322                            token_info.line_num,
323                            token_info.line_pos,
324                        )?;
325                        self.parse_test_string()?.into()
326                    }
327
328                    // RFC 5435
329                    Token::Identifier(Word::NotifyMethodCapability) => {
330                        self.validate_argument(
331                            0,
332                            Capability::Enotify.into(),
333                            token_info.line_num,
334                            token_info.line_pos,
335                        )?;
336                        self.parse_test_notify_method_capability()?.into()
337                    }
338                    Token::Identifier(Word::ValidNotifyMethod) => {
339                        self.validate_argument(
340                            0,
341                            Capability::Enotify.into(),
342                            token_info.line_num,
343                            token_info.line_pos,
344                        )?;
345                        self.parse_test_valid_notify_method()?.into()
346                    }
347
348                    // RFC 5183
349                    Token::Identifier(Word::Environment) => {
350                        self.validate_argument(
351                            0,
352                            Capability::Environment.into(),
353                            token_info.line_num,
354                            token_info.line_pos,
355                        )?;
356                        self.parse_test_environment()?.into()
357                    }
358
359                    // RFC 6134
360                    Token::Identifier(Word::ValidExtList) => {
361                        self.validate_argument(
362                            0,
363                            Capability::ExtLists.into(),
364                            token_info.line_num,
365                            token_info.line_pos,
366                        )?;
367                        self.parse_test_valid_ext_list()?.into()
368                    }
369
370                    // RFC 5463
371                    Token::Identifier(Word::Ihave) => {
372                        self.validate_argument(
373                            0,
374                            Capability::Ihave.into(),
375                            token_info.line_num,
376                            token_info.line_pos,
377                        )?;
378                        self.parse_test_ihave()?.into()
379                    }
380
381                    // RFC 5232
382                    Token::Identifier(Word::HasFlag) => {
383                        self.validate_argument(
384                            0,
385                            Capability::Imap4Flags.into(),
386                            token_info.line_num,
387                            token_info.line_pos,
388                        )?;
389                        self.parse_test_hasflag()?.into()
390                    }
391
392                    // RFC 5490
393                    Token::Identifier(Word::MailboxExists) => {
394                        self.validate_argument(
395                            0,
396                            Capability::Mailbox.into(),
397                            token_info.line_num,
398                            token_info.line_pos,
399                        )?;
400                        self.parse_test_mailboxexists()?.into()
401                    }
402                    Token::Identifier(Word::Metadata) => {
403                        self.validate_argument(
404                            0,
405                            Capability::MboxMetadata.into(),
406                            token_info.line_num,
407                            token_info.line_pos,
408                        )?;
409                        self.parse_test_metadata()?.into()
410                    }
411                    Token::Identifier(Word::MetadataExists) => {
412                        self.validate_argument(
413                            0,
414                            Capability::MboxMetadata.into(),
415                            token_info.line_num,
416                            token_info.line_pos,
417                        )?;
418                        self.parse_test_metadataexists()?.into()
419                    }
420                    Token::Identifier(Word::ServerMetadata) => {
421                        self.validate_argument(
422                            0,
423                            Capability::ServerMetadata.into(),
424                            token_info.line_num,
425                            token_info.line_pos,
426                        )?;
427                        self.parse_test_servermetadata()?.into()
428                    }
429                    Token::Identifier(Word::ServerMetadataExists) => {
430                        self.validate_argument(
431                            0,
432                            Capability::ServerMetadata.into(),
433                            token_info.line_num,
434                            token_info.line_pos,
435                        )?;
436                        self.parse_test_servermetadataexists()?.into()
437                    }
438
439                    // RFC 9042
440                    Token::Identifier(Word::MailboxIdExists) => {
441                        self.validate_argument(
442                            0,
443                            Capability::MailboxId.into(),
444                            token_info.line_num,
445                            token_info.line_pos,
446                        )?;
447                        self.parse_test_mailboxidexists()?.into()
448                    }
449
450                    // RFC 5235
451                    Token::Identifier(Word::SpamTest) => {
452                        self.validate_argument(
453                            0,
454                            Capability::SpamTest.into(),
455                            token_info.line_num,
456                            token_info.line_pos,
457                        )?;
458                        self.parse_test_spamtest()?.into()
459                    }
460                    Token::Identifier(Word::VirusTest) => {
461                        self.validate_argument(
462                            0,
463                            Capability::VirusTest.into(),
464                            token_info.line_num,
465                            token_info.line_pos,
466                        )?;
467                        self.parse_test_virustest()?.into()
468                    }
469
470                    // RFC 8579
471                    Token::Identifier(Word::SpecialUseExists) => {
472                        self.validate_argument(
473                            0,
474                            Capability::SpecialUse.into(),
475                            token_info.line_num,
476                            token_info.line_pos,
477                        )?;
478                        self.parse_test_specialuseexists()?.into()
479                    }
480
481                    // Expressions extension
482                    Token::Identifier(Word::Eval) => {
483                        self.validate_argument(
484                            0,
485                            Capability::Expressions.into(),
486                            token_info.line_num,
487                            token_info.line_pos,
488                        )?;
489
490                        Instruction::Eval(self.parse_expr()?)
491                    }
492                    Token::Identifier(word) => {
493                        self.ignore_test()?;
494                        Test::Invalid(Invalid {
495                            name: word.to_string(),
496                            line_num: token_info.line_num,
497                            line_pos: token_info.line_pos,
498                        })
499                        .into()
500                    }
501                    #[cfg(test)]
502                    Token::Unknown(name) if name.contains("test") => {
503                        use crate::compiler::Value;
504
505                        let mut arguments = Vec::new();
506                        arguments.push(Value::Text(name.into()));
507                        while !matches!(
508                            self.tokens.peek().map(|r| r.map(|t| &t.token)),
509                            Some(Ok(Token::Comma
510                                | Token::ParenthesisClose
511                                | Token::CurlyOpen))
512                        ) {
513                            arguments.push(match self.tokens.unwrap_next()?.token {
514                                Token::StringConstant(s) => Value::from(s),
515                                Token::StringVariable(s) => self
516                                    .tokenize_string(&s, true)
517                                    .map_err(|error_type| CompileError {
518                                        line_num: 0,
519                                        line_pos: 0,
520                                        error_type,
521                                    })?,
522                                Token::Number(n) => {
523                                    Value::Number(crate::compiler::Number::Integer(n as i64))
524                                }
525                                Token::Identifier(s) => Value::Text(s.to_string().into()),
526                                Token::Tag(s) => Value::Text(format!(":{s}").into()),
527                                Token::Unknown(s) => Value::Text(s.into()),
528                                other => panic!("Invalid test param {other:?}"),
529                            });
530                        }
531                        Test::TestCmd {
532                            arguments,
533                            is_not: false,
534                        }
535                        .into()
536                    }
537                    Token::Unknown(name) => {
538                        self.ignore_test()?;
539                        Test::Invalid(Invalid {
540                            name,
541                            line_num: token_info.line_num,
542                            line_pos: token_info.line_pos,
543                        })
544                        .into()
545                    }
546                    _ => return Err(token_info.expected("test name")),
547                };
548
549            while block.p_count > 0 {
550                self.tokens.expect_token(Token::ParenthesisClose)?;
551                block.p_count -= 1;
552            }
553
554            self.instructions
555                .push(if !is_not { test } else { test.set_not() });
556
557            if block_stack.is_empty() {
558                break;
559            }
560        }
561
562        self.instructions.push(Instruction::Jz(usize::MAX));
563        Ok(())
564    }
565
566    pub(crate) fn parse_expr(&mut self) -> Result<Vec<Expression>, CompileError> {
567        let mut next_token = self.tokens.unwrap_next()?;
568        let expr = match next_token.token {
569            Token::StringConstant(s) => s.into_string().into_bytes(),
570            Token::StringVariable(s) => s,
571            _ => return Err(next_token.expected("string")),
572        };
573
574        match ExpressionParser::from_tokenizer(Tokenizer::from_iter(
575            expr.iter().enumerate().peekable(),
576            |var_name, maybe_namespace| self.parse_expr_fnc_or_var(var_name, maybe_namespace),
577        ))
578        .parse()
579        {
580            Ok(parser) => Ok(parser.output),
581            Err(err) => {
582                let err = ErrorType::InvalidExpression(format!(
583                    "{}: {}",
584                    std::str::from_utf8(&expr).unwrap_or_default(),
585                    err
586                ));
587                next_token.token = Token::StringVariable(expr);
588                Err(next_token.custom(err))
589            }
590        }
591    }
592}
593
594impl From<Test> for Instruction {
595    fn from(test: Test) -> Self {
596        Instruction::Test(test)
597    }
598}
599
600impl Instruction {
601    pub fn set_not(mut self) -> Self {
602        match &mut self {
603            Instruction::Test(test) => match test {
604                Test::True => return Instruction::Test(Test::False),
605                Test::False => return Instruction::Test(Test::True),
606                Test::Address(op) => {
607                    op.is_not = true;
608                }
609                Test::Envelope(op) => {
610                    op.is_not = true;
611                }
612                Test::Exists(op) => {
613                    op.is_not = true;
614                }
615                Test::Header(op) => {
616                    op.is_not = true;
617                }
618                Test::Size(op) => {
619                    op.is_not = true;
620                }
621                Test::Body(op) => {
622                    op.is_not = true;
623                }
624                Test::Convert(op) => {
625                    op.is_not = true;
626                }
627                Test::Date(op) => {
628                    op.is_not = true;
629                }
630                Test::CurrentDate(op) => {
631                    op.is_not = true;
632                }
633                Test::Duplicate(op) => {
634                    op.is_not = true;
635                }
636                Test::String(op) | Test::Environment(op) => {
637                    op.is_not = true;
638                }
639                Test::NotifyMethodCapability(op) => {
640                    op.is_not = true;
641                }
642                Test::ValidNotifyMethod(op) => {
643                    op.is_not = true;
644                }
645                Test::ValidExtList(op) => {
646                    op.is_not = true;
647                }
648                Test::Ihave(op) => {
649                    op.is_not = true;
650                }
651                Test::HasFlag(op) => {
652                    op.is_not = true;
653                }
654                Test::MailboxExists(op) => {
655                    op.is_not = true;
656                }
657                Test::Metadata(op) => {
658                    op.is_not = true;
659                }
660                Test::MetadataExists(op) => {
661                    op.is_not = true;
662                }
663                Test::MailboxIdExists(op) => {
664                    op.is_not = true;
665                }
666                Test::SpamTest(op) => {
667                    op.is_not = true;
668                }
669                Test::VirusTest(op) => {
670                    op.is_not = true;
671                }
672                Test::SpecialUseExists(op) => {
673                    op.is_not = true;
674                }
675                #[cfg(test)]
676                Test::TestCmd { is_not, .. } => {
677                    *is_not = true;
678                }
679                Test::Vacation(_) | Test::Invalid(_) => {}
680            },
681            Instruction::Eval(expr) => expr.push(Expression::UnaryOperator(UnaryOperator::Not)),
682            _ => (),
683        }
684        self
685    }
686}