sieve/compiler/grammar/actions/
action_redirect.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::compiler::{
8    grammar::{
9        instruction::{CompilerState, Instruction, MapLocalVars},
10        Capability,
11    },
12    lexer::{word::Word, Token},
13    CompileError, Value,
14};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17#[cfg_attr(
18    any(test, feature = "serde"),
19    derive(serde::Serialize, serde::Deserialize)
20)]
21#[cfg_attr(
22    feature = "rkyv",
23    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
24)]
25pub(crate) struct Redirect {
26    pub copy: bool,
27    pub address: Value,
28    pub notify: Notify,
29    pub return_of_content: Ret,
30    pub by_time: ByTime<Value>,
31    pub list: bool,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Hash)]
35#[cfg_attr(
36    any(test, feature = "serde"),
37    derive(serde::Serialize, serde::Deserialize)
38)]
39#[cfg_attr(
40    feature = "rkyv",
41    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
42)]
43pub enum NotifyItem {
44    Success,
45    Failure,
46    Delay,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Hash)]
50#[cfg_attr(
51    any(test, feature = "serde"),
52    derive(serde::Serialize, serde::Deserialize)
53)]
54#[cfg_attr(
55    feature = "rkyv",
56    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
57)]
58pub enum Notify {
59    Never,
60    Items(Vec<NotifyItem>),
61    Default,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Hash)]
65#[cfg_attr(
66    any(test, feature = "serde"),
67    derive(serde::Serialize, serde::Deserialize)
68)]
69#[cfg_attr(
70    feature = "rkyv",
71    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
72)]
73pub enum Ret {
74    Full,
75    Hdrs,
76    Default,
77}
78
79/*
80
81   Usage:   redirect [:bytimerelative <rlimit: number> /
82                      :bytimeabsolute <alimit:string>
83                      [:bymode "notify"|"return"] [:bytrace]]
84                     <address: string>
85
86*/
87
88#[derive(Debug, Clone, PartialEq, Eq, Hash)]
89#[cfg_attr(
90    any(test, feature = "serde"),
91    derive(serde::Serialize, serde::Deserialize)
92)]
93#[cfg_attr(
94    feature = "rkyv",
95    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
96)]
97pub enum ByTime<T> {
98    Relative {
99        rlimit: u64,
100        mode: ByMode,
101        trace: bool,
102    },
103    Absolute {
104        alimit: T,
105        mode: ByMode,
106        trace: bool,
107    },
108    None,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Hash)]
112#[cfg_attr(
113    any(test, feature = "serde"),
114    derive(serde::Serialize, serde::Deserialize)
115)]
116#[cfg_attr(
117    feature = "rkyv",
118    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
119)]
120pub enum ByMode {
121    Notify,
122    Return,
123    Default,
124}
125
126impl CompilerState<'_> {
127    pub(crate) fn parse_redirect(&mut self) -> Result<(), CompileError> {
128        let address;
129        let mut copy = false;
130        let mut ret = Ret::Default;
131        let mut notify = Notify::Default;
132        let mut list = false;
133        let mut by_mode = ByMode::Default;
134        let mut by_trace = false;
135        let mut by_rlimit = None;
136        let mut by_alimit = None;
137
138        loop {
139            let token_info = self.tokens.unwrap_next()?;
140            match token_info.token {
141                Token::Tag(Word::Copy) => {
142                    self.validate_argument(
143                        1,
144                        Capability::Copy.into(),
145                        token_info.line_num,
146                        token_info.line_pos,
147                    )?;
148                    copy = true;
149                }
150                Token::Tag(Word::List) => {
151                    self.validate_argument(
152                        2,
153                        Capability::ExtLists.into(),
154                        token_info.line_num,
155                        token_info.line_pos,
156                    )?;
157                    list = true;
158                }
159                Token::Tag(Word::ByTrace) => {
160                    self.validate_argument(
161                        3,
162                        Capability::RedirectDeliverBy.into(),
163                        token_info.line_num,
164                        token_info.line_pos,
165                    )?;
166                    by_trace = true;
167                }
168                Token::Tag(Word::ByMode) => {
169                    self.validate_argument(
170                        4,
171                        Capability::RedirectDeliverBy.into(),
172                        token_info.line_num,
173                        token_info.line_pos,
174                    )?;
175                    let by_mode_ = self.tokens.expect_static_string()?;
176                    if by_mode_.eq_ignore_ascii_case("notify") {
177                        by_mode = ByMode::Notify;
178                    } else if by_mode_.eq_ignore_ascii_case("return") {
179                        by_mode = ByMode::Return;
180                    } else {
181                        return Err(token_info.expected("\"notify\" or \"return\""));
182                    }
183                }
184                Token::Tag(Word::ByTimeRelative) => {
185                    self.validate_argument(
186                        5,
187                        Capability::RedirectDeliverBy.into(),
188                        token_info.line_num,
189                        token_info.line_pos,
190                    )?;
191                    by_rlimit = (self.tokens.expect_number(u64::MAX as usize)? as u64).into();
192                }
193                Token::Tag(Word::ByTimeAbsolute) => {
194                    self.validate_argument(
195                        5,
196                        Capability::RedirectDeliverBy.into(),
197                        token_info.line_num,
198                        token_info.line_pos,
199                    )?;
200                    by_alimit = self.parse_string()?.into();
201                }
202                Token::Tag(Word::Ret) => {
203                    self.validate_argument(
204                        6,
205                        Capability::RedirectDsn.into(),
206                        token_info.line_num,
207                        token_info.line_pos,
208                    )?;
209                    let ret_ = self.tokens.expect_static_string()?;
210                    if ret_.eq_ignore_ascii_case("full") {
211                        ret = Ret::Full;
212                    } else if ret_.eq_ignore_ascii_case("hdrs") {
213                        ret = Ret::Hdrs;
214                    } else {
215                        return Err(token_info.expected("\"FULL\" or \"HDRS\""));
216                    }
217                }
218                Token::Tag(Word::Notify) => {
219                    self.validate_argument(
220                        7,
221                        Capability::RedirectDsn.into(),
222                        token_info.line_num,
223                        token_info.line_pos,
224                    )?;
225                    let notify_ = self.tokens.expect_static_string()?;
226                    if notify_.eq_ignore_ascii_case("never") {
227                        notify = Notify::Never;
228                    } else {
229                        let mut items = Vec::new();
230                        for item in notify_.split(',') {
231                            let item = item.trim();
232                            if item.eq_ignore_ascii_case("success") {
233                                items.push(NotifyItem::Success);
234                            } else if item.eq_ignore_ascii_case("failure") {
235                                items.push(NotifyItem::Failure);
236                            } else if item.eq_ignore_ascii_case("delay") {
237                                items.push(NotifyItem::Delay);
238                            }
239                        }
240                        if !items.is_empty() {
241                            notify = Notify::Items(items);
242                        } else {
243                            return Err(
244                                token_info.expected("\"NEVER\" or \"SUCCESS, FAILURE, DELAY, ..\"")
245                            );
246                        }
247                    }
248                }
249                _ => {
250                    address = self.parse_string_token(token_info)?;
251                    break;
252                }
253            }
254        }
255
256        self.instructions.push(Instruction::Redirect(Redirect {
257            address,
258            copy,
259            notify,
260            return_of_content: ret,
261            by_time: if let Some(alimit) = by_alimit {
262                ByTime::Absolute {
263                    alimit,
264                    mode: by_mode,
265                    trace: by_trace,
266                }
267            } else if let Some(rlimit) = by_rlimit {
268                ByTime::Relative {
269                    rlimit,
270                    mode: by_mode,
271                    trace: by_trace,
272                }
273            } else {
274                ByTime::None
275            },
276            list,
277        }));
278        Ok(())
279    }
280}
281
282impl MapLocalVars for ByTime<Value> {
283    fn map_local_vars(&mut self, last_id: usize) {
284        if let ByTime::Absolute { alimit, .. } = self {
285            alimit.map_local_vars(last_id)
286        }
287    }
288}