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