1use super::cfws;
5use crate::{
6 class,
7 error::{ErrorKind, ParseError, Result, WriteError, err},
8 limits::Limits,
9 scan::control_kind,
10 text::{Quoted, comment_end_folded, parse_quoted, skip_ows, trim_ows},
11 write::{CountSink, Sink, reserve},
12};
13
14const LOCAL_LIMIT: usize = 64;
16const ADDRESS_LIMIT: usize = 254;
19
20fn refused(v: &[u8], at: usize, kind: ErrorKind) -> ParseError {
21 match v.get(at) {
22 Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
23 _ => ParseError::new(kind, at),
24 }
25}
26
27fn check_dot_atom(seg: &[u8], base: usize) -> Result<()> {
30 if seg.is_empty() {
31 return err(ErrorKind::InvalidAddrSpec, base);
32 }
33 if let Err(e) = core::str::from_utf8(seg) {
34 return err(ErrorKind::InvalidUtf8, base + e.valid_up_to());
35 }
36 let mut prev_dot = true;
37 for (i, &b) in seg.iter().enumerate() {
38 if b == b'.' {
39 if prev_dot {
40 return err(ErrorKind::InvalidAddrSpec, base + i);
41 }
42 prev_dot = true;
43 } else if class::is_atext(b) || b >= 0x80 {
44 prev_dot = false;
45 } else {
46 return Err(refused(seg, i, ErrorKind::InvalidAddrSpec).shifted(base));
47 }
48 }
49 if prev_dot {
50 return err(ErrorKind::InvalidAddrSpec, base + seg.len() - 1);
51 }
52 Ok(())
53}
54
55fn check_dtext(seg: &[u8], base: usize) -> Result<()> {
57 if let Err(e) = core::str::from_utf8(seg) {
58 return err(ErrorKind::InvalidUtf8, base + e.valid_up_to());
59 }
60 match seg
61 .iter()
62 .position(|&b| !matches!(b, 33..=90 | 94..=126 | 0x80..))
63 {
64 Some(i) => Err(refused(seg, i, ErrorKind::InvalidAddrSpec).shifted(base)),
65 None => Ok(()),
66 }
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
82pub struct AddrSpec<'a> {
83 pub local: LocalPart<'a>,
85 pub domain: Domain<'a>,
87}
88
89impl AddrSpec<'_> {
90 #[must_use]
103 pub fn is_ascii(&self) -> bool {
104 let local = match self.local {
105 LocalPart::DotAtom(a) => a,
106 LocalPart::Quoted(q) => q.raw(),
107 };
108 let domain = match self.domain {
109 Domain::DotAtom(d) | Domain::Literal(d) => d,
110 };
111 local.is_ascii() && domain.is_ascii()
112 }
113}
114
115#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
127pub enum LocalPart<'a> {
128 DotAtom(&'a [u8]),
130 Quoted(Quoted<'a>),
132}
133
134#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
146pub enum Domain<'a> {
147 DotAtom(&'a [u8]),
149 Literal(&'a [u8]),
151}
152
153pub fn parse_addr_spec(value: &[u8]) -> Result<AddrSpec<'_>> {
178 let lead = skip_ows(value);
179 let v = trim_ows(value);
180 addr_spec(v).map_err(|e| e.shifted(lead))
181}
182
183fn addr_spec(v: &[u8]) -> Result<AddrSpec<'_>> {
184 let (local, at) = if v.first() == Some(&b'"') {
185 let (q, used) = parse_quoted(v)?;
186 if let Err(e) = core::str::from_utf8(q.raw()) {
187 return err(ErrorKind::InvalidUtf8, 1 + e.valid_up_to());
188 }
189 (LocalPart::Quoted(q), used)
190 } else {
191 let at = v.iter().position(|&b| b == b'@').unwrap_or(v.len());
192 let seg = v.get(..at).unwrap_or(&[]);
193 check_dot_atom(seg, 0)?;
194 (LocalPart::DotAtom(seg), at)
195 };
196 if at > LOCAL_LIMIT {
197 return err(ErrorKind::TooLong, LOCAL_LIMIT);
198 }
199 if v.get(at) != Some(&b'@') {
200 return Err(refused(v, at, ErrorKind::InvalidAddrSpec));
201 }
202 let start = at + 1;
203 let rest = v.get(start..).unwrap_or(&[]);
204 let domain = if rest.first() == Some(&b'[') {
205 let Some((&b']', inner)) = rest
206 .split_last()
207 .map(|(l, h)| (l, h.get(1..).unwrap_or(&[])))
208 else {
209 return err(ErrorKind::InvalidAddrSpec, v.len());
210 };
211 check_dtext(inner, start + 1)?;
212 Domain::Literal(inner)
213 } else {
214 check_dot_atom(rest, start)?;
215 Domain::DotAtom(rest)
216 };
217 if v.len() > ADDRESS_LIMIT {
218 return err(ErrorKind::TooLong, ADDRESS_LIMIT);
219 }
220 Ok(AddrSpec { local, domain })
221}
222
223#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
236pub struct Mailbox<'a> {
237 pub display_name: Option<&'a [u8]>,
240 pub addr: AddrSpec<'a>,
242}
243
244pub fn parse_mailbox<'a>(value: &'a [u8], limits: &Limits) -> Result<Mailbox<'a>> {
264 let lead = skip_ows(value);
265 let v = trim_ows(value);
266 let (mut i, mut has_word) = (0usize, false);
269 let lt = loop {
270 match v.get(i) {
271 None | Some(b'@') => {
272 let addr = addr_spec(v).map_err(|e| e.shifted(lead))?;
273 return Ok(Mailbox {
274 display_name: None,
275 addr,
276 });
277 }
278 Some(b'<') => break i,
279 Some(b'"') => {
280 let (q, used) =
281 parse_quoted(v.get(i..).unwrap_or(&[])).map_err(|e| e.shifted(lead + i))?;
282 if let Err(e) = core::str::from_utf8(q.raw()) {
283 return err(ErrorKind::InvalidUtf8, lead + i + 1 + e.valid_up_to());
284 }
285 has_word = true;
286 i += used;
287 }
288 Some(b'(') => {
289 i = comment_end_folded(v, i, limits)
290 .map_err(|e| e.shifted(lead))?
291 .0;
292 }
293 Some(b' ' | b'\t') => i += 1,
294 Some(&b) if class::is_atext(b) || b == b'.' || b >= 0x80 => {
295 has_word = true;
296 i += 1;
297 }
298 Some(_) => return Err(refused(v, i, ErrorKind::InvalidAddrSpec).shifted(lead)),
299 }
300 };
301 let mut j = lt + 1;
303 let gt = loop {
304 match v.get(j) {
305 None => return err(ErrorKind::InvalidAddrSpec, lead + j),
306 Some(b'>') => break j,
307 Some(b'"') => {
308 let (_, used) =
309 parse_quoted(v.get(j..).unwrap_or(&[])).map_err(|e| e.shifted(lead + j))?;
310 j += used;
311 }
312 Some(_) => j += 1,
313 }
314 };
315 let end = cfws(v, gt + 1, limits).map_err(|e| e.shifted(lead))?;
316 if end < v.len() {
317 return Err(refused(v, end, ErrorKind::InvalidAddrSpec).shifted(lead));
318 }
319 let display = trim_ows(v.get(..lt).unwrap_or(&[]));
320 if let Err(e) = core::str::from_utf8(display) {
321 return err(ErrorKind::InvalidUtf8, lead + e.valid_up_to());
322 }
323 let inner = v.get(lt + 1..gt).unwrap_or(&[]);
324 let addr = addr_spec(inner).map_err(|e| e.shifted(lead + lt + 1))?;
325 Ok(Mailbox {
326 display_name: has_word.then_some(display),
327 addr,
328 })
329}
330
331#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
341pub struct MsgId<'a> {
342 pub left: &'a [u8],
344 pub right: &'a [u8],
346}
347
348pub fn parse_msg_id(value: &[u8]) -> Result<MsgId<'_>> {
363 let lead = skip_ows(value);
364 let v = trim_ows(value);
365 let inner = match (v.first(), v.last()) {
366 (Some(b'<'), Some(b'>')) if v.len() >= 2 => v.get(1..v.len() - 1).unwrap_or(&[]),
367 _ => return err(ErrorKind::InvalidAddrSpec, lead),
368 };
369 let at = inner.iter().position(|&b| b == b'@').unwrap_or(inner.len());
370 let left = inner.get(..at).unwrap_or(&[]);
371 check_dot_atom(left, lead + 1)?;
372 if inner.get(at) != Some(&b'@') {
373 return err(ErrorKind::InvalidAddrSpec, lead + 1 + at);
374 }
375 let right = inner.get(at + 1..).unwrap_or(&[]);
376 let base = lead + 2 + at;
377 match (right.first(), right.last()) {
378 (Some(b'['), Some(b']')) if right.len() >= 2 => {
379 check_dtext(right.get(1..right.len() - 1).unwrap_or(&[]), base + 1)?
380 }
381 _ => check_dot_atom(right, base)?,
382 }
383 Ok(MsgId { left, right })
384}
385
386fn is_ascii_dot_atom(s: &[u8]) -> Option<usize> {
387 let mut prev_dot = true;
388 for (i, &b) in s.iter().enumerate() {
389 if b == b'.' {
390 if prev_dot {
391 return Some(i);
392 }
393 prev_dot = true;
394 } else if class::is_atext(b) {
395 prev_dot = false;
396 } else {
397 return Some(i);
398 }
399 }
400 prev_dot.then_some(s.len().saturating_sub(1))
401}
402
403pub fn write_msg_id(
424 left: &str,
425 right: &str,
426 out: &mut impl Sink,
427) -> core::result::Result<(), WriteError> {
428 let (l, r) = (left.as_bytes(), right.as_bytes());
429 if let Some(i) = is_ascii_dot_atom(l) {
430 return Err(WriteError::forbidden(i));
431 }
432 let bad_right = match (r.first(), r.last()) {
433 (Some(b'['), Some(b']')) if r.len() >= 2 => r
434 .get(1..r.len() - 1)
435 .unwrap_or(&[])
436 .iter()
437 .position(|&b| !matches!(b, 33..=90 | 94..=126))
438 .map(|i| i + 1),
439 _ => is_ascii_dot_atom(r),
440 };
441 if let Some(i) = bad_right {
442 return Err(WriteError::forbidden(l.len() + i));
443 }
444 let mut count = CountSink::new();
445 put_msg_id(l, r, &mut count)?;
446 if count.count() > super::HARD_LIMIT {
448 return Err(WriteError::OutOfRange);
449 }
450 reserve(out, count.count())?;
451 put_msg_id(l, r, out)
452}
453
454fn put_msg_id(l: &[u8], r: &[u8], out: &mut impl Sink) -> core::result::Result<(), WriteError> {
455 out.put(b"<")?;
456 out.put(l)?;
457 out.put(b"@")?;
458 out.put(r)?;
459 out.put(b">")?;
460 Ok(())
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466 use std::vec::Vec;
467
468 fn k(v: &[u8]) -> ErrorKind {
469 parse_addr_spec(v).unwrap_err().kind()
470 }
471
472 #[test]
473 fn test_addr_spec_edge_cases() {
474 let a = parse_addr_spec(b"\"john doe\"@example.com").unwrap();
475 assert!(matches!(a.local, LocalPart::Quoted(q) if q.eq_bytes(b"john doe")));
476 assert_eq!(a.domain, Domain::DotAtom(b"example.com"));
477 assert!(parse_addr_spec(b"a.b+tag@sub.example.co").is_ok());
478 assert_eq!(k(b"john..doe@example.com"), ErrorKind::InvalidAddrSpec);
479 assert_eq!(k(b".john@example.com"), ErrorKind::InvalidAddrSpec);
480 assert_eq!(k(b"john.@example.com"), ErrorKind::InvalidAddrSpec);
481 assert_eq!(k(b"john@example..com"), ErrorKind::InvalidAddrSpec);
482 assert_eq!(k(b"john@"), ErrorKind::InvalidAddrSpec);
483 assert_eq!(k(b"@example.com"), ErrorKind::InvalidAddrSpec);
484 assert_eq!(k(b"john.example.com"), ErrorKind::InvalidAddrSpec);
485 assert_eq!(k(b"john(c)@example.com"), ErrorKind::InvalidAddrSpec);
486 assert_eq!(k(b"john@example.com (c)"), ErrorKind::InvalidAddrSpec);
487 assert_eq!(k(b"a b@example.com"), ErrorKind::InvalidAddrSpec);
488 assert_eq!(k(b"a@b@example.com"), ErrorKind::InvalidAddrSpec);
489 assert_eq!(k(b"\"a@example.com"), ErrorKind::UnterminatedQuote);
490 assert_eq!(k(b"a\r\n@example.com"), ErrorKind::BareCr);
491 assert_eq!(k(b"j\xf6rg@example.de"), ErrorKind::InvalidUtf8);
492 assert!(
493 !parse_addr_spec("j\u{f6}rg@\u{e9}xample.de".as_bytes())
494 .unwrap()
495 .is_ascii()
496 );
497 let lit = parse_addr_spec(b"a@[IPv6:2001:db8::1]").unwrap();
498 assert_eq!(lit.domain, Domain::Literal(b"IPv6:2001:db8::1"));
499 assert_eq!(k(b"a@[1.2.3.4"), ErrorKind::InvalidAddrSpec);
500 assert_eq!(k(b"a@[a[b]"), ErrorKind::InvalidAddrSpec);
501 }
502
503 #[test]
504 fn test_addr_spec_lengths() {
505 let local = [b'a'; 64];
506 let ok = [&local[..], b"@example.com"].concat();
507 assert!(parse_addr_spec(&ok).is_ok());
508 let long = [&[b'a'; 65][..], b"@example.com"].concat();
509 assert_eq!(k(&long), ErrorKind::TooLong);
510 let mut domain = Vec::new();
511 while domain.len() < 250 {
512 domain.extend_from_slice(b"abcdefghi.");
513 }
514 domain.truncate(252);
515 domain.push(b'x');
516 let a = [b"a@".as_slice(), &domain[..251]].concat();
517 assert_eq!(a.len(), 253);
518 let a = [b"ab@".as_slice(), &domain[..251]].concat();
519 assert_eq!((a.len(), parse_addr_spec(&a).is_ok()), (254, true));
520 let a = [b"abc@".as_slice(), &domain[..251]].concat();
521 assert_eq!(k(&a), ErrorKind::TooLong);
522 }
523
524 #[test]
525 fn test_mailbox_forms() {
526 let l = Limits::DEFAULT;
527 let m = parse_mailbox(b"John Q. Public <jqp@example.com>", &l).unwrap();
528 assert_eq!(m.display_name, Some(&b"John Q. Public"[..]));
529 let m = parse_mailbox(b"=?UTF-8?Q?J=C3=B6rg?= (work) <j@example.de>", &l).unwrap();
530 assert_eq!(m.display_name, Some(&b"=?UTF-8?Q?J=C3=B6rg?= (work)"[..]));
531 let m = parse_mailbox(b"<a@b.c>", &l).unwrap();
532 assert!(m.display_name.is_none());
533 assert_eq!(
534 parse_mailbox(b"\"a <b@c.d>", &l).unwrap_err().kind(),
535 ErrorKind::UnterminatedQuote
536 );
537 assert_eq!(
538 parse_mailbox(b"(x <a@b.c>", &l).unwrap_err().kind(),
539 ErrorKind::UnbalancedComment
540 );
541 assert_eq!(
542 parse_mailbox(b"a@b <a@b.c>", &l).unwrap_err().kind(),
543 ErrorKind::InvalidAddrSpec
544 );
545 assert_eq!(
546 parse_mailbox(b"x <a..b@c.d>", &l).unwrap_err().kind(),
547 ErrorKind::InvalidAddrSpec
548 );
549 assert_eq!(
550 parse_mailbox(b"x a@b.c>", &l).unwrap_err().kind(),
551 ErrorKind::InvalidAddrSpec
552 );
553 }
554
555 #[test]
556 fn test_msg_id_parse_and_write() {
557 assert!(parse_msg_id(b"<a@[127.0.0.1]>").is_ok());
558 assert_eq!(
559 parse_msg_id(b"a@b").unwrap_err().kind(),
560 ErrorKind::InvalidAddrSpec
561 );
562 assert_eq!(
563 parse_msg_id(b"<ab>").unwrap_err().kind(),
564 ErrorKind::InvalidAddrSpec
565 );
566 assert_eq!(
567 parse_msg_id(b"<a..b@c>").unwrap_err().kind(),
568 ErrorKind::InvalidAddrSpec
569 );
570 assert_eq!(
571 parse_msg_id(b"<>").unwrap_err().kind(),
572 ErrorKind::InvalidAddrSpec
573 );
574 let mut buf = [0u8; 64];
575 let mut out = crate::SliceSink::new(&mut buf);
576 assert_eq!(
577 write_msg_id("a b", "c", &mut out),
578 Err(WriteError::Forbidden { at: 1 })
579 );
580 assert_eq!(
581 write_msg_id("a", "c..d", &mut out),
582 Err(WriteError::Forbidden { at: 3 })
583 );
584 assert_eq!(
585 write_msg_id("", "c", &mut out),
586 Err(WriteError::Forbidden { at: 0 })
587 );
588 assert_eq!(
589 write_msg_id("a", "[x]y]", &mut out),
590 Err(WriteError::Forbidden { at: 3 })
591 );
592 assert!(out.is_empty());
593 write_msg_id("x.y", "[192.0.2.1]", &mut out).unwrap();
594 let id = parse_msg_id(out.written()).unwrap();
595 assert_eq!((id.left, id.right), (&b"x.y"[..], &b"[192.0.2.1]"[..]));
596 }
597}