sieve/compiler/grammar/
test.rs

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