Skip to main content

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 super::{RuntimeError, Variable};
8use crate::{
9    Context, Envelope, Sieve,
10    bytecode::{
11        Corrupt, Decoded,
12        rec::{Range, Rec, tag},
13    },
14    compiler::{ReceivedHostname, ReceivedPart, grammar::AddressPart},
15};
16use bumpalo::Bump;
17use mail_parser::{
18    Addr, Header, HeaderName, HeaderValue, Host, PartType, Received,
19    decoders::html::{html_to_text, text_to_html},
20    parsers::MessageStream,
21};
22use smallvec::SmallVec;
23use std::{borrow::Cow, cmp::Ordering};
24
25pub(crate) type Values<'x> = SmallVec<[Variable<'x>; 4]>;
26
27#[derive(Debug, Clone, Copy)]
28pub(crate) enum ValueRef<'x> {
29    Text(&'x str),
30    Int(i64),
31    Float(f64),
32    Local(u16),
33    Match(u8),
34    Global(&'x str),
35    Env(&'x str),
36    Envelope(Envelope),
37    Part { kind: u8, convert: bool },
38    Header(HeaderVar<'x>),
39    Regex { pattern: &'x str },
40    Glob { pattern: &'x str },
41    HeaderName(&'x HeaderName<'static>),
42    List(Range),
43    None,
44}
45
46#[derive(Debug, Clone, Copy)]
47pub(crate) struct HeaderVar<'x> {
48    pub names: Range,
49    pub part: HeaderPartRef<'x>,
50    pub index_hdr: i32,
51    pub index_part: i32,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub(crate) enum HeaderPartRef<'x> {
56    Text,
57    Date,
58    Id,
59    Address(AddressPart),
60    ContentType(ContentTypeRef<'x>),
61    Received(ReceivedPart),
62    Raw,
63    RawName,
64    Exists,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub(crate) enum ContentTypeRef<'x> {
69    Type,
70    Subtype,
71    Attribute(&'x str),
72}
73
74impl<'x> ValueRef<'x> {
75    pub(crate) fn decode(
76        script: &'x Sieve<'x>,
77        rec: Rec,
78        following: &mut impl Iterator<Item = Rec>,
79    ) -> Decoded<ValueRef<'x>> {
80        Ok(match rec.tag {
81            tag::TEXT => ValueRef::Text(script.str(rec.str())?),
82            tag::INT => ValueRef::Int(rec.e as i64),
83            tag::FLOAT => ValueRef::Float(f64::from_bits(rec.e)),
84            tag::VAR_LOCAL => ValueRef::Local(rec.c),
85            tag::VAR_MATCH => ValueRef::Match(rec.b),
86            tag::VAR_GLOBAL => ValueRef::Global(script.str(rec.str())?),
87            tag::VAR_ENV => ValueRef::Env(script.str(rec.str())?),
88            tag::VAR_ENVELOPE | tag::ENVELOPE => ValueRef::Envelope(Envelope::from_code(rec.b)),
89            tag::VAR_PART => ValueRef::Part {
90                kind: rec.b,
91                convert: rec.c != 0,
92            },
93            tag::VAR_HEADER => {
94                let cont = following.next().ok_or(Corrupt)?;
95                if cont.tag != tag::CONT {
96                    return Err(Corrupt);
97                }
98                let attr = if cont.e != 0 {
99                    script.str(cont.str())?
100                } else {
101                    ""
102                };
103                ValueRef::Header(HeaderVar {
104                    names: Range {
105                        start: rec.d,
106                        len: cont.c as u32,
107                    },
108                    part: HeaderPartRef::decode(rec.b, rec.c, attr),
109                    index_hdr: (rec.e >> 32) as u32 as i32,
110                    index_part: rec.e as u32 as i32,
111                })
112            }
113            tag::REF => {
114                let target = script.rec(rec.d)?;
115                let mut iter = script.recs(Range {
116                    start: rec.d + 1,
117                    len: rec.e as u32,
118                })?;
119                return ValueRef::decode(script, target, &mut iter);
120            }
121            tag::REGEX => ValueRef::Regex {
122                pattern: script.str(rec.str())?,
123            },
124            tag::GLOB => ValueRef::Glob {
125                pattern: script.str(rec.str())?,
126            },
127            tag::HEADER => ValueRef::HeaderName(script.header_name(rec.c)?),
128            tag::LIST => ValueRef::List(rec.range()),
129            tag::NONE | tag::VARIABLE_NONE => ValueRef::None,
130            _ => return Err(Corrupt),
131        })
132    }
133}
134
135impl<'x> HeaderPartRef<'x> {
136    fn decode(part: u8, sub: u16, attr: &'x str) -> Self {
137        match part {
138            0 => HeaderPartRef::Text,
139            1 => HeaderPartRef::Date,
140            2 => HeaderPartRef::Id,
141            3 => HeaderPartRef::Address(AddressPart::from_code(sub as u8)),
142            4 => HeaderPartRef::ContentType(match sub as u8 {
143                0 => ContentTypeRef::Type,
144                1 => ContentTypeRef::Subtype,
145                _ => ContentTypeRef::Attribute(attr),
146            }),
147            5 => HeaderPartRef::Received(ReceivedPart::from_code(
148                (sub & 0xff) as u8,
149                (sub >> 8) as u8,
150            )),
151            6 => HeaderPartRef::Raw,
152            7 => HeaderPartRef::RawName,
153            _ => HeaderPartRef::Exists,
154        }
155    }
156}
157
158impl<'x> Context<'x> {
159    #[inline(always)]
160    pub(crate) fn cow_str(&self, s: &Cow<'x, str>) -> &'x str {
161        match s {
162            Cow::Borrowed(s) => s,
163            Cow::Owned(s) => self.alloc_str(s),
164        }
165    }
166
167    #[inline(always)]
168    pub(crate) fn raw_message(&self) -> Result<&'x [u8], RuntimeError> {
169        match &self.message.raw_message {
170            Cow::Borrowed(raw) => Ok(raw),
171            Cow::Owned(raw) => match self.raw_message_copy.get() {
172                Some(copy) => Ok(copy),
173                None => {
174                    let copy = self
175                        .arena
176                        .bump
177                        .try_alloc_slice_copy(raw)
178                        .map(|bytes| unsafe { super::context::extend(bytes) })
179                        .map_err(|_| RuntimeError::MemoryLimitReached)?;
180                    self.raw_message_copy.set(Some(copy));
181                    Ok(copy)
182                }
183            },
184        }
185    }
186
187    #[inline(always)]
188    pub(crate) fn eval_value(
189        &self,
190        script: &'x Sieve<'x>,
191        rec: Rec,
192    ) -> Result<Variable<'x>, RuntimeError> {
193        match rec.tag {
194            tag::TEXT => Ok(Variable::borrowed(script.str(rec.str())?)),
195            tag::VAR_LOCAL => Ok(self.local_variable(rec.c)),
196            _ => {
197                let value = ValueRef::decode(script, rec, &mut std::iter::empty())?;
198                self.eval_value_ref(script, value)
199            }
200        }
201    }
202
203    pub(crate) fn eval_value_ref(
204        &self,
205        script: &'x Sieve<'x>,
206        value: ValueRef<'x>,
207    ) -> Result<Variable<'x>, RuntimeError> {
208        Ok(match value {
209            ValueRef::Text(text) => Variable::borrowed(text),
210            ValueRef::Int(n) => Variable::Integer(n),
211            ValueRef::Float(n) => Variable::Float(n),
212            ValueRef::List(range) => {
213                let mut data = ArenaString::new_in(&self.arena.bump);
214                let mut iter = script.recs(range)?;
215                while let Some(item) = iter.next() {
216                    match item.tag {
217                        tag::TEXT => data.push_str(script.str(item.str())?)?,
218                        tag::VAR_LOCAL => {
219                            if let Some(value) =
220                                self.vars_local.get(self.local_base() + item.c as usize)
221                            {
222                                data.push_variable(value)?;
223                            }
224                        }
225                        tag::INT => data.push_str(&(item.e as i64).to_string())?,
226                        tag::FLOAT => data.push_str(&f64::from_bits(item.e).to_string())?,
227                        tag::REGEX | tag::GLOB => (),
228                        tag::HEADER => data.push_str(script.header_name(item.c)?.as_str())?,
229                        _ => {
230                            let item = ValueRef::decode(script, item, &mut iter)?;
231                            if let Some(value) = self.variable_ref(script, item)? {
232                                data.push_variable(&value)?;
233                            }
234                        }
235                    }
236                }
237                Variable::borrowed(unsafe { super::context::extend(data.into_str()) })
238            }
239            ValueRef::Regex { pattern, .. } | ValueRef::Glob { pattern, .. } => {
240                Variable::borrowed(pattern)
241            }
242            ValueRef::HeaderName(name) => Variable::borrowed(name.as_str()),
243            ValueRef::None => Variable::default(),
244            other => self.variable_ref(script, other)?.unwrap_or_default(),
245        })
246    }
247
248    #[inline(always)]
249    pub(crate) fn local_variable(&self, id: u16) -> Variable<'x> {
250        self.vars_local
251            .get(self.local_base() + id as usize)
252            .cloned()
253            .unwrap_or_default()
254    }
255
256    #[inline(always)]
257    pub(crate) fn match_variable(&self, id: u8) -> Variable<'x> {
258        self.vars_match
259            .get(self.match_base() + id as usize)
260            .cloned()
261            .unwrap_or_default()
262    }
263
264    pub(crate) fn variable_ref(
265        &self,
266        script: &'x Sieve<'x>,
267        value: ValueRef<'x>,
268    ) -> Result<Option<Variable<'x>>, RuntimeError> {
269        Ok(match value {
270            ValueRef::Local(id) => self
271                .vars_local
272                .get(self.local_base() + id as usize)
273                .cloned(),
274            ValueRef::Match(id) => self
275                .vars_match
276                .get(self.match_base() + id as usize)
277                .cloned(),
278            ValueRef::Global(name) => self.vars_global.get(name).cloned(),
279            ValueRef::Env(name) => self
280                .vars_env
281                .get(name)
282                .or_else(|| self.runtime.environment.get(name))
283                .cloned(),
284            ValueRef::Envelope(envelope) => self.envelope.iter().find_map(|(e, v)| {
285                if *e == envelope {
286                    Some(v.clone())
287                } else {
288                    None
289                }
290            }),
291            ValueRef::Header(header) => self.eval_header(script, &header)?,
292            ValueRef::Part { kind, convert } => self.eval_part(kind, convert),
293            ValueRef::Text(text) => Some(Variable::borrowed(text)),
294            ValueRef::Int(n) => Some(Variable::Integer(n)),
295            ValueRef::Float(n) => Some(Variable::Float(n)),
296            other => Some(self.eval_value_ref(script, other)?),
297        })
298    }
299
300    fn eval_part(&self, kind: u8, convert: bool) -> Option<Variable<'x>> {
301        match kind {
302            0 => {
303                let part = self
304                    .message
305                    .parts
306                    .get(*self.message.text_body.first()? as usize)?;
307                match &part.body {
308                    PartType::Text(text) => Some(Variable::borrowed(self.cow_str(text))),
309                    PartType::Html(html) if convert => Some(Variable::borrowed(
310                        self.alloc_string(html_to_text(html.as_ref())),
311                    )),
312                    _ => None,
313                }
314            }
315            1 => {
316                let part = self
317                    .message
318                    .parts
319                    .get(*self.message.html_body.first()? as usize)?;
320                match &part.body {
321                    PartType::Html(html) => Some(Variable::borrowed(self.cow_str(html))),
322                    PartType::Text(text) if convert => Some(Variable::borrowed(
323                        self.alloc_string(text_to_html(text.as_ref())),
324                    )),
325                    _ => None,
326                }
327            }
328            2 => match &self.message.parts.get(self.part as usize)?.body {
329                PartType::Text(text) | PartType::Html(text) => {
330                    Some(Variable::borrowed(self.cow_str(text)))
331                }
332                PartType::Binary(bin) | PartType::InlineBinary(bin) => Some(Variable::borrowed(
333                    self.alloc_str(&String::from_utf8_lossy(bin.as_ref())),
334                )),
335                _ => None,
336            },
337            _ => {
338                let part = self.message.parts.get(self.part as usize)?;
339                self.raw_message()
340                    .map_err(|_| self.note_oom())
341                    .ok()?
342                    .get(part.raw_body_offset() as usize..part.raw_end_offset() as usize)
343                    .map(|v| self.bytes_variable(v))
344            }
345        }
346    }
347
348    #[inline(always)]
349    pub(crate) fn bytes_variable(&self, bytes: &'x [u8]) -> Variable<'x> {
350        match std::str::from_utf8(bytes) {
351            Ok(text) => Variable::borrowed(text),
352            Err(_) => Variable::borrowed(self.alloc_str(&String::from_utf8_lossy(bytes))),
353        }
354    }
355
356    pub(crate) fn eval_values(
357        &self,
358        script: &'x Sieve<'x>,
359        range: Range,
360    ) -> Result<Values<'x>, RuntimeError> {
361        let mut result = Values::with_capacity(range.len as usize);
362        let mut iter = script.recs(range)?;
363        while let Some(rec) = iter.next() {
364            let value = ValueRef::decode(script, rec, &mut iter)?;
365            result.push(self.eval_value_ref(script, value)?);
366        }
367        Ok(result)
368    }
369
370    pub(crate) fn eval_strings(
371        &self,
372        script: &'x Sieve<'x>,
373        range: Range,
374    ) -> Result<SmallVec<[&'x str; 4]>, RuntimeError> {
375        let mut result = SmallVec::with_capacity(range.len as usize);
376        let mut iter = script.recs(range)?;
377        while let Some(rec) = iter.next() {
378            let value = ValueRef::decode(script, rec, &mut iter)?;
379            let value = self.eval_value_ref(script, value)?;
380            result.push(self.intern_cow(value.into_string()));
381        }
382        Ok(result)
383    }
384
385    pub(crate) fn eval_opt(
386        &self,
387        script: &'x Sieve<'x>,
388        rec: Rec,
389    ) -> Result<Option<Variable<'x>>, RuntimeError> {
390        if rec.tag == tag::NONE {
391            Ok(None)
392        } else {
393            self.eval_value(script, rec).map(Some)
394        }
395    }
396
397    pub(crate) fn eval_str(
398        &self,
399        script: &'x Sieve<'x>,
400        rec: Rec,
401    ) -> Result<&'x str, RuntimeError> {
402        Ok(self.intern_cow(self.eval_value(script, rec)?.into_string()))
403    }
404
405    pub(crate) fn eval_opt_str(
406        &self,
407        script: &'x Sieve<'x>,
408        rec: Rec,
409    ) -> Result<Option<&'x str>, RuntimeError> {
410        if rec.tag == tag::NONE {
411            Ok(None)
412        } else {
413            self.eval_str(script, rec).map(Some)
414        }
415    }
416
417    fn eval_header(
418        &self,
419        script: &'x Sieve<'x>,
420        header: &HeaderVar<'x>,
421    ) -> Result<Option<Variable<'x>>, RuntimeError> {
422        let mut result: SmallVec<[Variable<'x>; 4]> = SmallVec::new();
423        let Some(part) = self.message.part(self.part) else {
424            return Ok(None);
425        };
426        let raw = self.raw_message()?;
427        if !header.names.is_empty() {
428            let mut names: SmallVec<[&HeaderName<'static>; 2]> = SmallVec::new();
429            for rec in script.recs(header.names)? {
430                names.push(script.header_name(rec.c)?);
431            }
432            let mut headers = part
433                .headers
434                .iter()
435                .filter(|h| names.iter().any(|n| **n == h.name));
436            match header.index_hdr.cmp(&0) {
437                Ordering::Greater => {
438                    if let Some(h) = headers.nth((header.index_hdr - 1) as usize) {
439                        self.eval_header_part(header, h, raw, &mut result);
440                    }
441                }
442                Ordering::Less => {
443                    if let Some(h) = headers
444                        .rev()
445                        .nth((header.index_hdr.unsigned_abs() - 1) as usize)
446                    {
447                        self.eval_header_part(header, h, raw, &mut result);
448                    }
449                }
450                Ordering::Equal => {
451                    for h in headers {
452                        self.eval_header_part(header, h, raw, &mut result);
453                    }
454                }
455            }
456        } else {
457            for h in &part.headers {
458                match &header.part {
459                    HeaderPartRef::Raw => {
460                        if let Some(var) = raw
461                            .get(h.offset_field as usize..h.offset_end as usize)
462                            .map(sanitize_raw_header)
463                        {
464                            result.push(Variable::borrowed(self.alloc_string(var)));
465                        }
466                    }
467                    HeaderPartRef::Text => {
468                        if let HeaderValue::Text(text) = &h.value {
469                            result.push(Variable::borrowed(self.alloc_string(format!(
470                                "{}: {}",
471                                h.name.as_str(),
472                                text
473                            ))));
474                        } else if let HeaderValue::Text(text) = MessageStream::new(
475                            raw.get(h.offset_start as usize..h.offset_end as usize)
476                                .unwrap_or(b""),
477                        )
478                        .parse_unstructured()
479                        {
480                            result.push(Variable::borrowed(self.alloc_string(format!(
481                                "{}: {}",
482                                h.name.as_str(),
483                                text
484                            ))));
485                        }
486                    }
487                    _ => {
488                        self.eval_header_part(header, h, raw, &mut result);
489                    }
490                }
491            }
492        }
493
494        match result.len() {
495            1 if header.index_hdr != 0 && header.index_part != 0 => Ok(result.pop()),
496            0 => Ok(None),
497            _ => self
498                .try_alloc_variables(result)
499                .map(|items| Some(Variable::Array(super::variable::Array::Borrowed(items)))),
500        }
501    }
502
503    fn eval_header_part(
504        &self,
505        header: &HeaderVar<'x>,
506        h: &Header<'x>,
507        raw: &'x [u8],
508        result: &mut SmallVec<[Variable<'x>; 4]>,
509    ) {
510        let index_part = header.index_part;
511        let var = match &header.part {
512            HeaderPartRef::Text => match &h.value {
513                HeaderValue::Text(v) if [-1, 0, 1].contains(&index_part) => {
514                    Some(Variable::borrowed(self.cow_str(v)))
515                }
516                HeaderValue::TextList(list) => match index_part.cmp(&0) {
517                    Ordering::Greater => list
518                        .get((index_part - 1) as usize)
519                        .map(|v| Variable::borrowed(self.cow_str(v))),
520                    Ordering::Less => list
521                        .iter()
522                        .rev()
523                        .nth((index_part.unsigned_abs() - 1) as usize)
524                        .map(|v| Variable::borrowed(self.cow_str(v))),
525                    Ordering::Equal => {
526                        for item in list {
527                            result.push(Variable::borrowed(self.cow_str(item)));
528                        }
529                        return;
530                    }
531                },
532                HeaderValue::ContentType(ct) => {
533                    Some(Variable::borrowed(if let Some(st) = &ct.c_subtype {
534                        self.alloc_string(format!("{}/{}", ct.c_type, st))
535                    } else {
536                        self.cow_str(&ct.c_type)
537                    }))
538                }
539                HeaderValue::Address(list) => {
540                    let mut list = list.iter();
541                    match index_part.cmp(&0) {
542                        Ordering::Greater => list
543                            .nth((index_part - 1) as usize)
544                            .map(|a| self.addr_to_text(a)),
545                        Ordering::Less => list
546                            .rev()
547                            .nth((index_part.unsigned_abs() - 1) as usize)
548                            .map(|a| self.addr_to_text(a)),
549                        Ordering::Equal => {
550                            for item in list {
551                                result.push(self.addr_to_text(item));
552                            }
553                            return;
554                        }
555                    }
556                }
557                HeaderValue::DateTime(_) => raw
558                    .get(h.offset_start as usize..h.offset_end as usize)
559                    .and_then(|bytes| std::str::from_utf8(bytes).ok())
560                    .map(|s| Variable::borrowed(s.trim())),
561                _ => None,
562            },
563            HeaderPartRef::Address(part) => match &h.value {
564                HeaderValue::Address(addr) => {
565                    let mut list = addr.iter();
566                    match index_part.cmp(&0) {
567                        Ordering::Greater => list
568                            .nth((index_part - 1) as usize)
569                            .and_then(|a| self.addr_part(part, a)),
570                        Ordering::Less => list
571                            .rev()
572                            .nth((index_part.unsigned_abs() - 1) as usize)
573                            .and_then(|a| self.addr_part(part, a)),
574                        Ordering::Equal => {
575                            for item in list {
576                                result.push(self.addr_part(part, item).unwrap_or_default());
577                            }
578                            return;
579                        }
580                    }
581                }
582                HeaderValue::Text(_) => {
583                    let addr = raw
584                        .get(h.offset_start as usize..h.offset_end as usize)
585                        .and_then(|bytes| match MessageStream::new(bytes).parse_address() {
586                            HeaderValue::Address(addr) => addr.into(),
587                            _ => None,
588                        });
589                    if let Some(addr) = addr {
590                        let mut list = addr.iter();
591                        match index_part.cmp(&0) {
592                            Ordering::Greater => list
593                                .nth((index_part - 1) as usize)
594                                .and_then(|a| part.eval_strict(a))
595                                .map(|s| Variable::borrowed(self.alloc_str(s))),
596                            Ordering::Less => list
597                                .rev()
598                                .nth((index_part.unsigned_abs() - 1) as usize)
599                                .and_then(|a| part.eval_strict(a))
600                                .map(|s| Variable::borrowed(self.alloc_str(s))),
601                            Ordering::Equal => {
602                                for item in list {
603                                    result.push(
604                                        part.eval_strict(item)
605                                            .map(|s| Variable::borrowed(self.alloc_str(s)))
606                                            .unwrap_or_default(),
607                                    );
608                                }
609                                return;
610                            }
611                        }
612                    } else {
613                        None
614                    }
615                }
616                _ => None,
617            },
618            HeaderPartRef::Date => {
619                if let HeaderValue::DateTime(dt) = &h.value {
620                    Variable::from(dt.to_timestamp()).into()
621                } else {
622                    raw.get(h.offset_start as usize..h.offset_end as usize)
623                        .and_then(|bytes| match MessageStream::new(bytes).parse_date() {
624                            HeaderValue::DateTime(dt) => Variable::from(dt.to_timestamp()).into(),
625                            _ => None,
626                        })
627                }
628            }
629            HeaderPartRef::Id => match &h.name {
630                HeaderName::MessageId | HeaderName::ResentMessageId => match &h.value {
631                    HeaderValue::Text(id) => Variable::borrowed(self.cow_str(id)).into(),
632                    HeaderValue::TextList(ids) => {
633                        for id in ids {
634                            result.push(Variable::borrowed(self.cow_str(id)));
635                        }
636                        return;
637                    }
638                    _ => None,
639                },
640                HeaderName::Other(_) => {
641                    match MessageStream::new(
642                        raw.get(h.offset_start as usize..h.offset_end as usize)
643                            .unwrap_or(b""),
644                    )
645                    .parse_id()
646                    {
647                        HeaderValue::Text(id) => Variable::borrowed(self.cow_str(&id)).into(),
648                        HeaderValue::TextList(ids) => {
649                            for id in ids {
650                                result.push(Variable::borrowed(self.cow_str(&id)));
651                            }
652                            return;
653                        }
654                        _ => None,
655                    }
656                }
657                _ => None,
658            },
659            HeaderPartRef::Raw => raw
660                .get(h.offset_start as usize..h.offset_end as usize)
661                .map(sanitize_raw_header)
662                .map(|s| Variable::borrowed(self.alloc_string(s))),
663            HeaderPartRef::RawName => (h.offset_start as usize)
664                .checked_sub(1)
665                .and_then(|end| raw.get(h.offset_field as usize..end))
666                .map(|bytes| std::str::from_utf8(bytes).unwrap_or_default())
667                .map(Variable::borrowed),
668            HeaderPartRef::Exists => Variable::from(true).into(),
669            HeaderPartRef::ContentType(part) => match &h.value {
670                HeaderValue::ContentType(ct) => match part {
671                    ContentTypeRef::Type => Variable::borrowed(self.cow_str(&ct.c_type)).into(),
672                    ContentTypeRef::Subtype => ct
673                        .c_subtype
674                        .as_ref()
675                        .map(|s| Variable::borrowed(self.cow_str(s))),
676                    ContentTypeRef::Attribute(attr) => ct.attributes.as_ref().and_then(|attrs| {
677                        attrs.iter().find_map(|a| {
678                            if a.name.eq_ignore_ascii_case(attr) {
679                                Some(Variable::borrowed(self.cow_str(&a.value)))
680                            } else {
681                                None
682                            }
683                        })
684                    }),
685                },
686                _ => None,
687            },
688            HeaderPartRef::Received(part) => match &h.value {
689                HeaderValue::Received(rcvd) => self.received_part(part, rcvd),
690                _ => None,
691            },
692        };
693
694        result.push(var.unwrap_or_default());
695    }
696
697    fn addr_to_text(&self, addr: &Addr<'x>) -> Variable<'x> {
698        if let Some(name) = &addr.name {
699            if let Some(address) = &addr.address {
700                Variable::borrowed(self.alloc_string(format!("{name} <{address}>")))
701            } else {
702                Variable::borrowed(self.cow_str(name))
703            }
704        } else if let Some(address) = &addr.address {
705            Variable::borrowed(self.alloc_string(format!("<{address}>")))
706        } else {
707            Variable::default()
708        }
709    }
710
711    fn addr_part(&self, part: &AddressPart, addr: &Addr<'x>) -> Option<Variable<'x>> {
712        match part {
713            AddressPart::Name => addr
714                .name
715                .as_ref()
716                .map(|n| Variable::borrowed(self.cow_str(n))),
717            AddressPart::All => addr
718                .address
719                .as_ref()
720                .map(|a| Variable::borrowed(self.cow_str(a))),
721            _ => part
722                .eval_strict(addr)
723                .map(|s| Variable::borrowed(self.alloc_str(s))),
724        }
725    }
726
727    pub fn received_part(&self, part: &ReceivedPart, rcvd: &Received<'x>) -> Option<Variable<'x>> {
728        match part {
729            ReceivedPart::From(from) => rcvd
730                .from()
731                .or_else(|| rcvd.helo())
732                .and_then(|v| self.host_variable(from, v)),
733            ReceivedPart::FromIp => rcvd
734                .from_ip()
735                .map(|ip| Variable::borrowed(self.alloc_string(ip.to_string()))),
736            ReceivedPart::FromIpRev => rcvd
737                .from_iprev()
738                .map(|v| Variable::borrowed(self.alloc_str(v))),
739            ReceivedPart::By(by) => rcvd.by().and_then(|v: &Host<'_>| self.host_variable(by, v)),
740            ReceivedPart::For => rcvd.for_().map(|v| Variable::borrowed(self.alloc_str(v))),
741            ReceivedPart::With => rcvd.with().map(|v| Variable::borrowed(v.as_str())),
742            ReceivedPart::TlsVersion => rcvd.tls_version().map(|v| Variable::borrowed(v.as_str())),
743            ReceivedPart::TlsCipher => rcvd
744                .tls_cipher()
745                .map(|v| Variable::borrowed(self.alloc_str(v))),
746            ReceivedPart::Id => rcvd.id().map(|v| Variable::borrowed(self.alloc_str(v))),
747            ReceivedPart::Ident => rcvd.ident().map(|v| Variable::borrowed(self.alloc_str(v))),
748            ReceivedPart::Via => rcvd.via().map(|v| Variable::borrowed(self.alloc_str(v))),
749            ReceivedPart::Date => rcvd.date().map(|d| Variable::from(d.to_timestamp())),
750            ReceivedPart::DateRaw => rcvd
751                .date()
752                .map(|d| Variable::borrowed(self.alloc_string(d.to_rfc822()))),
753        }
754    }
755
756    fn host_variable(&self, hostname: &ReceivedHostname, host: &Host<'x>) -> Option<Variable<'x>> {
757        match (hostname, host) {
758            (ReceivedHostname::Name, Host::Name(name)) => {
759                Variable::borrowed(self.cow_str(name)).into()
760            }
761            (ReceivedHostname::Ip, Host::IpAddr(ip)) => {
762                Variable::borrowed(self.alloc_string(ip.to_string())).into()
763            }
764            (ReceivedHostname::Any, _) => {
765                Variable::borrowed(self.alloc_string(host.to_string())).into()
766            }
767            _ => None,
768        }
769    }
770}
771
772struct ArenaString<'a> {
773    bytes: bumpalo::collections::Vec<'a, u8>,
774}
775
776impl<'a> ArenaString<'a> {
777    #[inline(always)]
778    fn new_in(arena: &'a Bump) -> Self {
779        ArenaString {
780            bytes: bumpalo::collections::Vec::new_in(arena),
781        }
782    }
783
784    #[inline(always)]
785    fn push_str(&mut self, s: &str) -> Result<(), RuntimeError> {
786        self.bytes
787            .try_reserve(s.len())
788            .map_err(|_| RuntimeError::MemoryLimitReached)?;
789        self.bytes.extend_from_slice(s.as_bytes());
790        Ok(())
791    }
792
793    fn push_variable(&mut self, value: &Variable<'_>) -> Result<(), RuntimeError> {
794        match value {
795            Variable::String(s) => self.push_str(s),
796            Variable::Integer(n) => self.push_str(&n.to_string()),
797            Variable::Float(n) => self.push_str(&n.to_string()),
798            Variable::Array(items) => self.push_str(&super::variable::array_to_string(items)),
799        }
800    }
801
802    #[inline(always)]
803    fn into_str(self) -> &'a str {
804        unsafe { std::str::from_utf8_unchecked(self.bytes.into_bump_slice()) }
805    }
806}
807
808pub(crate) trait IntoString: Sized {
809    fn into_string(self) -> String;
810}
811
812impl IntoString for Vec<u8> {
813    fn into_string(self) -> String {
814        String::from_utf8(self)
815            .unwrap_or_else(|err| String::from_utf8_lossy(err.as_bytes()).into_owned())
816    }
817}
818
819pub(crate) fn sanitize_raw_header(bytes: &[u8]) -> String {
820    let mut result = Vec::with_capacity(bytes.len());
821    let mut last_is_space = false;
822
823    for &ch in bytes {
824        if ch.is_ascii_whitespace() {
825            last_is_space = true;
826        } else {
827            if last_is_space {
828                result.push(b' ');
829                last_is_space = false;
830            }
831            result.push(ch);
832        }
833    }
834
835    result.into_string()
836}