sieve/compiler/grammar/actions/
action_editheader.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 mail_parser::HeaderName;
25use serde::{Deserialize, Serialize};
26
27use crate::compiler::{
28    grammar::{
29        instruction::{CompilerState, Instruction},
30        Capability, Comparator,
31    },
32    lexer::{word::Word, Token},
33    CompileError, ErrorType, Value,
34};
35
36use crate::compiler::grammar::MatchType;
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39pub struct AddHeader {
40    pub last: bool,
41    pub field_name: Value,
42    pub value: Value,
43}
44
45/*
46      Usage: "deleteheader" [":index" <fieldno: number> [":last"]]
47                   [COMPARATOR] [MATCH-TYPE]
48                   <field-name: string>
49                   [<value-patterns: string-list>]
50
51*/
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct DeleteHeader {
54    pub index: Option<i32>,
55    pub comparator: Comparator,
56    pub match_type: MatchType,
57    pub field_name: Value,
58    pub value_patterns: Vec<Value>,
59    pub mime_anychild: bool,
60}
61
62impl<'x> CompilerState<'x> {
63    pub(crate) fn parse_addheader(&mut self) -> Result<(), CompileError> {
64        let mut field_name = None;
65        let value;
66        let mut last = false;
67
68        loop {
69            let token_info = self.tokens.unwrap_next()?;
70            match token_info.token {
71                Token::Tag(Word::Last) => {
72                    self.validate_argument(1, None, token_info.line_num, token_info.line_pos)?;
73                    last = true;
74                }
75                _ => {
76                    let string = self.parse_string_token(token_info)?;
77                    if field_name.is_none() {
78                        if let Value::Text(header_name) = &string {
79                            if HeaderName::parse(header_name.as_ref()).is_none() {
80                                return Err(self
81                                    .tokens
82                                    .unwrap_next()?
83                                    .custom(ErrorType::InvalidHeaderName));
84                            }
85                        }
86
87                        field_name = string.into();
88                    } else {
89                        if matches!(
90                            &string,
91                            Value::Text(value) if value.len() > self.compiler.max_header_size
92                        ) {
93                            return Err(self
94                                .tokens
95                                .unwrap_next()?
96                                .custom(ErrorType::HeaderTooLong));
97                        }
98                        value = string;
99                        break;
100                    }
101                }
102            }
103        }
104        self.instructions.push(Instruction::AddHeader(AddHeader {
105            last,
106            field_name: field_name.unwrap(),
107            value,
108        }));
109        Ok(())
110    }
111
112    pub(crate) fn parse_deleteheader(&mut self) -> Result<(), CompileError> {
113        let field_name: Value;
114        let mut match_type = MatchType::Is;
115        let mut comparator = Comparator::AsciiCaseMap;
116        let mut index = None;
117        let mut index_last = false;
118        let mut mime = false;
119        let mut mime_anychild = false;
120
121        loop {
122            let token_info = self.tokens.unwrap_next()?;
123            match token_info.token {
124                Token::Tag(
125                    word @ (Word::Is
126                    | Word::Contains
127                    | Word::Matches
128                    | Word::Value
129                    | Word::Count
130                    | Word::Regex),
131                ) => {
132                    self.validate_argument(
133                        1,
134                        match word {
135                            Word::Value | Word::Count => Capability::Relational.into(),
136                            Word::Regex => Capability::Regex.into(),
137                            Word::List => Capability::ExtLists.into(),
138                            _ => None,
139                        },
140                        token_info.line_num,
141                        token_info.line_pos,
142                    )?;
143                    match_type = self.parse_match_type(word)?;
144                }
145                Token::Tag(Word::Comparator) => {
146                    self.validate_argument(2, None, token_info.line_num, token_info.line_pos)?;
147                    comparator = self.parse_comparator()?;
148                }
149                Token::Tag(Word::Index) => {
150                    self.validate_argument(3, None, token_info.line_num, token_info.line_pos)?;
151                    index = (self.tokens.expect_number(u16::MAX as usize)? as i32).into();
152                }
153                Token::Tag(Word::Last) => {
154                    self.validate_argument(4, None, token_info.line_num, token_info.line_pos)?;
155                    index_last = true;
156                }
157                Token::Tag(Word::Mime) => {
158                    self.validate_argument(
159                        5,
160                        Capability::Mime.into(),
161                        token_info.line_num,
162                        token_info.line_pos,
163                    )?;
164                    mime = true;
165                }
166                Token::Tag(Word::AnyChild) => {
167                    self.validate_argument(
168                        6,
169                        Capability::Mime.into(),
170                        token_info.line_num,
171                        token_info.line_pos,
172                    )?;
173                    mime_anychild = true;
174                }
175                _ => {
176                    field_name = self.parse_string_token(token_info)?;
177                    if let Value::Text(header_name) = &field_name {
178                        if HeaderName::parse(header_name.as_ref()).is_none() {
179                            return Err(self
180                                .tokens
181                                .unwrap_next()?
182                                .custom(ErrorType::InvalidHeaderName));
183                        }
184                    }
185                    break;
186                }
187            }
188        }
189
190        if !mime && mime_anychild {
191            return Err(self.tokens.unwrap_next()?.missing_tag(":mime"));
192        }
193
194        let cmd = Instruction::DeleteHeader(DeleteHeader {
195            index: if index_last { index.map(|i| -i) } else { index },
196            comparator,
197            match_type,
198            field_name,
199            value_patterns: if let Some(Ok(
200                Token::StringConstant(_) | Token::StringVariable(_) | Token::BracketOpen,
201            )) = self.tokens.peek().map(|r| r.map(|t| &t.token))
202            {
203                let mut key_list = self.parse_strings(false)?;
204                self.validate_match(&match_type, &mut key_list)?;
205                key_list
206            } else {
207                Vec::new()
208            },
209            mime_anychild,
210        });
211        self.instructions.push(cmd);
212        Ok(())
213    }
214}