mod addr;
mod b64;
mod params;
mod phrase;
mod words;
pub use addr::{
AddrSpec, Domain, LocalPart, Mailbox, MsgId, parse_addr_spec, parse_mailbox, parse_msg_id,
write_msg_id,
};
pub use params::{
Continuation, MimeParamIter, MimeParams, MimeValue, parse_mime_params, write_mime_param,
write_mime_param_at,
};
pub use phrase::decode_phrase;
pub use words::{decode_words, encode_words, encode_words_b};
pub use crate::MailDate;
use crate::{
error::{ErrorKind, Result, WriteError, err},
scan::control_kind,
write::{CountSink, Sink, reserve},
};
const HARD_LIMIT: usize = 998;
const SOFT_LIMIT: usize = 78;
pub(super) fn cfws(v: &[u8], mut i: usize, limits: &crate::Limits) -> Result<usize> {
loop {
match v.get(i) {
Some(b' ' | b'\t') => i += 1,
Some(b'\r') => {
if v.get(i + 1) == Some(&b'\n') && matches!(v.get(i + 2), Some(b' ' | b'\t')) {
i += 3;
} else {
return err(ErrorKind::BareCr, i);
}
}
Some(b'\n') => return err(ErrorKind::LineFeed, i),
Some(b'(') => i = crate::text::comment_end_folded(v, i, limits)?.0,
_ => return Ok(i),
}
}
}
const fn is_refused(b: u8) -> bool {
b != b'\t' && crate::scan::is_control(b)
}
pub fn check_header_value(value: &[u8]) -> Result<()> {
if let Some(at) = value.iter().position(|&b| is_refused(b)) {
return err(control_kind(value.get(at).copied().unwrap_or(0)), at);
}
if value.len() > HARD_LIMIT {
return err(ErrorKind::LineTooLong, HARD_LIMIT);
}
Ok(())
}
#[must_use]
pub fn is_field_name(name: &[u8]) -> bool {
!name.is_empty() && name.iter().all(|&b| matches!(b, 33..=57 | 59..=126))
}
pub fn unfold(value: &[u8]) -> Result<Unfolded<'_>> {
let mut i = 0usize;
while let Some(&b) = value.get(i) {
match b {
b'\r' => {
if value.get(i + 1) == Some(&b'\n')
&& matches!(value.get(i + 2), Some(b' ' | b'\t'))
{
i += 3;
continue;
}
return err(ErrorKind::BareCr, i);
}
b'\n' => return err(ErrorKind::LineFeed, i),
0 => return err(ErrorKind::Nul, i),
_ => i += 1,
}
}
Ok(Unfolded { value })
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Unfolded<'a> {
value: &'a [u8],
}
impl<'a> Unfolded<'a> {
#[must_use]
pub fn iter(&self) -> UnfoldIter<'a> {
UnfoldIter {
rest: Some(self.value),
}
}
}
#[derive(Clone, Debug)]
pub struct UnfoldIter<'a> {
rest: Option<&'a [u8]>,
}
impl<'a> Iterator for UnfoldIter<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<&'a [u8]> {
let rest = self.rest?;
match rest.iter().position(|&b| b == b'\r') {
Some(at) => {
self.rest = rest.get(at + 2..);
rest.get(..at)
}
None => {
self.rest = None;
Some(rest)
}
}
}
}
pub fn fold(name: &str, value: &[u8], out: &mut impl Sink) -> core::result::Result<(), WriteError> {
if !is_field_name(name.as_bytes()) {
return Err(WriteError::NotToken);
}
if let Some(at) = value.iter().position(|&b| is_refused(b)) {
return Err(WriteError::forbidden(at));
}
let mut count = CountSink::new();
put_fold(name.as_bytes(), value, &mut count)?;
reserve(out, count.count())?;
put_fold(name.as_bytes(), value, out)
}
fn put_fold(
name: &[u8],
value: &[u8],
out: &mut impl Sink,
) -> core::result::Result<(), WriteError> {
out.put(name)?;
out.put(b":")?;
let mut col = name.len() + 1;
let mut first = true;
let mut rest = value;
loop {
let ws_len = rest
.iter()
.take_while(|&&b| b == b' ' || b == b'\t')
.count();
let (ws, tail) = rest.split_at(ws_len);
let word_len = tail
.iter()
.take_while(|&&b| b != b' ' && b != b'\t')
.count();
let (word, tail) = tail.split_at(word_len);
if word.is_empty() {
if col + ws.len() > HARD_LIMIT {
return Err(WriteError::OutOfRange);
}
out.put(ws)?;
return Ok(());
}
let own_space = first && ws.is_empty();
let mut gap: &[u8] = if own_space { b" " } else { ws };
let width = col + gap.len() + word.len();
if width > HARD_LIMIT || (width > SOFT_LIMIT && !own_space) {
if gap.len() + word.len() > HARD_LIMIT {
let (keep, last) = gap.split_at(gap.len() - 1);
if col + keep.len() > HARD_LIMIT {
return Err(WriteError::OutOfRange);
}
out.put(keep)?;
gap = last;
}
out.put(b"\r\n")?;
col = 0;
}
if col + gap.len() + word.len() > HARD_LIMIT {
return Err(WriteError::OutOfRange);
}
out.put(gap)?;
out.put(word)?;
col += gap.len() + word.len();
first = false;
rest = tail;
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{vec, vec::Vec};
#[test]
fn test_check_header_value_limits() {
assert!(check_header_value(&[b'a'; 998]).is_ok());
assert_eq!(
check_header_value(&[b'a'; 999]).unwrap_err().kind(),
ErrorKind::LineTooLong
);
assert_eq!(
check_header_value(b"a\nb").unwrap_err().kind(),
ErrorKind::LineFeed
);
assert_eq!(
check_header_value(b"a\x00b").unwrap_err().kind(),
ErrorKind::Nul
);
assert!(check_header_value(b"").is_ok());
}
#[test]
fn test_field_names() {
assert!(is_field_name(b"Subject") && is_field_name(b"!#$%&'*+-.^_`|~9"));
assert!(!is_field_name(b"A B") && !is_field_name(b"A:") && !is_field_name(b"\x7f"));
}
#[test]
fn test_unfold_edge_cases() {
let u = unfold(b"a\r\n\tb\r\n c").unwrap();
assert_eq!(u.iter().collect::<Vec<_>>(), vec![&b"a"[..], b"\tb", b" c"]);
assert_eq!(
unfold(b"").unwrap().iter().collect::<Vec<_>>(),
vec![&b""[..]]
);
assert_eq!(unfold(b"a\r").unwrap_err().kind(), ErrorKind::BareCr);
assert_eq!(unfold(b"a\r\n").unwrap_err().kind(), ErrorKind::BareCr);
assert_eq!(unfold(b"a\nb").unwrap_err().kind(), ErrorKind::LineFeed);
}
fn folded(name: &str, value: &[u8]) -> Vec<u8> {
let mut buf = vec![0u8; 4096];
let mut out = crate::SliceSink::new(&mut buf);
fold(name, value, &mut out).unwrap();
out.written().to_vec()
}
#[test]
fn test_fold_lines_and_round_trip() {
let value = "the quick brown fox jumps over the lazy dog ".repeat(5);
let value = value.trim_end().as_bytes();
let f = folded("Subject", value);
for line in f.split(|&b| b == b'\n') {
let line = line.strip_suffix(b"\r").unwrap_or(line);
assert!(line.len() <= 78, "{}", line.len());
assert!(!line.iter().all(|&b| b == b' '));
}
let body = f.strip_prefix(b"Subject: ").unwrap();
let back: Vec<u8> = unfold(body).unwrap().iter().flatten().copied().collect();
assert_eq!(back, value);
assert_eq!(folded("X", b" lead"), b"X: lead");
assert_eq!(folded("X", b"tab\tsep"), b"X: tab\tsep");
assert_eq!(folded("X", b""), b"X:");
assert_eq!(folded("X", b"a "), b"X: a ");
}
#[test]
fn test_fold_long_words() {
let word = vec![b'w'; 989];
let f = folded("Subject", &word);
assert_eq!(f.len(), 998);
let mut buf = vec![0u8; 4096];
let mut out = crate::SliceSink::new(&mut buf);
assert_eq!(
fold("Subject", &[b'w'; 1000], &mut out),
Err(WriteError::OutOfRange)
);
let mut two = vec![b'a'; 10];
two.push(b' ');
two.extend_from_slice(&[b'w'; 997]);
assert_eq!(fold("S", &two, &mut out), Ok(()));
let mut two = vec![b'a'; 10];
two.push(b' ');
two.extend_from_slice(&[b'w'; 998]);
assert_eq!(fold("S", &two, &mut out), Err(WriteError::OutOfRange));
let mut tail = vec![b'w'; 989];
tail.push(b'\t');
assert_eq!(
fold("Subject", &tail, &mut out),
Err(WriteError::OutOfRange)
);
let mut fits = vec![b'w'; 988];
fits.push(b'\t');
assert_eq!(folded("Subject", &fits).len(), 998);
let mut many = b"a b ".to_vec();
many.extend_from_slice(&[b'w'; 997]);
many.extend_from_slice(b" ");
assert_eq!(fold("S", &many, &mut out), Err(WriteError::OutOfRange));
}
#[test]
fn test_fold_refusals_write_nothing() {
let mut buf = [0u8; 8];
let mut out = crate::SliceSink::new(&mut buf);
assert_eq!(fold("A B", b"x", &mut out), Err(WriteError::NotToken));
assert_eq!(
fold("A", b"x\r\nBcc: y", &mut out),
Err(WriteError::Forbidden { at: 1 })
);
assert_eq!(
fold("A", b"too long for eight", &mut out),
Err(WriteError::Full)
);
assert!(out.is_empty());
}
}