1use 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}