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