Skip to main content

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