sieve/compiler/grammar/tests/
test_header.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 mail_parser::HeaderName;
25use serde::{Deserialize, Serialize};
26
27use crate::compiler::{
28    grammar::{
29        actions::action_mime::MimeOpts,
30        instruction::{CompilerState, MapLocalVars},
31        Capability, Comparator,
32    },
33    lexer::{word::Word, Token},
34    CompileError, ErrorType, Value,
35};
36
37use crate::compiler::grammar::{test::Test, MatchType};
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub struct TestHeader {
41    pub header_list: Vec<Value>,
42    pub key_list: Vec<Value>,
43    pub match_type: MatchType,
44    pub comparator: Comparator,
45    pub index: Option<i32>,
46
47    pub mime_opts: MimeOpts<Value>,
48    pub mime_anychild: bool,
49    pub is_not: bool,
50}
51
52impl<'x> CompilerState<'x> {
53    pub(crate) fn parse_test_header(&mut self) -> Result<Test, CompileError> {
54        let mut match_type = MatchType::Is;
55        let mut comparator = Comparator::AsciiCaseMap;
56        let mut header_list = None;
57        let mut key_list;
58        let mut index = None;
59        let mut index_last = false;
60
61        let mut mime = false;
62        let mut mime_opts = MimeOpts::None;
63        let mut mime_anychild = false;
64
65        loop {
66            let token_info = self.tokens.unwrap_next()?;
67            match token_info.token {
68                Token::Tag(
69                    word @ (Word::Is
70                    | Word::Contains
71                    | Word::Matches
72                    | Word::Value
73                    | Word::Count
74                    | Word::Regex
75                    | Word::List),
76                ) => {
77                    self.validate_argument(
78                        1,
79                        match word {
80                            Word::Value | Word::Count => Capability::Relational.into(),
81                            Word::Regex => Capability::Regex.into(),
82                            Word::List => Capability::ExtLists.into(),
83                            _ => None,
84                        },
85                        token_info.line_num,
86                        token_info.line_pos,
87                    )?;
88
89                    match_type = self.parse_match_type(word)?;
90                }
91                Token::Tag(Word::Comparator) => {
92                    self.validate_argument(2, None, token_info.line_num, token_info.line_pos)?;
93
94                    comparator = self.parse_comparator()?;
95                }
96                Token::Tag(Word::Index) => {
97                    self.validate_argument(
98                        3,
99                        Capability::Index.into(),
100                        token_info.line_num,
101                        token_info.line_pos,
102                    )?;
103
104                    index = (self.tokens.expect_number(u16::MAX as usize)? as i32).into();
105                }
106                Token::Tag(Word::Last) => {
107                    self.validate_argument(
108                        4,
109                        Capability::Index.into(),
110                        token_info.line_num,
111                        token_info.line_pos,
112                    )?;
113
114                    index_last = true;
115                }
116                Token::Tag(Word::Mime) => {
117                    self.validate_argument(
118                        5,
119                        Capability::Mime.into(),
120                        token_info.line_num,
121                        token_info.line_pos,
122                    )?;
123                    mime = true;
124                }
125                Token::Tag(Word::AnyChild) => {
126                    self.validate_argument(
127                        6,
128                        Capability::Mime.into(),
129                        token_info.line_num,
130                        token_info.line_pos,
131                    )?;
132                    mime_anychild = true;
133                }
134                Token::Tag(
135                    word @ (Word::Type | Word::Subtype | Word::ContentType | Word::Param),
136                ) => {
137                    self.validate_argument(
138                        7,
139                        Capability::Mime.into(),
140                        token_info.line_num,
141                        token_info.line_pos,
142                    )?;
143                    mime_opts = self.parse_mimeopts(word)?;
144                }
145                _ => {
146                    if header_list.is_none() {
147                        let headers = self.parse_strings_token(token_info)?;
148                        for header in &headers {
149                            if let Value::Text(header_name) = &header {
150                                if HeaderName::parse(header_name.as_ref()).is_none() {
151                                    return Err(self
152                                        .tokens
153                                        .unwrap_next()?
154                                        .custom(ErrorType::InvalidHeaderName));
155                                }
156                            }
157                        }
158                        header_list = headers.into();
159                    } else {
160                        key_list = self.parse_strings_token(token_info)?;
161                        break;
162                    }
163                }
164            }
165        }
166
167        if !mime && (mime_anychild || mime_opts != MimeOpts::None) {
168            return Err(self.tokens.unwrap_next()?.missing_tag(":mime"));
169        }
170        self.validate_match(&match_type, &mut key_list)?;
171
172        Ok(Test::Header(TestHeader {
173            header_list: header_list.unwrap(),
174            key_list,
175            match_type,
176            comparator,
177            index: if index_last { index.map(|i| -i) } else { index },
178            mime_opts,
179            mime_anychild,
180            is_not: false,
181        }))
182    }
183}
184
185impl MapLocalVars for MimeOpts<Value> {
186    fn map_local_vars(&mut self, last_id: usize) {
187        if let MimeOpts::Param(value) = self {
188            value.map_local_vars(last_id)
189        }
190    }
191}