sieve/compiler/grammar/tests/
test_spamtest.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    grammar::{instruction::CompilerState, Capability, Comparator},
28    lexer::{word::Word, Token},
29    CompileError, Value,
30};
31
32use crate::compiler::grammar::{test::Test, MatchType};
33
34/*
35           Usage:    spamtest [":percent"] [COMPARATOR] [MATCH-TYPE]
36                     <value: string>
37*/
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub struct TestSpamTest {
41    pub value: Value,
42    pub match_type: MatchType,
43    pub comparator: Comparator,
44    pub percent: bool,
45    pub is_not: bool,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49pub(crate) struct TestVirusTest {
50    pub value: Value,
51    pub match_type: MatchType,
52    pub comparator: Comparator,
53    pub is_not: bool,
54}
55
56impl<'x> CompilerState<'x> {
57    pub(crate) fn parse_test_spamtest(&mut self) -> Result<Test, CompileError> {
58        let mut match_type = MatchType::Is;
59        let mut comparator = Comparator::AsciiCaseMap;
60        let mut percent = false;
61        let value;
62
63        loop {
64            let token_info = self.tokens.unwrap_next()?;
65            match token_info.token {
66                Token::Tag(
67                    word @ (Word::Is
68                    | Word::Contains
69                    | Word::Matches
70                    | Word::Value
71                    | Word::Count
72                    | Word::Regex),
73                ) => {
74                    self.validate_argument(
75                        1,
76                        match word {
77                            Word::Value | Word::Count => Capability::Relational.into(),
78                            Word::Regex => Capability::Regex.into(),
79                            Word::List => Capability::ExtLists.into(),
80                            _ => None,
81                        },
82                        token_info.line_num,
83                        token_info.line_pos,
84                    )?;
85
86                    match_type = self.parse_match_type(word)?;
87                }
88                Token::Tag(Word::Comparator) => {
89                    self.validate_argument(2, None, token_info.line_num, token_info.line_pos)?;
90                    comparator = self.parse_comparator()?;
91                }
92                Token::Tag(Word::Percent) => {
93                    self.validate_argument(
94                        3,
95                        Capability::SpamTestPlus.into(),
96                        token_info.line_num,
97                        token_info.line_pos,
98                    )?;
99                    percent = true;
100                }
101                _ => {
102                    value = self.parse_string_token(token_info)?;
103                    break;
104                }
105            }
106        }
107
108        Ok(Test::SpamTest(TestSpamTest {
109            value,
110            percent,
111            match_type,
112            comparator,
113            is_not: false,
114        }))
115    }
116
117    pub(crate) fn parse_test_virustest(&mut self) -> Result<Test, CompileError> {
118        let mut match_type = MatchType::Is;
119        let mut comparator = Comparator::AsciiCaseMap;
120        let value;
121
122        loop {
123            let token_info = self.tokens.unwrap_next()?;
124            match token_info.token {
125                Token::Tag(
126                    word @ (Word::Is
127                    | Word::Contains
128                    | Word::Matches
129                    | Word::Value
130                    | Word::Count
131                    | Word::Regex),
132                ) => {
133                    self.validate_argument(
134                        1,
135                        match word {
136                            Word::Value | Word::Count => Capability::Relational.into(),
137                            Word::Regex => Capability::Regex.into(),
138                            Word::List => Capability::ExtLists.into(),
139                            _ => None,
140                        },
141                        token_info.line_num,
142                        token_info.line_pos,
143                    )?;
144
145                    match_type = self.parse_match_type(word)?;
146                }
147                Token::Tag(Word::Comparator) => {
148                    self.validate_argument(2, None, token_info.line_num, token_info.line_pos)?;
149                    comparator = self.parse_comparator()?;
150                }
151                _ => {
152                    value = self.parse_string_token(token_info)?;
153                    break;
154                }
155            }
156        }
157
158        Ok(Test::VirusTest(TestVirusTest {
159            value,
160            match_type,
161            comparator,
162            is_not: false,
163        }))
164    }
165}