sieve/runtime/
eval.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 std::cmp::Ordering;
25
26use mail_parser::{
27    decoders::html::{html_to_text, text_to_html},
28    parsers::MessageStream,
29    Addr, Header, HeaderName, HeaderValue, Host, PartType, Received,
30};
31
32use crate::{
33    compiler::{
34        ContentTypePart, HeaderPart, HeaderVariable, MessagePart, ReceivedHostname, ReceivedPart,
35        Value, VariableType,
36    },
37    Context,
38};
39
40use super::Variable;
41
42impl<'x> Context<'x> {
43    pub(crate) fn variable<'y: 'x>(&'y self, var: &VariableType) -> Option<Variable> {
44        match var {
45            VariableType::Local(var_num) => self.vars_local.get(*var_num).cloned(),
46            VariableType::Match(var_num) => self.vars_match.get(*var_num).cloned(),
47            VariableType::Global(var_name) => self.vars_global.get(var_name.as_str()).cloned(),
48            VariableType::Environment(var_name) => self
49                .vars_env
50                .get(var_name.as_str())
51                .or_else(|| self.runtime.environment.get(var_name.as_str()))
52                .cloned(),
53            VariableType::Envelope(envelope) => {
54                self.envelope.iter().find_map(
55                    |(e, v)| {
56                        if e == envelope {
57                            Some(v.clone())
58                        } else {
59                            None
60                        }
61                    },
62                )
63            }
64            VariableType::Header(header) => self.eval_header(header),
65            VariableType::Part(part) => match part {
66                MessagePart::TextBody(convert) => {
67                    let part = self.message.parts.get(*self.message.text_body.first()?)?;
68                    match &part.body {
69                        PartType::Text(text) => Some(text.as_ref().into()),
70                        PartType::Html(html) if *convert => {
71                            Some(html_to_text(html.as_ref()).into())
72                        }
73                        _ => None,
74                    }
75                }
76                MessagePart::HtmlBody(convert) => {
77                    let part = self.message.parts.get(*self.message.html_body.first()?)?;
78                    match &part.body {
79                        PartType::Html(html) => Some(html.as_ref().into()),
80                        PartType::Text(text) if *convert => {
81                            Some(text_to_html(text.as_ref()).into())
82                        }
83                        _ => None,
84                    }
85                }
86                MessagePart::Contents => match &self.message.parts.get(self.part)?.body {
87                    PartType::Text(text) | PartType::Html(text) => {
88                        Variable::from(text.as_ref()).into()
89                    }
90                    PartType::Binary(bin) | PartType::InlineBinary(bin) => {
91                        Variable::from(String::from_utf8_lossy(bin.as_ref())).into()
92                    }
93                    _ => None,
94                },
95                MessagePart::Raw => {
96                    let part = self.message.parts.get(self.part)?;
97                    self.message
98                        .raw_message()
99                        .get(part.raw_body_offset()..part.raw_end_offset())
100                        .map(|v| Variable::from(String::from_utf8_lossy(v)))
101                }
102            },
103        }
104    }
105
106    pub(crate) fn eval_value(&self, string: &Value) -> Variable {
107        match string {
108            Value::Text(text) => Variable::String(text.clone()),
109            Value::Variable(var) => self.variable(var).unwrap_or_default(),
110            Value::List(list) => {
111                let mut data = String::new();
112                for item in list {
113                    match item {
114                        Value::Text(string) => {
115                            data.push_str(string);
116                        }
117                        Value::Variable(var) => {
118                            if let Some(value) = self.variable(var) {
119                                data.push_str(&value.to_string());
120                            }
121                        }
122                        Value::List(_) => {
123                            debug_assert!(false, "This should not have happened: {string:?}");
124                        }
125                        Value::Number(n) => {
126                            data.push_str(&n.to_string());
127                        }
128                        Value::Regex(_) => (),
129                    }
130                }
131                data.into()
132            }
133            Value::Number(n) => Variable::from(*n),
134            Value::Regex(r) => Variable::String(r.expr.clone().into()),
135        }
136    }
137
138    fn eval_header<'z: 'x>(&'z self, header: &HeaderVariable) -> Option<Variable> {
139        let mut result = Vec::new();
140        let part = self.message.part(self.part)?;
141        let raw = self.message.raw_message();
142        if !header.name.is_empty() {
143            let mut headers = part
144                .headers
145                .iter()
146                .filter(|h| header.name.contains(&h.name));
147            match header.index_hdr.cmp(&0) {
148                Ordering::Greater => {
149                    if let Some(h) = headers.nth((header.index_hdr - 1) as usize) {
150                        header.eval_part(h, raw, &mut result);
151                    }
152                }
153                Ordering::Less => {
154                    if let Some(h) = headers
155                        .rev()
156                        .nth((header.index_hdr.unsigned_abs() - 1) as usize)
157                    {
158                        header.eval_part(h, raw, &mut result);
159                    }
160                }
161                Ordering::Equal => {
162                    for h in headers {
163                        header.eval_part(h, raw, &mut result);
164                    }
165                }
166            }
167        } else {
168            for h in &part.headers {
169                match &header.part {
170                    HeaderPart::Raw => {
171                        if let Some(var) = raw
172                            .get(h.offset_field..h.offset_end)
173                            .map(sanitize_raw_header)
174                        {
175                            result.push(Variable::from(var));
176                        }
177                    }
178                    HeaderPart::Text => {
179                        if let HeaderValue::Text(text) = &h.value {
180                            result.push(Variable::from(format!("{}: {}", h.name.as_str(), text)));
181                        } else if let HeaderValue::Text(text) =
182                            MessageStream::new(raw.get(h.offset_start..h.offset_end).unwrap_or(b""))
183                                .parse_unstructured()
184                        {
185                            result.push(Variable::from(format!("{}: {}", h.name.as_str(), text)));
186                        }
187                    }
188                    _ => {
189                        header.eval_part(h, raw, &mut result);
190                    }
191                }
192            }
193        }
194
195        match result.len() {
196            1 if header.index_hdr != 0 && header.index_part != 0 => result.pop(),
197            0 => None,
198            _ => Some(Variable::Array(result.into())),
199        }
200    }
201
202    #[inline(always)]
203    pub(crate) fn eval_values<'z: 'y, 'y>(&'z self, strings: &'y [Value]) -> Vec<Variable> {
204        strings.iter().map(|s| self.eval_value(s)).collect()
205    }
206
207    #[inline(always)]
208    pub(crate) fn eval_values_owned(&self, strings: &[Value]) -> Vec<String> {
209        strings
210            .iter()
211            .map(|s| self.eval_value(s).to_string().into_owned())
212            .collect()
213    }
214}
215
216impl HeaderVariable {
217    fn eval_part<'x>(&self, header: &'x Header<'x>, raw: &'x [u8], result: &mut Vec<Variable>) {
218        let var = match &self.part {
219            HeaderPart::Text => match &header.value {
220                HeaderValue::Text(v) if self.include_single_part() => {
221                    Some(Variable::from(v.as_ref()))
222                }
223                HeaderValue::TextList(list) => match self.index_part.cmp(&0) {
224                    Ordering::Greater => list
225                        .get((self.index_part - 1) as usize)
226                        .map(|v| Variable::from(v.as_ref())),
227                    Ordering::Less => list
228                        .iter()
229                        .rev()
230                        .nth((self.index_part.unsigned_abs() - 1) as usize)
231                        .map(|v| Variable::from(v.as_ref())),
232                    Ordering::Equal => {
233                        for item in list {
234                            result.push(Variable::from(item.as_ref()));
235                        }
236                        return;
237                    }
238                },
239                HeaderValue::ContentType(ct) => if let Some(st) = &ct.c_subtype {
240                    Variable::from(format!("{}/{}", ct.c_type, st))
241                } else {
242                    Variable::from(ct.c_type.as_ref())
243                }
244                .into(),
245                HeaderValue::Address(list) => {
246                    let mut list = list.iter();
247                    match self.index_part.cmp(&0) {
248                        Ordering::Greater => list
249                            .nth((self.index_part - 1) as usize)
250                            .map(|a| a.to_text()),
251                        Ordering::Less => list
252                            .rev()
253                            .nth((self.index_part.unsigned_abs() - 1) as usize)
254                            .map(|a| a.to_text()),
255                        Ordering::Equal => {
256                            for item in list {
257                                result.push(item.to_text());
258                            }
259                            return;
260                        }
261                    }
262                }
263                HeaderValue::DateTime(_) => raw
264                    .get(header.offset_start..header.offset_end)
265                    .and_then(|bytes| std::str::from_utf8(bytes).ok())
266                    .map(|s| s.trim())
267                    .map(Variable::from),
268                _ => None,
269            },
270            HeaderPart::Address(part) => match &header.value {
271                HeaderValue::Address(addr) => {
272                    let mut list = addr.iter();
273                    match self.index_part.cmp(&0) {
274                        Ordering::Greater => list
275                            .nth((self.index_part - 1) as usize)
276                            .and_then(|a| part.eval_strict(a))
277                            .map(Variable::from),
278                        Ordering::Less => list
279                            .rev()
280                            .nth((self.index_part.unsigned_abs() - 1) as usize)
281                            .and_then(|a| part.eval_strict(a))
282                            .map(Variable::from),
283                        Ordering::Equal => {
284                            for item in list {
285                                result.push(
286                                    part.eval_strict(item)
287                                        .map(Variable::from)
288                                        .unwrap_or_default(),
289                                );
290                            }
291                            return;
292                        }
293                    }
294                }
295                HeaderValue::Text(_) => {
296                    let addr = raw
297                        .get(header.offset_start..header.offset_end)
298                        .and_then(|bytes| match MessageStream::new(bytes).parse_address() {
299                            HeaderValue::Address(addr) => addr.into(),
300                            _ => None,
301                        });
302                    if let Some(addr) = addr {
303                        let mut list = addr.iter();
304                        match self.index_part.cmp(&0) {
305                            Ordering::Greater => list
306                                .nth((self.index_part - 1) as usize)
307                                .and_then(|a| part.eval_strict(a))
308                                .map(|s| Variable::String(s.to_string().into())),
309                            Ordering::Less => list
310                                .rev()
311                                .nth((self.index_part.unsigned_abs() - 1) as usize)
312                                .and_then(|a| part.eval_strict(a))
313                                .map(|s| Variable::String(s.to_string().into())),
314                            Ordering::Equal => {
315                                for item in list {
316                                    result.push(
317                                        part.eval_strict(item)
318                                            .map(|s| Variable::String(s.to_string().into()))
319                                            .unwrap_or_default(),
320                                    );
321                                }
322                                return;
323                            }
324                        }
325                    } else {
326                        None
327                    }
328                }
329                _ => None,
330            },
331            HeaderPart::Date => {
332                if let HeaderValue::DateTime(dt) = &header.value {
333                    Variable::from(dt.to_timestamp()).into()
334                } else {
335                    raw.get(header.offset_start..header.offset_end)
336                        .and_then(|bytes| match MessageStream::new(bytes).parse_date() {
337                            HeaderValue::DateTime(dt) => Variable::from(dt.to_timestamp()).into(),
338                            _ => None,
339                        })
340                }
341            }
342            HeaderPart::Id => match &header.name {
343                HeaderName::MessageId | HeaderName::ResentMessageId => match &header.value {
344                    HeaderValue::Text(id) => Variable::from(id.as_ref()).into(),
345                    HeaderValue::TextList(ids) => {
346                        for id in ids {
347                            result.push(Variable::from(id.as_ref()));
348                        }
349                        return;
350                    }
351                    _ => None,
352                },
353                HeaderName::Other(_) => {
354                    match MessageStream::new(
355                        raw.get(header.offset_start..header.offset_end)
356                            .unwrap_or(b""),
357                    )
358                    .parse_id()
359                    {
360                        HeaderValue::Text(id) => Variable::from(id).into(),
361                        HeaderValue::TextList(ids) => {
362                            for id in ids {
363                                result.push(Variable::from(id));
364                            }
365                            return;
366                        }
367                        _ => None,
368                    }
369                }
370                _ => None,
371            },
372
373            HeaderPart::Raw => raw
374                .get(header.offset_start..header.offset_end)
375                .map(sanitize_raw_header)
376                .map(Variable::from),
377            HeaderPart::RawName => raw
378                .get(header.offset_field..header.offset_start - 1)
379                .map(|bytes| std::str::from_utf8(bytes).unwrap_or_default())
380                .map(Variable::from),
381            HeaderPart::Exists => Variable::from(true).into(),
382            _ => match (&header.value, &self.part) {
383                (HeaderValue::ContentType(ct), HeaderPart::ContentType(part)) => match part {
384                    ContentTypePart::Type => Variable::from(ct.c_type.as_ref()).into(),
385                    ContentTypePart::Subtype => {
386                        ct.c_subtype.as_ref().map(|s| Variable::from(s.as_ref()))
387                    }
388                    ContentTypePart::Attribute(attr) => ct.attributes.as_ref().and_then(|attrs| {
389                        attrs.iter().find_map(|(k, v)| {
390                            if k.eq_ignore_ascii_case(attr) {
391                                Some(Variable::from(v.as_ref()))
392                            } else {
393                                None
394                            }
395                        })
396                    }),
397                },
398                (HeaderValue::Received(rcvd), HeaderPart::Received(part)) => part.eval(rcvd),
399                _ => None,
400            },
401        };
402
403        result.push(var.unwrap_or_default());
404    }
405
406    #[inline(always)]
407    fn include_single_part(&self) -> bool {
408        [-1, 0, 1].contains(&self.index_part)
409    }
410}
411
412impl ReceivedPart {
413    pub fn eval<'x>(&self, rcvd: &'x Received<'x>) -> Option<Variable> {
414        match self {
415            ReceivedPart::From(from) => rcvd
416                .from()
417                .or_else(|| rcvd.helo())
418                .and_then(|v| from.to_variable(v)),
419            ReceivedPart::FromIp => rcvd.from_ip().map(|ip| Variable::from(ip.to_string())),
420            ReceivedPart::FromIpRev => rcvd.from_iprev().map(Variable::from),
421            ReceivedPart::By(by) => rcvd.by().and_then(|v: &Host<'_>| by.to_variable(v)),
422            ReceivedPart::For => rcvd.for_().map(Variable::from),
423            ReceivedPart::With => rcvd.with().map(|v| Variable::from(v.as_str())),
424            ReceivedPart::TlsVersion => rcvd.tls_version().map(|v| Variable::from(v.as_str())),
425            ReceivedPart::TlsCipher => rcvd.tls_cipher().map(Variable::from),
426            ReceivedPart::Id => rcvd.id().map(Variable::from),
427            ReceivedPart::Ident => rcvd.ident().map(Variable::from),
428            ReceivedPart::Via => rcvd.via().map(Variable::from),
429            ReceivedPart::Date => rcvd.date().map(|d| Variable::from(d.to_timestamp())),
430            ReceivedPart::DateRaw => rcvd.date().map(|d| Variable::from(d.to_rfc822())),
431        }
432    }
433}
434
435trait AddrToText<'x> {
436    fn to_text<'z: 'x>(&'z self) -> Variable;
437}
438
439impl<'x> AddrToText<'x> for Addr<'x> {
440    fn to_text<'z: 'x>(&'z self) -> Variable {
441        if let Some(name) = &self.name {
442            if let Some(address) = &self.address {
443                Variable::String(format!("{name} <{address}>").into())
444            } else {
445                Variable::String(name.to_string().into())
446            }
447        } else if let Some(address) = &self.address {
448            Variable::String(format!("<{address}>").into())
449        } else {
450            Variable::default()
451        }
452    }
453}
454
455impl ReceivedHostname {
456    fn to_variable<'x>(&self, host: &'x Host<'x>) -> Option<Variable> {
457        match (self, host) {
458            (ReceivedHostname::Name, Host::Name(name)) => Variable::from(name.as_ref()).into(),
459            (ReceivedHostname::Ip, Host::IpAddr(ip)) => Variable::from(ip.to_string()).into(),
460            (ReceivedHostname::Any, _) => Variable::from(host.to_string()).into(),
461            _ => None,
462        }
463    }
464}
465
466pub(crate) trait IntoString: Sized {
467    fn into_string(self) -> String;
468}
469
470pub(crate) trait ToString: Sized {
471    fn to_string(&self) -> String;
472}
473
474impl IntoString for Vec<u8> {
475    fn into_string(self) -> String {
476        String::from_utf8(self)
477            .unwrap_or_else(|err| String::from_utf8_lossy(err.as_bytes()).into_owned())
478    }
479}
480
481fn sanitize_raw_header(bytes: &[u8]) -> String {
482    let mut result = Vec::with_capacity(bytes.len());
483    let mut last_is_space = false;
484
485    for &ch in bytes {
486        if ch.is_ascii_whitespace() {
487            last_is_space = true;
488        } else {
489            if last_is_space {
490                result.push(b' ');
491                last_is_space = false;
492            }
493            result.push(ch);
494        }
495    }
496
497    result.into_string()
498}