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