diff --git a/examples/parse_command.rs b/examples/parse_command.rs index a1d9ea3..29b96b7 100644 --- a/examples/parse_command.rs +++ b/examples/parse_command.rs @@ -1,6 +1,6 @@ use std::io::Write; -use instant_smtp::parse::command::command; +use instant_smtp::types::Command; fn main() -> std::io::Result<()> { let mut args = std::env::args(); @@ -8,7 +8,7 @@ fn main() -> std::io::Result<()> { if let Some(path) = args.nth(1) { let data = std::fs::read(path).unwrap(); - match command(&data) { + match Command::from_bytes(&data) { Ok((remaining, command)) => { println!("[!] {:#?}", command); let serialized = { @@ -44,7 +44,7 @@ fn main() -> std::io::Result<()> { break; } - match command(line.as_bytes()) { + match Command::from_bytes(line.as_bytes()) { Ok((remaining, command)) => { println!("[!] {:#?}", command); let serialized = { diff --git a/fuzz/fuzz_targets/command.rs b/fuzz/fuzz_targets/command.rs index ea7b9a6..5ce2ef7 100644 --- a/fuzz/fuzz_targets/command.rs +++ b/fuzz/fuzz_targets/command.rs @@ -1,9 +1,9 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use instant_smtp::parse::command::command; +use instant_smtp::types::Command; fuzz_target!(|data: &[u8]| { - if let Ok((_, cmd)) = command(data) { + if let Ok((_, cmd)) = Command::from_bytes(data) { // Fuzzer created a valid SMTP command. // dbg!(&cmd); @@ -13,7 +13,7 @@ fuzz_target!(|data: &[u8]| { cmd.serialize(&mut buf).unwrap(); // ... parse it again ... - let (rem, cmd2) = command(&buf).unwrap(); + let (rem, cmd2) = Command::from_bytes(&buf).unwrap(); assert!(rem.is_empty()); // dbg!(&cmd2); diff --git a/src/lib.rs b/src/lib.rs index f77b7df..bfb14e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,2 @@ -pub mod parse; +mod parse; pub mod types; diff --git a/src/parse/imf.rs b/src/parse/imf.rs deleted file mode 100644 index 8b878ed..0000000 --- a/src/parse/imf.rs +++ /dev/null @@ -1,610 +0,0 @@ -//! Internet Message Format (RFC 5322) -//! -//! TODO: replace this with an IMF library, e.g. rustyknife? - -/// 3.2.1. Quoted characters -pub mod quoted_characters { - use nom::{ - branch::alt, - bytes::streaming::{tag, take_while_m_n}, - combinator::recognize, - sequence::tuple, - IResult, - }; - - use super::obsolete::obs_qp; - - /// quoted-pair = ("\" (VCHAR / WSP)) / obs-qp - pub fn quoted_pair(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = alt(( - recognize(tuple(( - tag(b"\\"), - alt(( - take_while_m_n(1, 1, |byte| matches!(byte, 0x21..=0x7E)), - alt((tag(" "), tag("\t"))), - )), - ))), - obs_qp, - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } -} - -/// 3.2.2. Folding White Space and Comments -pub mod folding_ws_and_comment { - use nom::IResult; - - /// Folding white space - /// - /// FWS = ([*WSP CRLF] 1*WSP) / obs-FWS - pub fn FWS(_input: &[u8]) -> IResult<&[u8], &[u8]> { - unimplemented!() - } - - // Printable US-ASCII characters not including "(", ")", or "\" - // - // ctext = %d33-39 / %d42-91 / %d93-126 / obs-ctext - - // ccontent = ctext / quoted-pair / comment - - // comment = "(" *([FWS] ccontent) [FWS] ")" - - /// CFWS = (1*([FWS] comment) [FWS]) / FWS - pub fn CFWS(_input: &[u8]) -> IResult<&[u8], &[u8]> { - unimplemented!() - } -} - -/// 3.2.3. Atom -pub mod atom { - use nom::{ - bytes::streaming::{tag, take_while1}, - character::{is_alphabetic, is_digit}, - combinator::{opt, recognize}, - multi::many0, - sequence::tuple, - IResult, - }; - - use super::folding_ws_and_comment::CFWS; - - /// Printable US-ASCII characters not including specials. - /// Used for atoms. - /// - /// atext = ALPHA / DIGIT / - /// "!" / "#" / - /// "$" / "%" / - /// "&" / "'" / - /// "*" / "+" / - /// "-" / "/" / - /// "=" / "?" / - /// "^" / "_" / - /// "`" / "{" / - /// "|" / "}" / - /// "~" - pub fn is_atext(byte: u8) -> bool { - let allowed = b"!#$%&'*+-/=?^_`{|}~"; - - is_alphabetic(byte) || is_digit(byte) || allowed.contains(&byte) - } - - /// atom = [CFWS] 1*atext [CFWS] - pub fn atom(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((opt(CFWS), take_while1(is_atext), opt(CFWS))); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - /// dot-atom-text = 1*atext *("." 1*atext) - pub fn dot_atom_text(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple(( - take_while1(is_atext), - many0(tuple((tag(b"."), take_while1(is_atext)))), - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // dot-atom = [CFWS] dot-atom-text [CFWS] - pub fn dot_atom(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((opt(CFWS), dot_atom_text, opt(CFWS))); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // Special characters that do not appear in atext. - // - // specials = "(" / ")" / - // "<" / ">" / - // "[" / "]" / - // ":" / ";" / - // "@" / "\" / - // "," / "." / - // DQUOTE - // ... -} - -/// 3.2.4. Quoted Strings -pub mod quoted_strings { - use nom::{ - branch::alt, - bytes::streaming::{tag, take_while_m_n}, - combinator::{opt, recognize}, - multi::many0, - sequence::tuple, - IResult, - }; - - use super::{ - folding_ws_and_comment::{CFWS, FWS}, - obsolete::is_obs_qtext, - quoted_characters::quoted_pair, - }; - - /// Printable US-ASCII characters not including "\" or the quote character. - /// - /// qtext = %d33 / %d35-91 / %d93-126 / obs-qtext - pub fn is_qtext(byte: u8) -> bool { - match byte { - 33 | 35..=91 | 93..=126 => true, - _ if is_obs_qtext(byte) => true, - _ => false, - } - } - - /// qcontent = qtext / quoted-pair - pub fn qcontent(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = alt((take_while_m_n(1, 1, is_qtext), quoted_pair)); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - /// quoted-string = [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS] - pub fn quoted_string(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple(( - opt(CFWS), - tag("\""), - many0(tuple((opt(FWS), qcontent))), - opt(FWS), - tag("\""), - opt(CFWS), - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } -} - -/// 3.2.5. Miscellaneous Tokens -pub mod miscellaneous { - use nom::{branch::alt, IResult}; - - use super::{atom::atom, quoted_strings::quoted_string}; - - /// word = atom / quoted-string - pub fn word(input: &[u8]) -> IResult<&[u8], &[u8]> { - alt((atom, quoted_string))(input) - } - - // phrase = 1*word / obs-phrase - // ... - - // unstructured = (*([FWS] VCHAR) *WSP) / obs-unstruct - // ... -} - -/// 3.3. Date and Time Specification -pub mod datetime { - use nom::{ - branch::alt, - bytes::streaming::{tag, tag_no_case, take_while_m_n}, - character::is_digit, - combinator::{opt, recognize}, - sequence::tuple, - IResult, - }; - - use super::folding_ws_and_comment::{CFWS, FWS}; - - // date-time = [ day-of-week "," ] date time [CFWS] - pub fn date_time(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((opt(tuple((day_of_week, tag(b",")))), date, time, opt(CFWS))); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // day-of-week = ([FWS] day-name) / obs-day-of-week - pub fn day_of_week(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((opt(FWS), day_name)); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun" - pub fn day_name(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = alt(( - tag_no_case(b"Mon"), - tag_no_case(b"Tue"), - tag_no_case(b"Wed"), - tag_no_case(b"Thu"), - tag_no_case(b"Fri"), - tag_no_case(b"Sat"), - tag_no_case(b"Sun"), - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // date = day month year - pub fn date(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((day, month, year)); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // day = ([FWS] 1*2DIGIT FWS) / obs-day - pub fn day(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((opt(FWS), take_while_m_n(1, 2, is_digit), FWS)); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec" - pub fn month(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = alt(( - tag_no_case(b"Jan"), - tag_no_case(b"Feb"), - tag_no_case(b"Mar"), - tag_no_case(b"Apr"), - tag_no_case(b"May"), - tag_no_case(b"Jun"), - tag_no_case(b"Jul"), - tag_no_case(b"Aug"), - tag_no_case(b"Sep"), - tag_no_case(b"Oct"), - tag_no_case(b"Nov"), - tag_no_case(b"Dec"), - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // year = (FWS 4*DIGIT FWS) / obs-year - pub fn year(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((FWS, take_while_m_n(4, 8, is_digit), FWS)); // FIXME: 4*?! - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // time = time-of-day zone - pub fn time(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((time_of_day, zone)); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // time-of-day = hour ":" minute [ ":" second ] - pub fn time_of_day(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((hour, tag(b":"), minute, opt(tuple((tag(b":"), second))))); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // hour = 2DIGIT / obs-hour - pub fn hour(input: &[u8]) -> IResult<&[u8], &[u8]> { - // FIXME: obs- forms must not be used in SMTP. Never? - - let parser = take_while_m_n(2, 2, is_digit); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // minute = 2DIGIT / obs-minute - pub fn minute(input: &[u8]) -> IResult<&[u8], &[u8]> { - // FIXME: obs- forms must not be used in SMTP. Never? - - let parser = take_while_m_n(2, 2, is_digit); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // second = 2DIGIT / obs-second - pub fn second(input: &[u8]) -> IResult<&[u8], &[u8]> { - // FIXME: obs- forms must not be used in SMTP. Never? - - let parser = take_while_m_n(2, 2, is_digit); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // zone = (FWS ( "+" / "-" ) 4DIGIT) / obs-zone - pub fn zone(input: &[u8]) -> IResult<&[u8], &[u8]> { - // FIXME: obs- forms must not be used in SMTP. Never? - - let parser = tuple(( - FWS, - alt((tag(b"+"), tag(b"-"))), - take_while_m_n(4, 4, is_digit), - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } -} - -/// 3.4.1. Addr-Spec Specification -pub mod addr_spec { - use nom::{ - branch::alt, - bytes::streaming::{tag, take_while_m_n}, - combinator::{opt, recognize}, - multi::many0, - sequence::tuple, - IResult, - }; - - use super::{ - atom::dot_atom, - folding_ws_and_comment::{CFWS, FWS}, - obsolete::{obs_domain, obs_dtext, obs_local_part}, - quoted_strings::quoted_string, - }; - - // addr-spec = local-part "@" domain - - /// local-part = dot-atom / quoted-string / obs-local-part - pub fn local_part(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = alt((dot_atom, quoted_string, obs_local_part)); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - /// domain = dot-atom / domain-literal / obs-domain - pub fn domain(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = alt((dot_atom, domain_literal, obs_domain)); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - /// domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS] - pub fn domain_literal(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple(( - opt(CFWS), - tag(b"["), - many0(tuple((opt(FWS), dtext))), - opt(FWS), - tag(b"]"), - opt(CFWS), - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - /// Printable US-ASCII characters not including "[", "]", or "\". - /// - /// dtext = %d33-90 / %d94-126 / obs-dtext - pub fn dtext(input: &[u8]) -> IResult<&[u8], &[u8]> { - fn is_a(byte: u8) -> bool { - matches!(byte, 33..=90) - } - - fn is_b(byte: u8) -> bool { - matches!(byte, 94..=126) - } - - let parser = alt(( - take_while_m_n(1, 1, is_a), - take_while_m_n(1, 1, is_b), - obs_dtext, - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } -} - -/// 3.6.4. Identification Fields -pub mod identification { - use nom::{ - branch::alt, - bytes::streaming::tag, - combinator::{opt, recognize}, - multi::many0, - sequence::{delimited, tuple}, - IResult, - }; - - use super::{ - addr_spec::dtext, - atom::dot_atom_text, - folding_ws_and_comment::CFWS, - obsolete::{obs_id_left, obs_id_right}, - }; - - // message-id = "Message-ID:" msg-id CRLF - // ... - - // in-reply-to = "In-Reply-To:" 1*msg-id CRLF - // ... - - // references = "References:" 1*msg-id CRLF - // ... - - /// msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS] - pub fn msg_id(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple(( - opt(CFWS), - tag(b"<"), - id_left, - tag(b"@"), - id_right, - tag(b">"), - opt(CFWS), - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - /// id-left = dot-atom-text / obs-id-left - pub fn id_left(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = alt((dot_atom_text, obs_id_left)); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - /// id-right = dot-atom-text / no-fold-literal / obs-id-right - pub fn id_right(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = alt((dot_atom_text, no_fold_literal, obs_id_right)); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // no-fold-literal = "[" *dtext "]" - pub fn no_fold_literal(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = delimited(tag(b"["), many0(dtext), tag(b"]")); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } -} - -/// 4.1. Miscellaneous Obsolete Tokens -pub mod obsolete { - use nom::{ - branch::alt, - bytes::streaming::{tag, take_while_m_n}, - combinator::recognize, - multi::many0, - sequence::tuple, - IResult, - }; - - use super::{ - addr_spec::{domain, local_part}, - atom::atom, - miscellaneous::word, - quoted_characters::quoted_pair, - }; - - /// US-ASCII control characters that do not include the carriage - /// return, line feed, and white space characters - /// - /// obs-NO-WS-CTL = %d1-8 / %d11 / %d12 / %d14-31 / %d127 - pub fn is_obs_NO_WS_CTL(byte: u8) -> bool { - matches!(byte, 1..=8 | 11 | 12 | 14..=31 | 127) - } - - /// obs-qtext = obs-NO-WS-CTL - pub fn is_obs_qtext(byte: u8) -> bool { - is_obs_NO_WS_CTL(byte) - } - - /// obs-qp = "\" (%d0 / obs-NO-WS-CTL / LF / CR) - pub fn obs_qp(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple(( - tag(b"\\"), - alt(( - take_while_m_n(1, 1, |x| x == 0x00), - take_while_m_n(1, 1, is_obs_NO_WS_CTL), - tag("\n"), - tag("\r"), - )), - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // 4.4. Obsolete Addressing (RFC 5322) - - /// obs-local-part = word *("." word) - pub fn obs_local_part(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((word, many0(tuple((tag(b"."), word))))); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - /// obs-domain = atom *("." atom) - pub fn obs_domain(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((atom, many0(tuple((tag(b"."), atom))))); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - /// obs-dtext = obs-NO-WS-CTL / quoted-pair - pub fn obs_dtext(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = alt((take_while_m_n(1, 1, is_obs_NO_WS_CTL), quoted_pair)); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) - } - - // 4.5.4. Obsolete Identification Fields (RFC 5322) - - /// obs-id-left = local-part - pub fn obs_id_left(input: &[u8]) -> IResult<&[u8], &[u8]> { - local_part(input) - } - - /// obs-id-right = domain - pub fn obs_id_right(input: &[u8]) -> IResult<&[u8], &[u8]> { - domain(input) - } -} diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 6c342cb..4ad6963 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -13,13 +13,11 @@ use nom::{ IResult, }; -use crate::{parse::imf::atom::is_atext, types::AtomOrQuoted}; +use crate::types::AtomOrQuoted; pub mod address; pub mod command; -pub mod imf; pub mod response; -pub mod trace; pub fn base64(input: &[u8]) -> IResult<&[u8], &str> { let mut parser = map_res( @@ -58,6 +56,26 @@ pub fn Atom(input: &[u8]) -> IResult<&[u8], &str> { map_res(take_while1(is_atext), std::str::from_utf8)(input) } +/// Printable US-ASCII characters not including specials. +/// Used for atoms. +/// +/// atext = ALPHA / DIGIT / +/// "!" / "#" / +/// "$" / "%" / +/// "&" / "'" / +/// "*" / "+" / +/// "-" / "/" / +/// "=" / "?" / +/// "^" / "_" / +/// "`" / "{" / +/// "|" / "}" / +/// "~" +pub fn is_atext(byte: u8) -> bool { + let allowed = b"!#$%&'*+-/=?^_`{|}~"; + + is_alphabetic(byte) || is_digit(byte) || allowed.contains(&byte) +} + /// Quoted-string = DQUOTE *QcontentSMTP DQUOTE pub fn Quoted_string(input: &[u8]) -> IResult<&[u8], Cow<'_, str>> { map( diff --git a/src/parse/trace.rs b/src/parse/trace.rs deleted file mode 100644 index 4ea8635..0000000 --- a/src/parse/trace.rs +++ /dev/null @@ -1,218 +0,0 @@ -/// 4.4. Trace Information (RFC 5321) -use nom::{ - branch::alt, - bytes::streaming::{tag, tag_no_case}, - combinator::{map_res, opt, recognize}, - multi::many1, - sequence::tuple, - IResult, -}; - -use crate::parse::{ - address::address_literal, - command::{Mailbox, Path, Reverse_path}, - imf::{ - datetime::date_time, - folding_ws_and_comment::{CFWS, FWS}, - identification::msg_id, - }, - Atom, Domain, String, -}; - -/// Return-path-line = "Return-Path:" FWS Reverse-path -pub fn Return_path_line(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((tag_no_case(b"Return-Path:"), FWS, Reverse_path, tag("\r\n"))); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) -} - -/// Time-stamp-line = "Received:" FWS Stamp -pub fn Time_stamp_line(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((tag_no_case(b"Received:"), FWS, Stamp, tag("\r\n"))); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) -} - -/// Stamp = From-domain By-domain Opt-info [CFWS] ";" FWS date-time -/// -/// Caution: Where "date-time" is as defined in RFC 5322 [4] -/// but the "obs-" forms, especially two-digit -/// years, are prohibited in SMTP and MUST NOT be used. -pub fn Stamp(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple(( - From_domain, - By_domain, - Opt_info, - opt(CFWS), - tag(b";"), - FWS, - date_time, - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) -} - -/// From-domain = "FROM" FWS Extended-Domain -pub fn From_domain(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((tag_no_case(b"FROM"), FWS, Extended_Domain)); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) -} - -/// By-domain = CFWS "BY" FWS Extended-Domain -pub fn By_domain(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((CFWS, tag_no_case(b"BY"), FWS, Extended_Domain)); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) -} - -/// Extended-Domain = Domain / -/// ( Domain FWS "(" TCP-info ")" ) / -/// ( address-literal FWS "(" TCP-info ")" ) -pub fn Extended_Domain(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = alt(( - recognize(Domain), - recognize(tuple((Domain, FWS, tag(b"("), TCP_info, tag(b")")))), - recognize(tuple(( - address_literal, - FWS, - tag(b"("), - TCP_info, - tag(b")"), - ))), - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) -} - -/// Information derived by server from TCP connection not client EHLO. -/// -/// TCP-info = address-literal / ( Domain FWS address-literal ) -pub fn TCP_info(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = alt(( - recognize(address_literal), - recognize(tuple((Domain, FWS, address_literal))), - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) -} - -/// Opt-info = [Via] [With] [ID] [For] [Additional-Registered-Clauses] -pub fn Opt_info(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple(( - opt(Via), - opt(With), - opt(ID), - opt(For), - opt(Additional_Registered_Clauses), - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) -} - -/// Via = CFWS "VIA" FWS Link -pub fn Via(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((CFWS, tag_no_case(b"VIA"), FWS, Link)); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) -} - -/// With = CFWS "WITH" FWS Protocol -pub fn With(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple((CFWS, tag_no_case(b"WITH"), FWS, Protocol)); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) -} - -/// ID = CFWS "ID" FWS ( Atom / msg-id ) -/// ; msg-id is defined in RFC 5322 [4] -pub fn ID(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple(( - CFWS, - tag_no_case(b"ID"), - FWS, - recognize(alt((recognize(Atom), msg_id))), - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) -} - -/// For = CFWS "FOR" FWS ( Path / Mailbox ) -pub fn For(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = tuple(( - CFWS, - tag_no_case(b"FOR"), - FWS, - alt((recognize(Path), Mailbox)), - )); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) -} - -/// Additional standard clauses may be added in this location by future standards and registration with -/// IANA. SMTP servers SHOULD NOT use unregistered names. See Section 8. -/// -/// Additional-Registered-Clauses = CFWS Atom FWS String -pub fn Additional_Registered_Clauses(input: &[u8]) -> IResult<&[u8], &[u8]> { - let parser = many1(tuple((CFWS, Atom, FWS, String))); - - let (remaining, parsed) = recognize(parser)(input)?; - - Ok((remaining, parsed)) -} - -/// Link = "TCP" / Addtl-Link -pub fn Link(input: &[u8]) -> IResult<&[u8], &str> { - alt((map_res(tag_no_case("TCP"), std::str::from_utf8), Addtl_Link))(input) -} - -/// Additional standard names for links are registered with the Internet Assigned Numbers -/// Authority (IANA). "Via" is primarily of value with non-Internet transports. SMTP servers -/// SHOULD NOT use unregistered names. -/// -/// Addtl-Link = Atom -pub fn Addtl_Link(input: &[u8]) -> IResult<&[u8], &str> { - Atom(input) -} - -/// Protocol = "ESMTP" / "SMTP" / Attdl-Protocol -pub fn Protocol(input: &[u8]) -> IResult<&[u8], &str> { - alt(( - map_res(tag_no_case(b"ESMTP"), std::str::from_utf8), - map_res(tag_no_case(b"SMTP"), std::str::from_utf8), - Attdl_Protocol, - ))(input) -} - -/// Additional standard names for protocols are registered with the Internet Assigned Numbers -/// Authority (IANA) in the "mail parameters" registry [9]. SMTP servers SHOULD NOT -/// use unregistered names. -/// -/// Attdl-Protocol = Atom -pub fn Attdl_Protocol(input: &[u8]) -> IResult<&[u8], &str> { - Atom(input) -} diff --git a/src/types.rs b/src/types.rs index 91d62fd..ff8658b 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,5 +1,6 @@ use std::{borrow::Cow, fmt, io::Write, ops::Deref}; +use nom::IResult; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -95,6 +96,12 @@ pub enum Command { AuthPlain(Option), } +impl Command { + pub fn from_bytes(input: &[u8]) -> IResult<&[u8], Self> { + crate::parse::command::command(input) + } +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum DomainOrAddress { Domain(String), @@ -308,6 +315,10 @@ pub enum Response { } impl Response { + pub fn parse_greeting(input: &[u8]) -> IResult<&[u8], Self> { + crate::parse::response::Greeting(input) + } + pub fn greeting(domain: D, text: T) -> Response where D: Into, @@ -319,6 +330,14 @@ impl Response { } } + pub fn parse_ehlo(input: &[u8]) -> IResult<&[u8], Self> { + crate::parse::response::ehlo_ok_rsp(input) + } + + pub fn parse_other(input: &[u8]) -> IResult<&[u8], Self> { + crate::parse::response::Reply_lines(input) + } + pub fn ehlo(domain: D, greet: Option, capabilities: Vec) -> Response where D: Into, diff --git a/tests/trace.rs b/tests/trace.rs index eec02f7..e863695 100644 --- a/tests/trace.rs +++ b/tests/trace.rs @@ -1,14 +1,8 @@ -use instant_smtp::{ - parse::{ - command::command, - response::{ehlo_ok_rsp, Greeting, Reply_lines}, - }, - types::Command, -}; +use instant_smtp::types::{Command, Response}; use nom::FindSubstring; fn parse_trace(mut trace: &[u8]) { - let (rem, greeting) = Greeting(trace).unwrap(); + let (rem, greeting) = Response::parse_greeting(trace).unwrap(); println!("S: {:?}", greeting); trace = rem; @@ -17,18 +11,18 @@ fn parse_trace(mut trace: &[u8]) { break; } - let (rem, cmd) = command(trace).unwrap(); + let (rem, cmd) = Command::from_bytes(trace).unwrap(); println!("C: {:?}", cmd); trace = rem; match cmd { Command::Ehlo { .. } => { - let (rem, rsp) = ehlo_ok_rsp(trace).unwrap(); + let (rem, rsp) = Response::parse_ehlo(trace).unwrap(); println!("S: {:?}", rsp); trace = rem; } Command::Data { .. } => { - let (rem, rsp) = Reply_lines(trace).unwrap(); + let (rem, rsp) = Response::parse_other(trace).unwrap(); println!("S: {:?}", rsp); trace = rem; @@ -37,12 +31,12 @@ fn parse_trace(mut trace: &[u8]) { println!("C (data): <{}>", std::str::from_utf8(data).unwrap()); trace = rem; - let (rem, rsp) = Reply_lines(trace).unwrap(); + let (rem, rsp) = Response::parse_other(trace).unwrap(); println!("S: {:?}", rsp); trace = rem; } _ => { - let (rem, rsp) = Reply_lines(trace).unwrap(); + let (rem, rsp) = Response::parse_other(trace).unwrap(); println!("S: {:?}", rsp); trace = rem; }