1use crate::{
5 class,
6 error::{ErrorKind, ParseError, Result, WriteError, err},
7 limits::Limits,
8 scan::control_kind,
9 text::{Quoted, comment_end, parse_quoted, skip_ows, trim_ows},
10 write::{CountSink, Sink, reserve},
11};
12
13const LOCAL_LIMIT: usize = 64;
15const ADDRESS_LIMIT: usize = 254;
18
19fn refused(v: &[u8], at: usize, kind: ErrorKind) -> ParseError {
20 match v.get(at) {
21 Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
22 _ => ParseError::new(kind, at),
23 }
24}
25
26fn check_dot_atom(seg: &[u8], base: usize) -> Result<()> {
29 if seg.is_empty() {
30 return err(ErrorKind::InvalidAddrSpec, base);
31 }
32 if let Err(e) = core::str::from_utf8(seg) {
33 return err(ErrorKind::InvalidUtf8, base + e.valid_up_to());
34 }
35 let mut prev_dot = true;
36 for (i, &b) in seg.iter().enumerate() {
37 if b == b'.' {
38 if prev_dot {
39 return err(ErrorKind::InvalidAddrSpec, base + i);
40 }
41 prev_dot = true;
42 } else if class::is_atext(b) || b >= 0x80 {
43 prev_dot = false;
44 } else {
45 return Err(refused(seg, i, ErrorKind::InvalidAddrSpec).shifted(base));
46 }
47 }
48 if prev_dot {
49 return err(ErrorKind::InvalidAddrSpec, base + seg.len() - 1);
50 }
51 Ok(())
52}
53
54fn check_dtext(seg: &[u8], base: usize) -> Result<()> {
56 if let Err(e) = core::str::from_utf8(seg) {
57 return err(ErrorKind::InvalidUtf8, base + e.valid_up_to());
58 }
59 match seg
60 .iter()
61 .position(|&b| !matches!(b, 33..=90 | 94..=126 | 0x80..))
62 {
63 Some(i) => Err(refused(seg, i, ErrorKind::InvalidAddrSpec).shifted(base)),
64 None => Ok(()),
65 }
66}
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
81pub struct AddrSpec<'a> {
82 pub local: LocalPart<'a>,
84 pub domain: Domain<'a>,
86}
87
88impl AddrSpec<'_> {
89 #[must_use]
102 pub fn is_ascii(&self) -> bool {
103 let local = match self.local {
104 LocalPart::DotAtom(a) => a,
105 LocalPart::Quoted(q) => q.raw(),
106 };
107 let domain = match self.domain {
108 Domain::DotAtom(d) | Domain::Literal(d) => d,
109 };
110 local.is_ascii() && domain.is_ascii()
111 }
112}
113
114#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
126pub enum LocalPart<'a> {
127 DotAtom(&'a [u8]),
129 Quoted(Quoted<'a>),
131}
132
133#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
145pub enum Domain<'a> {
146 DotAtom(&'a [u8]),
148 Literal(&'a [u8]),
150}
151
152pub fn parse_addr_spec(value: &[u8]) -> Result<AddrSpec<'_>> {
177 let lead = skip_ows(value);
178 let v = trim_ows(value);
179 addr_spec(v).map_err(|e| e.shifted(lead))
180}
181
182fn addr_spec(v: &[u8]) -> Result<AddrSpec<'_>> {
183 let (local, at) = if v.first() == Some(&b'"') {
184 let (q, used) = parse_quoted(v)?;
185 if let Err(e) = core::str::from_utf8(q.raw()) {
186 return err(ErrorKind::InvalidUtf8, 1 + e.valid_up_to());
187 }
188 (LocalPart::Quoted(q), used)
189 } else {
190 let at = v.iter().position(|&b| b == b'@').unwrap_or(v.len());
191 let seg = v.get(..at).unwrap_or(&[]);
192 check_dot_atom(seg, 0)?;
193 (LocalPart::DotAtom(seg), at)
194 };
195 if at > LOCAL_LIMIT {
196 return err(ErrorKind::TooLong, LOCAL_LIMIT);
197 }
198 if v.get(at) != Some(&b'@') {
199 return Err(refused(v, at, ErrorKind::InvalidAddrSpec));
200 }
201 let start = at + 1;
202 let rest = v.get(start..).unwrap_or(&[]);
203 let domain = if rest.first() == Some(&b'[') {
204 let Some((&b']', inner)) = rest
205 .split_last()
206 .map(|(l, h)| (l, h.get(1..).unwrap_or(&[])))
207 else {
208 return err(ErrorKind::InvalidAddrSpec, v.len());
209 };
210 check_dtext(inner, start + 1)?;
211 Domain::Literal(inner)
212 } else {
213 check_dot_atom(rest, start)?;
214 Domain::DotAtom(rest)
215 };
216 if v.len() > ADDRESS_LIMIT {
217 return err(ErrorKind::TooLong, ADDRESS_LIMIT);
218 }
219 Ok(AddrSpec { local, domain })
220}
221
222#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
235pub struct Mailbox<'a> {
236 pub display_name: Option<&'a [u8]>,
239 pub addr: AddrSpec<'a>,
241}
242
243pub fn parse_mailbox<'a>(value: &'a [u8], limits: &Limits) -> Result<Mailbox<'a>> {
263 let lead = skip_ows(value);
264 let v = trim_ows(value);
265 if v.last() != Some(&b'>') {
266 let addr = addr_spec(v).map_err(|e| e.shifted(lead))?;
267 return Ok(Mailbox {
268 display_name: None,
269 addr,
270 });
271 }
272 let mut i = 0usize;
274 let lt = loop {
275 match v.get(i) {
276 None => return err(ErrorKind::InvalidAddrSpec, lead + i),
277 Some(b'<') => break i,
278 Some(b'"') => {
279 let (q, used) =
280 parse_quoted(v.get(i..).unwrap_or(&[])).map_err(|e| e.shifted(lead + i))?;
281 if let Err(e) = core::str::from_utf8(q.raw()) {
282 return err(ErrorKind::InvalidUtf8, lead + i + 1 + e.valid_up_to());
283 }
284 i += used;
285 }
286 Some(b'(') => i = comment_end(v, i, limits).map_err(|e| e.shifted(lead))?.0,
287 Some(&b) if class::is_atext(b) || matches!(b, b'.' | b' ' | b'\t') || b >= 0x80 => {
288 i += 1
289 }
290 Some(_) => return Err(refused(v, i, ErrorKind::InvalidAddrSpec).shifted(lead)),
291 }
292 };
293 let display = trim_ows(v.get(..lt).unwrap_or(&[]));
294 if let Err(e) = core::str::from_utf8(display) {
295 return err(ErrorKind::InvalidUtf8, lead + e.valid_up_to());
296 }
297 let inner = v.get(lt + 1..v.len() - 1).unwrap_or(&[]);
298 let addr = addr_spec(inner).map_err(|e| e.shifted(lead + lt + 1))?;
299 Ok(Mailbox {
300 display_name: (!display.is_empty()).then_some(display),
301 addr,
302 })
303}
304
305#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
315pub struct MsgId<'a> {
316 pub left: &'a [u8],
318 pub right: &'a [u8],
320}
321
322pub fn parse_msg_id(value: &[u8]) -> Result<MsgId<'_>> {
337 let lead = skip_ows(value);
338 let v = trim_ows(value);
339 let inner = match (v.first(), v.last()) {
340 (Some(b'<'), Some(b'>')) if v.len() >= 2 => v.get(1..v.len() - 1).unwrap_or(&[]),
341 _ => return err(ErrorKind::InvalidAddrSpec, lead),
342 };
343 let at = inner.iter().position(|&b| b == b'@').unwrap_or(inner.len());
344 let left = inner.get(..at).unwrap_or(&[]);
345 check_dot_atom(left, lead + 1)?;
346 if inner.get(at) != Some(&b'@') {
347 return err(ErrorKind::InvalidAddrSpec, lead + 1 + at);
348 }
349 let right = inner.get(at + 1..).unwrap_or(&[]);
350 let base = lead + 2 + at;
351 match (right.first(), right.last()) {
352 (Some(b'['), Some(b']')) if right.len() >= 2 => {
353 check_dtext(right.get(1..right.len() - 1).unwrap_or(&[]), base + 1)?
354 }
355 _ => check_dot_atom(right, base)?,
356 }
357 Ok(MsgId { left, right })
358}
359
360fn is_ascii_dot_atom(s: &[u8]) -> Option<usize> {
361 let mut prev_dot = true;
362 for (i, &b) in s.iter().enumerate() {
363 if b == b'.' {
364 if prev_dot {
365 return Some(i);
366 }
367 prev_dot = true;
368 } else if class::is_atext(b) {
369 prev_dot = false;
370 } else {
371 return Some(i);
372 }
373 }
374 prev_dot.then_some(s.len().saturating_sub(1))
375}
376
377pub fn write_msg_id(
398 left: &str,
399 right: &str,
400 out: &mut impl Sink,
401) -> core::result::Result<(), WriteError> {
402 let (l, r) = (left.as_bytes(), right.as_bytes());
403 if let Some(i) = is_ascii_dot_atom(l) {
404 return Err(WriteError::forbidden(i));
405 }
406 let bad_right = match (r.first(), r.last()) {
407 (Some(b'['), Some(b']')) if r.len() >= 2 => r
408 .get(1..r.len() - 1)
409 .unwrap_or(&[])
410 .iter()
411 .position(|&b| !matches!(b, 33..=90 | 94..=126))
412 .map(|i| i + 1),
413 _ => is_ascii_dot_atom(r),
414 };
415 if let Some(i) = bad_right {
416 return Err(WriteError::forbidden(l.len() + i));
417 }
418 let mut count = CountSink::new();
419 put_msg_id(l, r, &mut count)?;
420 reserve(out, count.count())?;
421 put_msg_id(l, r, out)
422}
423
424fn put_msg_id(l: &[u8], r: &[u8], out: &mut impl Sink) -> core::result::Result<(), WriteError> {
425 out.put(b"<")?;
426 out.put(l)?;
427 out.put(b"@")?;
428 out.put(r)?;
429 out.put(b">")?;
430 Ok(())
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436 use std::vec::Vec;
437
438 fn k(v: &[u8]) -> ErrorKind {
439 parse_addr_spec(v).unwrap_err().kind()
440 }
441
442 #[test]
443 fn test_addr_spec_edge_cases() {
444 let a = parse_addr_spec(b"\"john doe\"@example.com").unwrap();
445 assert!(matches!(a.local, LocalPart::Quoted(q) if q.eq_bytes(b"john doe")));
446 assert_eq!(a.domain, Domain::DotAtom(b"example.com"));
447 assert!(parse_addr_spec(b"a.b+tag@sub.example.co").is_ok());
448 assert_eq!(k(b"john..doe@example.com"), ErrorKind::InvalidAddrSpec);
449 assert_eq!(k(b".john@example.com"), ErrorKind::InvalidAddrSpec);
450 assert_eq!(k(b"john.@example.com"), ErrorKind::InvalidAddrSpec);
451 assert_eq!(k(b"john@example..com"), ErrorKind::InvalidAddrSpec);
452 assert_eq!(k(b"john@"), ErrorKind::InvalidAddrSpec);
453 assert_eq!(k(b"@example.com"), ErrorKind::InvalidAddrSpec);
454 assert_eq!(k(b"john.example.com"), ErrorKind::InvalidAddrSpec);
455 assert_eq!(k(b"john(c)@example.com"), ErrorKind::InvalidAddrSpec);
456 assert_eq!(k(b"john@example.com (c)"), ErrorKind::InvalidAddrSpec);
457 assert_eq!(k(b"a b@example.com"), ErrorKind::InvalidAddrSpec);
458 assert_eq!(k(b"a@b@example.com"), ErrorKind::InvalidAddrSpec);
459 assert_eq!(k(b"\"a@example.com"), ErrorKind::UnterminatedQuote);
460 assert_eq!(k(b"a\r\n@example.com"), ErrorKind::BareCr);
461 assert_eq!(k(b"j\xf6rg@example.de"), ErrorKind::InvalidUtf8);
462 assert!(
463 !parse_addr_spec("j\u{f6}rg@\u{e9}xample.de".as_bytes())
464 .unwrap()
465 .is_ascii()
466 );
467 let lit = parse_addr_spec(b"a@[IPv6:2001:db8::1]").unwrap();
468 assert_eq!(lit.domain, Domain::Literal(b"IPv6:2001:db8::1"));
469 assert_eq!(k(b"a@[1.2.3.4"), ErrorKind::InvalidAddrSpec);
470 assert_eq!(k(b"a@[a[b]"), ErrorKind::InvalidAddrSpec);
471 }
472
473 #[test]
474 fn test_addr_spec_lengths() {
475 let local = [b'a'; 64];
476 let ok = [&local[..], b"@example.com"].concat();
477 assert!(parse_addr_spec(&ok).is_ok());
478 let long = [&[b'a'; 65][..], b"@example.com"].concat();
479 assert_eq!(k(&long), ErrorKind::TooLong);
480 let mut domain = Vec::new();
481 while domain.len() < 250 {
482 domain.extend_from_slice(b"abcdefghi.");
483 }
484 domain.truncate(252);
485 domain.push(b'x');
486 let a = [b"a@".as_slice(), &domain[..251]].concat();
487 assert_eq!(a.len(), 253);
488 let a = [b"ab@".as_slice(), &domain[..251]].concat();
489 assert_eq!((a.len(), parse_addr_spec(&a).is_ok()), (254, true));
490 let a = [b"abc@".as_slice(), &domain[..251]].concat();
491 assert_eq!(k(&a), ErrorKind::TooLong);
492 }
493
494 #[test]
495 fn test_mailbox_forms() {
496 let l = Limits::DEFAULT;
497 let m = parse_mailbox(b"John Q. Public <jqp@example.com>", &l).unwrap();
498 assert_eq!(m.display_name, Some(&b"John Q. Public"[..]));
499 let m = parse_mailbox(b"=?UTF-8?Q?J=C3=B6rg?= (work) <j@example.de>", &l).unwrap();
500 assert_eq!(m.display_name, Some(&b"=?UTF-8?Q?J=C3=B6rg?= (work)"[..]));
501 let m = parse_mailbox(b"<a@b.c>", &l).unwrap();
502 assert!(m.display_name.is_none());
503 assert_eq!(
504 parse_mailbox(b"\"a <b@c.d>", &l).unwrap_err().kind(),
505 ErrorKind::UnterminatedQuote
506 );
507 assert_eq!(
508 parse_mailbox(b"(x <a@b.c>", &l).unwrap_err().kind(),
509 ErrorKind::UnbalancedComment
510 );
511 assert_eq!(
512 parse_mailbox(b"a@b <a@b.c>", &l).unwrap_err().kind(),
513 ErrorKind::InvalidAddrSpec
514 );
515 assert_eq!(
516 parse_mailbox(b"x <a..b@c.d>", &l).unwrap_err().kind(),
517 ErrorKind::InvalidAddrSpec
518 );
519 assert_eq!(
520 parse_mailbox(b"x a@b.c>", &l).unwrap_err().kind(),
521 ErrorKind::InvalidAddrSpec
522 );
523 }
524
525 #[test]
526 fn test_msg_id_parse_and_write() {
527 assert!(parse_msg_id(b"<a@[127.0.0.1]>").is_ok());
528 assert_eq!(
529 parse_msg_id(b"a@b").unwrap_err().kind(),
530 ErrorKind::InvalidAddrSpec
531 );
532 assert_eq!(
533 parse_msg_id(b"<ab>").unwrap_err().kind(),
534 ErrorKind::InvalidAddrSpec
535 );
536 assert_eq!(
537 parse_msg_id(b"<a..b@c>").unwrap_err().kind(),
538 ErrorKind::InvalidAddrSpec
539 );
540 assert_eq!(
541 parse_msg_id(b"<>").unwrap_err().kind(),
542 ErrorKind::InvalidAddrSpec
543 );
544 let mut buf = [0u8; 64];
545 let mut out = crate::SliceSink::new(&mut buf);
546 assert_eq!(
547 write_msg_id("a b", "c", &mut out),
548 Err(WriteError::Forbidden { at: 1 })
549 );
550 assert_eq!(
551 write_msg_id("a", "c..d", &mut out),
552 Err(WriteError::Forbidden { at: 3 })
553 );
554 assert_eq!(
555 write_msg_id("", "c", &mut out),
556 Err(WriteError::Forbidden { at: 0 })
557 );
558 assert_eq!(
559 write_msg_id("a", "[x]y]", &mut out),
560 Err(WriteError::Forbidden { at: 3 })
561 );
562 assert!(out.is_empty());
563 write_msg_id("x.y", "[192.0.2.1]", &mut out).unwrap();
564 let id = parse_msg_id(out.written()).unwrap();
565 assert_eq!((id.left, id.right), (&b"x.y"[..], &b"[192.0.2.1]"[..]));
566 }
567}