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