sieve/runtime/actions/
action_editheader.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 std::borrow::Cow;
8
9use mail_parser::{Header, HeaderName, HeaderValue};
10
11use crate::{
12    compiler::grammar::{
13        actions::{
14            action_editheader::{AddHeader, DeleteHeader},
15            action_mime::MimeOpts,
16        },
17        MatchType,
18    },
19    Context,
20};
21
22impl AddHeader {
23    pub(crate) fn exec(&self, ctx: &mut Context) {
24        let header_name__ = ctx.eval_value(&self.field_name);
25        let header_name_ = header_name__.to_string();
26        let mut header_name = String::with_capacity(header_name_.len());
27
28        for ch in header_name_.chars() {
29            if ch.is_alphanumeric() || ch == '-' {
30                header_name.push(ch);
31            }
32        }
33
34        if !header_name.is_empty() {
35            if let Some(header_name) = HeaderName::parse(header_name) {
36                if !ctx.runtime.protected_headers.contains(&header_name) {
37                    ctx.has_changes = true;
38                    ctx.insert_header(
39                        ctx.part,
40                        header_name,
41                        ctx.eval_value(&self.value)
42                            .to_string()
43                            .as_ref()
44                            .remove_crlf(ctx.runtime.max_header_size),
45                        self.last,
46                    )
47                }
48            }
49        }
50    }
51}
52
53impl DeleteHeader {
54    pub(crate) fn exec(&self, ctx: &mut Context) {
55        let header_name__ = ctx.eval_value(&self.field_name);
56        let header_name_ = header_name__.to_string();
57        let header_name = if let Some(header_name) = HeaderName::parse(header_name_.as_ref()) {
58            header_name
59        } else {
60            return;
61        };
62        let value_patterns = ctx.eval_values(&self.value_patterns);
63        let mut deleted_headers = Vec::new();
64        let mut deleted_bytes = 0;
65
66        if ctx.runtime.protected_headers.contains(&header_name) {
67            return;
68        }
69
70        ctx.find_headers(
71            &[header_name],
72            self.index,
73            self.mime_anychild,
74            |header, part_id, header_pos| {
75                if !value_patterns.is_empty() {
76                    let did_match = ctx.find_header_values(header, &MimeOpts::None, |value| {
77                        for (pattern_expr, pattern) in
78                            value_patterns.iter().zip(self.value_patterns.iter())
79                        {
80                            if match &self.match_type {
81                                MatchType::Is => self.comparator.is(&value, pattern_expr),
82                                MatchType::Contains => self
83                                    .comparator
84                                    .contains(value, pattern_expr.to_string().as_ref()),
85                                MatchType::Value(rel_match) => {
86                                    self.comparator.relational(rel_match, &value, pattern_expr)
87                                }
88                                MatchType::Matches(_) => self.comparator.matches(
89                                    value,
90                                    pattern_expr.to_string().as_ref(),
91                                    0,
92                                    &mut Vec::new(),
93                                ),
94                                MatchType::Regex(_) => self.comparator.regex(
95                                    pattern,
96                                    pattern_expr,
97                                    value,
98                                    0,
99                                    &mut Vec::new(),
100                                ),
101                                MatchType::Count(_) => false,
102                                MatchType::List => false,
103                            } {
104                                return true;
105                            }
106                        }
107                        false
108                    });
109
110                    if !did_match {
111                        return false;
112                    }
113                }
114
115                if header.offset_end != 0 {
116                    deleted_bytes += (header.offset_end - header.offset_field) as usize;
117                } else {
118                    deleted_bytes += header.name.as_str().len() + header.value.len() + 4;
119                }
120                deleted_headers.push((part_id, header_pos));
121
122                false
123            },
124        );
125
126        if !deleted_headers.is_empty() {
127            ctx.has_changes = true;
128            for (part_id, header_pos) in deleted_headers.iter().rev() {
129                ctx.message.parts[*part_id as usize]
130                    .headers
131                    .remove(*header_pos);
132            }
133        }
134
135        ctx.message_size -= deleted_bytes;
136    }
137}
138
139pub(crate) trait RemoveCrLf {
140    fn remove_crlf(&self, max_len: usize) -> String;
141}
142
143impl RemoveCrLf for &str {
144    fn remove_crlf(&self, max_len: usize) -> String {
145        let mut header_value = String::with_capacity(self.len());
146        for ch in self.chars() {
147            if !['\n', '\r'].contains(&ch) {
148                if header_value.len() + ch.len_utf8() <= max_len {
149                    header_value.push(ch);
150                } else {
151                    return header_value;
152                }
153            }
154        }
155        header_value
156    }
157}
158
159impl<'x> Context<'x> {
160    pub(crate) fn insert_header(
161        &mut self,
162        part_id: u32,
163        header_name: HeaderName<'x>,
164        header_value: impl Into<Cow<'static, str>>,
165        last: bool,
166    ) {
167        let header_value = header_value.into();
168        self.message_size += header_name.len() + header_value.len() + 4;
169        let header = Header {
170            name: header_name,
171            value: HeaderValue::Text(header_value),
172            offset_start: 0,
173            offset_end: 0,
174            offset_field: 0,
175        };
176
177        if !last {
178            self.message.parts[part_id as usize]
179                .headers
180                .insert(0, header);
181        } else {
182            self.message.parts[part_id as usize].headers.push(header);
183        }
184    }
185}