eml-codec 0.1.2

Email enCOder DECoder in Rust. Support Internet Message Format and MIME (RFC 822, 5322, 2045, 2046, 2047, 2048, 2049).
Documentation
use crate::text::ascii;
use crate::text::whitespace::cfws;
use nom::{
    bytes::complete::{tag, take_while1},
    character::is_alphanumeric,
    combinator::{opt, recognize},
    multi::many0,
    sequence::{delimited, pair},
    IResult,
};

pub fn is_vchar(c: u8) -> bool {
    (ascii::EXCLAMATION..=ascii::TILDE).contains(&c)
}

/// MIME Token allowed characters
///
/// forbidden: ()<>@,;:\"/[]?=
fn is_mime_atom_text(c: u8) -> bool {
    is_alphanumeric(c)
        || c == ascii::EXCLAMATION
        || c == ascii::NUM
        || c == ascii::DOLLAR
        || c == ascii::PERCENT
        || c == ascii::AMPERSAND
        || c == ascii::SQUOTE
        || c == ascii::ASTERISK
        || c == ascii::PLUS
        || c == ascii::MINUS
        || c == ascii::PERIOD
        || c == ascii::CARRET
        || c == ascii::UNDERSCORE
        || c == ascii::GRAVE
        || c == ascii::LEFT_CURLY
        || c == ascii::PIPE
        || c == ascii::RIGHT_CURLY
        || c == ascii::TILDE
}

/// MIME Token
///
/// `[CFWS] 1*token_text [CFWS]`
pub fn mime_atom(input: &[u8]) -> IResult<&[u8], &[u8]> {
    delimited(opt(cfws), take_while1(is_mime_atom_text), opt(cfws))(input)
}

/// Atom allowed characters
///
/// authorized: !#$%&'*+-/=?^_`{|}~
fn is_atext(c: u8) -> bool {
    is_alphanumeric(c)
        || c == ascii::EXCLAMATION
        || c == ascii::NUM
        || c == ascii::DOLLAR
        || c == ascii::PERCENT
        || c == ascii::AMPERSAND
        || c == ascii::SQUOTE
        || c == ascii::ASTERISK
        || c == ascii::PLUS
        || c == ascii::MINUS
        || c == ascii::SLASH
        || c == ascii::EQ
        || c == ascii::QUESTION
        || c == ascii::CARRET
        || c == ascii::UNDERSCORE
        || c == ascii::GRAVE
        || c == ascii::LEFT_CURLY
        || c == ascii::PIPE
        || c == ascii::RIGHT_CURLY
        || c == ascii::TILDE
}

/// Atom
///
/// `[CFWS] 1*atext [CFWS]`
pub fn atom(input: &[u8]) -> IResult<&[u8], &[u8]> {
    delimited(opt(cfws), take_while1(is_atext), opt(cfws))(input)
}

/// dot-atom-text
///
/// `1*atext *("." 1*atext)`
pub fn dot_atom_text(input: &[u8]) -> IResult<&[u8], &[u8]> {
    recognize(pair(
        take_while1(is_atext),
        many0(pair(tag("."), take_while1(is_atext))),
    ))(input)
}

/// dot-atom
///
/// `[CFWS] dot-atom-text [CFWS]`
#[allow(dead_code)]
pub fn dot_atom(input: &[u8]) -> IResult<&[u8], &[u8]> {
    delimited(opt(cfws), dot_atom_text, opt(cfws))(input)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_atext() {
        assert!(is_atext(b'='));
        assert!(is_atext(b'5'));
        assert!(is_atext(b'Q'));
        assert!(!is_atext(b' '));
        //assert!(is_atext('É')); // support utf8
    }

    #[test]
    fn test_atom() {
        assert_eq!(
            atom(b"(skip)  imf_codec (hidden) aerogramme"),
            Ok((&b"aerogramme"[..], &b"imf_codec"[..]))
        );
    }

    #[test]
    fn test_dot_atom_text() {
        assert_eq!(
            dot_atom_text(b"quentin.dufour.io abcdef"),
            Ok((&b" abcdef"[..], &b"quentin.dufour.io"[..]))
        );
    }

    #[test]
    fn test_dot_atom() {
        assert_eq!(
            dot_atom(b"   (skip) quentin.dufour.io abcdef"),
            Ok((&b"abcdef"[..], &b"quentin.dufour.io"[..]))
        );
    }
}