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