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