sieve/compiler/grammar/tests/
test_envelope.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
7
8
9use crate::{
10    compiler::{
11        grammar::{instruction::CompilerState, Capability, Comparator},
12        lexer::{word::Word, Token},
13        CompileError, ErrorType, Value,
14    },
15    Envelope,
16};
17
18use crate::compiler::grammar::{test::Test, AddressPart, MatchType};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[cfg_attr(
22    any(test, feature = "serde"),
23    derive(serde::Serialize, serde::Deserialize)
24)]
25#[cfg_attr(
26    feature = "rkyv",
27    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
28)]
29pub(crate) struct TestEnvelope {
30    pub envelope_list: Vec<Envelope>,
31    pub key_list: Vec<Value>,
32    pub address_part: AddressPart,
33    pub match_type: MatchType,
34    pub comparator: Comparator,
35    pub zone: Option<i64>,
36    pub is_not: bool,
37}
38
39impl CompilerState<'_> {
40    pub(crate) fn parse_test_envelope(&mut self) -> Result<Test, CompileError> {
41        let mut address_part = AddressPart::All;
42        let mut match_type = MatchType::Is;
43        let mut comparator = Comparator::AsciiCaseMap;
44        let mut envelope_list = None;
45        let mut key_list;
46        let mut zone = None;
47
48        loop {
49            let mut token_info = self.tokens.unwrap_next()?;
50            match token_info.token {
51                Token::Tag(
52                    word @ (Word::LocalPart | Word::Domain | Word::All | Word::User | Word::Detail),
53                ) => {
54                    self.validate_argument(
55                        1,
56                        if matches!(word, Word::User | Word::Detail) {
57                            Capability::SubAddress.into()
58                        } else {
59                            None
60                        },
61                        token_info.line_num,
62                        token_info.line_pos,
63                    )?;
64                    address_part = word.into();
65                }
66                Token::Tag(
67                    word @ (Word::Is
68                    | Word::Contains
69                    | Word::Matches
70                    | Word::Value
71                    | Word::Count
72                    | Word::Regex
73                    | Word::List),
74                ) => {
75                    self.validate_argument(
76                        2,
77                        match word {
78                            Word::Value | Word::Count => Capability::Relational.into(),
79                            Word::Regex => Capability::Regex.into(),
80                            Word::List => Capability::ExtLists.into(),
81                            _ => None,
82                        },
83                        token_info.line_num,
84                        token_info.line_pos,
85                    )?;
86
87                    match_type = self.parse_match_type(word)?;
88                }
89                Token::Tag(Word::Comparator) => {
90                    self.validate_argument(3, None, token_info.line_num, token_info.line_pos)?;
91                    comparator = self.parse_comparator()?;
92                }
93                Token::Tag(Word::Zone) => {
94                    self.validate_argument(
95                        4,
96                        Capability::EnvelopeDeliverBy.into(),
97                        token_info.line_num,
98                        token_info.line_pos,
99                    )?;
100                    zone = self.parse_timezone()?.into();
101                }
102                _ => {
103                    if envelope_list.is_none() {
104                        let mut envelopes = Vec::new();
105                        let line_num = token_info.line_num;
106                        let line_pos = token_info.line_pos;
107
108                        match token_info.token {
109                            Token::StringConstant(s) => {
110                                match Envelope::try_from(s.into_string().to_ascii_lowercase()) {
111                                    Ok(envelope) => {
112                                        envelopes.push(envelope);
113                                    }
114                                    Err(invalid) => {
115                                        token_info.token = Token::Comma;
116                                        return Err(
117                                            token_info.custom(ErrorType::InvalidEnvelope(invalid))
118                                        );
119                                    }
120                                }
121                            }
122                            Token::BracketOpen => loop {
123                                let mut token_info = self.tokens.unwrap_next()?;
124                                match token_info.token {
125                                    Token::StringConstant(s) => {
126                                        match Envelope::try_from(
127                                            s.into_string().to_ascii_lowercase(),
128                                        ) {
129                                            Ok(envelope) => {
130                                                if !envelopes.contains(&envelope) {
131                                                    envelopes.push(envelope);
132                                                }
133                                            }
134                                            Err(invalid) => {
135                                                token_info.token = Token::Comma;
136                                                return Err(token_info
137                                                    .custom(ErrorType::InvalidEnvelope(invalid)));
138                                            }
139                                        }
140                                    }
141                                    Token::Comma => (),
142                                    Token::BracketClose if !envelopes.is_empty() => break,
143                                    _ => return Err(token_info.expected("constant string")),
144                                }
145                            },
146                            _ => return Err(token_info.expected("constant string")),
147                        }
148
149                        for envelope in &envelopes {
150                            match envelope {
151                                Envelope::ByTimeAbsolute
152                                | Envelope::ByTimeRelative
153                                | Envelope::ByMode
154                                | Envelope::ByTrace => {
155                                    self.validate_argument(
156                                        0,
157                                        Capability::EnvelopeDeliverBy.into(),
158                                        line_num,
159                                        line_pos,
160                                    )?;
161                                }
162
163                                Envelope::Notify
164                                | Envelope::Orcpt
165                                | Envelope::Ret
166                                | Envelope::Envid => {
167                                    self.validate_argument(
168                                        0,
169                                        Capability::EnvelopeDsn.into(),
170                                        line_num,
171                                        line_pos,
172                                    )?;
173                                }
174                                _ => (),
175                            }
176                        }
177
178                        envelope_list = envelopes.into();
179                    } else {
180                        key_list = self.parse_strings_token(token_info)?;
181                        break;
182                    }
183                }
184            }
185        }
186        self.validate_match(&match_type, &mut key_list)?;
187
188        Ok(Test::Envelope(TestEnvelope {
189            envelope_list: envelope_list.unwrap(),
190            key_list,
191            address_part,
192            match_type,
193            comparator,
194            zone,
195            is_not: false,
196        }))
197    }
198}
199
200impl TryFrom<String> for Envelope {
201    type Error = String;
202
203    fn try_from(value: String) -> Result<Self, Self::Error> {
204        if let Some(envelope) = lookup_envelope(&value) {
205            Ok(envelope)
206        } else {
207            Err(value)
208        }
209    }
210}
211
212impl<'x> TryFrom<&'x str> for Envelope {
213    type Error = &'x str;
214
215    fn try_from(value: &'x str) -> Result<Self, Self::Error> {
216        if let Some(envelope) = lookup_envelope(value) {
217            Ok(envelope)
218        } else {
219            Err(value)
220        }
221    }
222}
223
224fn lookup_envelope(input: &str) -> Option<Envelope> {
225    hashify::tiny_map!(
226        input.as_bytes(),
227        "from" => Envelope::From,
228        "to" => Envelope::To,
229        "bytimeabsolute" => Envelope::ByTimeAbsolute,
230        "bytimerelative" => Envelope::ByTimeRelative,
231        "bymode" => Envelope::ByMode,
232        "bytrace" => Envelope::ByTrace,
233        "notify" => Envelope::Notify,
234        "orcpt" => Envelope::Orcpt,
235        "ret" => Envelope::Ret,
236        "envid" => Envelope::Envid,
237    )
238}