Too much, sorry.
This commit is contained in:
parent
a327db7285
commit
10f614edb6
22
src/lib.rs
22
src/lib.rs
|
@ -1,24 +1,4 @@
|
|||
pub mod parse;
|
||||
pub mod types;
|
||||
|
||||
pub fn escape(bytes: &[u8]) -> String {
|
||||
bytes
|
||||
.iter()
|
||||
.map(|byte| match byte {
|
||||
0x00..=0x08 => format!("\\x{:02x}", byte),
|
||||
0x09 => String::from("\\t"),
|
||||
0x0A => String::from("\\n\n"),
|
||||
0x0B => format!("\\x{:02x}", byte),
|
||||
0x0C => format!("\\x{:02x}", byte),
|
||||
0x0D => String::from("\\r"),
|
||||
0x0e..=0x1f => format!("\\x{:02x}", byte),
|
||||
0x20..=0x22 => format!("{}", *byte as char),
|
||||
0x23..=0x5B => format!("{}", *byte as char),
|
||||
0x5C => String::from("\\\\"),
|
||||
0x5D..=0x7E => format!("{}", *byte as char),
|
||||
0x7f => format!("\\x{:02x}", byte),
|
||||
0x80..=0xff => format!("\\x{:02x}", byte),
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
}
|
||||
mod utils;
|
||||
|
|
|
@ -1,17 +1,38 @@
|
|||
//! 4.1.3. Address Literals (RFC 5321)
|
||||
|
||||
use crate::parse::command::Ldh_str;
|
||||
use crate::parse::Ldh_str;
|
||||
use abnf_core::streaming::is_DIGIT;
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::streaming::{tag, tag_no_case, take_while1, take_while_m_n},
|
||||
character::is_hex_digit,
|
||||
combinator::{opt, recognize},
|
||||
combinator::{map_res, opt, recognize},
|
||||
multi::{count, many_m_n},
|
||||
sequence::tuple,
|
||||
sequence::{delimited, tuple},
|
||||
IResult,
|
||||
};
|
||||
|
||||
/// address-literal = "[" (
|
||||
/// IPv4-address-literal /
|
||||
/// IPv6-address-literal /
|
||||
/// General-address-literal
|
||||
/// ) "]"
|
||||
/// ; See Section 4.1.3
|
||||
pub fn address_literal(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
delimited(
|
||||
tag(b"["),
|
||||
map_res(
|
||||
alt((
|
||||
IPv4_address_literal,
|
||||
IPv6_address_literal,
|
||||
General_address_literal,
|
||||
)),
|
||||
std::str::from_utf8,
|
||||
),
|
||||
tag(b"]"),
|
||||
)(input)
|
||||
}
|
||||
|
||||
/// IPv4-address-literal = Snum 3("." Snum)
|
||||
pub fn IPv4_address_literal(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((Snum, count(tuple((tag(b"."), Snum)), 3)));
|
||||
|
@ -21,47 +42,16 @@ pub fn IPv4_address_literal(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// IPv6-address-literal = "IPv6:" IPv6-addr
|
||||
pub fn IPv6_address_literal(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((tag_no_case(b"IPv6:"), IPv6_addr));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// General-address-literal = Standardized-tag ":" 1*dcontent
|
||||
pub fn General_address_literal(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((Standardized_tag, tag(b":"), take_while1(is_dcontent)));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Standardized-tag MUST be specified in a Standards-Track RFC and registered with IANA
|
||||
///
|
||||
/// Standardized-tag = Ldh-str
|
||||
pub fn Standardized_tag(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = Ldh_str;
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Printable US-ASCII excl. "[", "\", "]"
|
||||
///
|
||||
/// dcontent = %d33-90 / %d94-126
|
||||
pub fn is_dcontent(byte: u8) -> bool {
|
||||
matches!(byte, 33..=90 | 94..=126)
|
||||
}
|
||||
|
||||
/// Representing a decimal integer value in the range 0 through 255
|
||||
///
|
||||
/// Snum = 1*3DIGIT
|
||||
pub fn Snum(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = take_while_m_n(1, 3, is_DIGIT);
|
||||
take_while_m_n(1, 3, is_DIGIT)(input)
|
||||
}
|
||||
|
||||
/// IPv6-address-literal = "IPv6:" IPv6-addr
|
||||
pub fn IPv6_address_literal(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((tag_no_case(b"IPv6:"), IPv6_addr));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
|
@ -77,15 +67,6 @@ pub fn IPv6_addr(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// IPv6-hex = 1*4HEXDIG
|
||||
pub fn IPv6_hex(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = take_while_m_n(1, 4, is_hex_digit);
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// IPv6-full = IPv6-hex 7(":" IPv6-hex)
|
||||
pub fn IPv6_full(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((IPv6_hex, count(tuple((tag(b":"), IPv6_hex)), 7)));
|
||||
|
@ -95,6 +76,11 @@ pub fn IPv6_full(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// IPv6-hex = 1*4HEXDIG
|
||||
pub fn IPv6_hex(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
take_while_m_n(1, 4, is_hex_digit)(input)
|
||||
}
|
||||
|
||||
/// The "::" represents at least 2 16-bit groups of zeros.
|
||||
/// No more than 6 groups in addition to the "::" may be present.
|
||||
///
|
||||
|
@ -156,3 +142,26 @@ pub fn IPv6v4_comp(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// General-address-literal = Standardized-tag ":" 1*dcontent
|
||||
pub fn General_address_literal(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((Standardized_tag, tag(b":"), take_while1(is_dcontent)));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Standardized-tag MUST be specified in a Standards-Track RFC and registered with IANA
|
||||
///
|
||||
/// Standardized-tag = Ldh-str
|
||||
pub fn Standardized_tag(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
Ldh_str(input)
|
||||
}
|
||||
|
||||
/// Printable US-ASCII excl. "[", "\", "]"
|
||||
///
|
||||
/// dcontent = %d33-90 / %d94-126
|
||||
pub fn is_dcontent(byte: u8) -> bool {
|
||||
matches!(byte, 33..=90 | 94..=126)
|
||||
}
|
||||
|
|
|
@ -1,32 +1,24 @@
|
|||
use crate::{
|
||||
parse::{
|
||||
address::{General_address_literal, IPv4_address_literal, IPv6_address_literal},
|
||||
base64,
|
||||
imf::atom::is_atext,
|
||||
},
|
||||
types::Command,
|
||||
parse::{address::address_literal, base64, Atom, Domain, Quoted_string, String},
|
||||
types::{Command, Parameter},
|
||||
};
|
||||
use abnf_core::streaming::{is_ALPHA, is_DIGIT, CRLF, DQUOTE, SP};
|
||||
use abnf_core::streaming::{is_ALPHA, is_DIGIT, CRLF, SP};
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::streaming::{tag, tag_no_case, take_while, take_while1, take_while_m_n},
|
||||
combinator::{map_res, opt, recognize},
|
||||
multi::many0,
|
||||
combinator::{map, map_res, opt, recognize, value},
|
||||
multi::separated_list1,
|
||||
sequence::{delimited, preceded, tuple},
|
||||
IResult,
|
||||
};
|
||||
|
||||
pub fn command(input: &[u8]) -> IResult<&[u8], Command> {
|
||||
let mut parser = alt((
|
||||
alt((
|
||||
helo, ehlo, mail, rcpt, data, rset, vrfy, expn, help, noop, quit,
|
||||
starttls, // Extensions
|
||||
auth_login, // https://interoperability.blob.core.windows.net/files/MS-XLOGIN/[MS-XLOGIN].pdf
|
||||
auth_plain, // RFC 4616
|
||||
));
|
||||
|
||||
let (remaining, parsed) = parser(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
))(input)
|
||||
}
|
||||
|
||||
/// helo = "HELO" SP Domain CRLF
|
||||
|
@ -83,11 +75,49 @@ pub fn mail(input: &[u8]) -> IResult<&[u8], Command> {
|
|||
remaining,
|
||||
Command::Mail {
|
||||
reverse_path: data.into(),
|
||||
parameters: maybe_params.map(|params| params.into()),
|
||||
parameters: maybe_params.unwrap_or_default(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
/// Mail-parameters = esmtp-param *(SP esmtp-param)
|
||||
pub fn Mail_parameters(input: &[u8]) -> IResult<&[u8], Vec<Parameter>> {
|
||||
separated_list1(SP, esmtp_param)(input)
|
||||
}
|
||||
|
||||
/// esmtp-param = esmtp-keyword ["=" esmtp-value]
|
||||
pub fn esmtp_param(input: &[u8]) -> IResult<&[u8], Parameter> {
|
||||
map(
|
||||
tuple((esmtp_keyword, opt(preceded(tag(b"="), esmtp_value)))),
|
||||
|(keyword, value)| Parameter::new(keyword, value),
|
||||
)(input)
|
||||
}
|
||||
|
||||
/// esmtp-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
|
||||
pub fn esmtp_keyword(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
let parser = tuple((
|
||||
take_while_m_n(1, 1, |byte| is_ALPHA(byte) || is_DIGIT(byte)),
|
||||
take_while(|byte| is_ALPHA(byte) || is_DIGIT(byte) || byte == b'-'),
|
||||
));
|
||||
|
||||
let (remaining, parsed) = map_res(recognize(parser), std::str::from_utf8)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Any CHAR excluding "=", SP, and control characters.
|
||||
/// If this string is an email address, i.e., a Mailbox,
|
||||
/// then the "xtext" syntax [32] SHOULD be used.
|
||||
///
|
||||
/// esmtp-value = 1*(%d33-60 / %d62-126)
|
||||
pub fn esmtp_value(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
fn is_value_character(byte: u8) -> bool {
|
||||
matches!(byte, 33..=60 | 62..=126)
|
||||
}
|
||||
|
||||
map_res(take_while1(is_value_character), std::str::from_utf8)(input)
|
||||
}
|
||||
|
||||
/// rcpt = "RCPT TO:" ( "<Postmaster@" Domain ">" / "<Postmaster>" / Forward-path ) [SP Rcpt-parameters] CRLF
|
||||
///
|
||||
/// Note that, in a departure from the usual rules for
|
||||
|
@ -98,8 +128,11 @@ pub fn rcpt(input: &[u8]) -> IResult<&[u8], Command> {
|
|||
tag_no_case(b"RCPT TO:"),
|
||||
opt(SP), // Out-of-Spec, but Outlook does it ...
|
||||
alt((
|
||||
recognize(tuple((tag_no_case(b"<Postmaster@"), Domain, tag(b">")))),
|
||||
tag_no_case(b"<Postmaster>"),
|
||||
map_res(
|
||||
recognize(tuple((tag_no_case("<Postmaster@"), Domain, tag(">")))),
|
||||
std::str::from_utf8,
|
||||
),
|
||||
map_res(tag_no_case("<Postmaster>"), std::str::from_utf8),
|
||||
Forward_path,
|
||||
)),
|
||||
opt(preceded(SP, Rcpt_parameters)),
|
||||
|
@ -112,27 +145,24 @@ pub fn rcpt(input: &[u8]) -> IResult<&[u8], Command> {
|
|||
remaining,
|
||||
Command::Rcpt {
|
||||
forward_path: data.into(),
|
||||
parameters: maybe_params.map(|params| params.into()),
|
||||
parameters: maybe_params.unwrap_or_default(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
/// Rcpt-parameters = esmtp-param *(SP esmtp-param)
|
||||
pub fn Rcpt_parameters(input: &[u8]) -> IResult<&[u8], Vec<Parameter>> {
|
||||
separated_list1(SP, esmtp_param)(input)
|
||||
}
|
||||
|
||||
/// data = "DATA" CRLF
|
||||
pub fn data(input: &[u8]) -> IResult<&[u8], Command> {
|
||||
let mut parser = tuple((tag_no_case(b"DATA"), CRLF));
|
||||
|
||||
let (remaining, _) = parser(input)?;
|
||||
|
||||
Ok((remaining, Command::Data))
|
||||
value(Command::Data, tuple((tag_no_case(b"DATA"), CRLF)))(input)
|
||||
}
|
||||
|
||||
/// rset = "RSET" CRLF
|
||||
pub fn rset(input: &[u8]) -> IResult<&[u8], Command> {
|
||||
let mut parser = tuple((tag_no_case(b"RSET"), CRLF));
|
||||
|
||||
let (remaining, _) = parser(input)?;
|
||||
|
||||
Ok((remaining, Command::Rset))
|
||||
value(Command::Rset, tuple((tag_no_case(b"RSET"), CRLF)))(input)
|
||||
}
|
||||
|
||||
/// vrfy = "VRFY" SP String CRLF
|
||||
|
@ -144,7 +174,7 @@ pub fn vrfy(input: &[u8]) -> IResult<&[u8], Command> {
|
|||
Ok((
|
||||
remaining,
|
||||
Command::Vrfy {
|
||||
user_or_mailbox: data.into(),
|
||||
user_or_mailbox: data,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
@ -155,12 +185,7 @@ pub fn expn(input: &[u8]) -> IResult<&[u8], Command> {
|
|||
|
||||
let (remaining, (_, _, data, _)) = parser(input)?;
|
||||
|
||||
Ok((
|
||||
remaining,
|
||||
Command::Expn {
|
||||
mailing_list: data.into(),
|
||||
},
|
||||
))
|
||||
Ok((remaining, Command::Expn { mailing_list: data }))
|
||||
}
|
||||
|
||||
/// help = "HELP" [ SP String ] CRLF
|
||||
|
@ -172,7 +197,7 @@ pub fn help(input: &[u8]) -> IResult<&[u8], Command> {
|
|||
Ok((
|
||||
remaining,
|
||||
Command::Help {
|
||||
argument: maybe_data.map(|data| data.into()),
|
||||
argument: maybe_data,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
@ -186,26 +211,18 @@ pub fn noop(input: &[u8]) -> IResult<&[u8], Command> {
|
|||
Ok((
|
||||
remaining,
|
||||
Command::Noop {
|
||||
argument: maybe_data.map(|data| data.into()),
|
||||
argument: maybe_data,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
/// quit = "QUIT" CRLF
|
||||
pub fn quit(input: &[u8]) -> IResult<&[u8], Command> {
|
||||
let mut parser = tuple((tag_no_case(b"QUIT"), CRLF));
|
||||
|
||||
let (remaining, _) = parser(input)?;
|
||||
|
||||
Ok((remaining, Command::Quit))
|
||||
value(Command::Quit, tuple((tag_no_case(b"QUIT"), CRLF)))(input)
|
||||
}
|
||||
|
||||
pub fn starttls(input: &[u8]) -> IResult<&[u8], Command> {
|
||||
let mut parser = tuple((tag_no_case(b"STARTTLS"), CRLF));
|
||||
|
||||
let (remaining, _) = parser(input)?;
|
||||
|
||||
Ok((remaining, Command::StartTLS))
|
||||
value(Command::StartTLS, tuple((tag_no_case(b"STARTTLS"), CRLF)))(input)
|
||||
}
|
||||
|
||||
/// https://interoperability.blob.core.windows.net/files/MS-XLOGIN/[MS-XLOGIN].pdf
|
||||
|
@ -256,35 +273,25 @@ pub fn auth_plain(input: &[u8]) -> IResult<&[u8], Command> {
|
|||
// ----- 4.1.2. Command Argument Syntax (RFC 5321) -----
|
||||
|
||||
/// Reverse-path = Path / "<>"
|
||||
pub fn Reverse_path(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = alt((Path, tag(b"<>")));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
pub fn Reverse_path(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
alt((Path, value("", tag("<>"))))(input)
|
||||
}
|
||||
|
||||
/// Forward-path = Path
|
||||
pub fn Forward_path(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = Path;
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
pub fn Forward_path(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
Path(input)
|
||||
}
|
||||
|
||||
// Path = "<" [ A-d-l ":" ] Mailbox ">"
|
||||
pub fn Path(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((
|
||||
pub fn Path(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
delimited(
|
||||
tag(b"<"),
|
||||
opt(tuple((A_d_l, tag(b":")))),
|
||||
Mailbox,
|
||||
map_res(
|
||||
recognize(tuple((opt(tuple((A_d_l, tag(b":")))), Mailbox))),
|
||||
std::str::from_utf8,
|
||||
),
|
||||
tag(b">"),
|
||||
));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
)(input)
|
||||
}
|
||||
|
||||
/// A-d-l = At-domain *( "," At-domain )
|
||||
|
@ -292,7 +299,7 @@ pub fn Path(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
/// ; route", MUST BE accepted, SHOULD NOT be
|
||||
/// ; generated, and SHOULD be ignored.
|
||||
pub fn A_d_l(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((At_domain, many0(tuple((tag(b","), At_domain)))));
|
||||
let parser = separated_list1(tag(b","), At_domain);
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
|
@ -308,133 +315,6 @@ pub fn At_domain(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Mail-parameters = esmtp-param *(SP esmtp-param)
|
||||
pub fn Mail_parameters(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((esmtp_param, many0(tuple((SP, esmtp_param)))));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Rcpt-parameters = esmtp-param *(SP esmtp-param)
|
||||
pub fn Rcpt_parameters(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((esmtp_param, many0(tuple((SP, esmtp_param)))));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// esmtp-param = esmtp-keyword ["=" esmtp-value]
|
||||
pub fn esmtp_param(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((esmtp_keyword, opt(tuple((tag(b"="), esmtp_value)))));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// esmtp-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
|
||||
pub fn esmtp_keyword(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((
|
||||
take_while_m_n(1, 1, |byte| is_ALPHA(byte) || is_DIGIT(byte)),
|
||||
take_while(|byte| is_ALPHA(byte) || is_DIGIT(byte) || byte == b'-'),
|
||||
));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Any CHAR excluding "=", SP, and control characters.
|
||||
/// If this string is an email address, i.e., a Mailbox,
|
||||
/// then the "xtext" syntax [32] SHOULD be used.
|
||||
///
|
||||
/// esmtp-value = 1*(%d33-60 / %d62-126)
|
||||
pub fn esmtp_value(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
fn is_value_character(byte: u8) -> bool {
|
||||
matches!(byte, 33..=60 | 62..=126)
|
||||
}
|
||||
|
||||
take_while1(is_value_character)(input)
|
||||
}
|
||||
|
||||
/// Keyword = Ldh-str
|
||||
pub fn Keyword(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = Ldh_str;
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Argument = Atom
|
||||
pub fn Argument(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
Atom(input)
|
||||
}
|
||||
|
||||
/// Domain = sub-domain *("." sub-domain)
|
||||
pub fn Domain(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
let parser = tuple((sub_domain, many0(tuple((tag(b"."), sub_domain)))));
|
||||
|
||||
let (remaining, parsed) = map_res(recognize(parser), std::str::from_utf8)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// sub-domain = Let-dig [Ldh-str]
|
||||
pub fn sub_domain(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((take_while_m_n(1, 1, is_Let_dig), opt(Ldh_str)));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Let-dig = ALPHA / DIGIT
|
||||
pub fn is_Let_dig(byte: u8) -> bool {
|
||||
is_ALPHA(byte) || is_DIGIT(byte)
|
||||
}
|
||||
|
||||
/// Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig
|
||||
pub fn Ldh_str(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = many0(alt((
|
||||
take_while_m_n(1, 1, is_ALPHA),
|
||||
take_while_m_n(1, 1, is_DIGIT),
|
||||
recognize(tuple((tag(b"-"), take_while_m_n(1, 1, is_Let_dig)))),
|
||||
)));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// address-literal = "[" (
|
||||
/// IPv4-address-literal /
|
||||
/// IPv6-address-literal /
|
||||
/// General-address-literal
|
||||
/// ) "]"
|
||||
/// ; See Section 4.1.3
|
||||
pub fn address_literal(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
let mut parser = delimited(
|
||||
tag(b"["),
|
||||
map_res(
|
||||
alt((
|
||||
IPv4_address_literal,
|
||||
IPv6_address_literal,
|
||||
General_address_literal,
|
||||
)),
|
||||
std::str::from_utf8,
|
||||
),
|
||||
tag(b"]"),
|
||||
);
|
||||
|
||||
let (remaining, parsed) = parser(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Mailbox = Local-part "@" ( Domain / address-literal )
|
||||
pub fn Mailbox(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((Local_part, tag(b"@"), alt((Domain, address_literal))));
|
||||
|
@ -447,96 +327,41 @@ pub fn Mailbox(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
/// Local-part = Dot-string / Quoted-string
|
||||
/// ; MAY be case-sensitive
|
||||
pub fn Local_part(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = alt((Dot_string, Quoted_string));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
alt((recognize(Dot_string), recognize(Quoted_string)))(input)
|
||||
}
|
||||
|
||||
/// Dot-string = Atom *("." Atom)
|
||||
pub fn Dot_string(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((Atom, many0(tuple((tag(b"."), Atom)))));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
pub fn Dot_string(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
map_res(
|
||||
recognize(separated_list1(tag(b"."), Atom)),
|
||||
std::str::from_utf8,
|
||||
)(input)
|
||||
}
|
||||
|
||||
/// Atom = 1*atext
|
||||
pub fn Atom(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
take_while1(is_atext)(input)
|
||||
}
|
||||
// Not used?
|
||||
/// Keyword = Ldh-str
|
||||
//pub fn Keyword(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
// Ldh_str(input)
|
||||
//}
|
||||
|
||||
/// Quoted-string = DQUOTE *QcontentSMTP DQUOTE
|
||||
pub fn Quoted_string(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = delimited(DQUOTE, many0(QcontentSMTP), DQUOTE);
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// QcontentSMTP = qtextSMTP / quoted-pairSMTP
|
||||
pub fn QcontentSMTP(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = alt((take_while_m_n(1, 1, is_qtextSMTP), quoted_pairSMTP));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Backslash followed by any ASCII graphic (including itself) or SPace
|
||||
///
|
||||
/// quoted-pairSMTP = %d92 %d32-126
|
||||
pub fn quoted_pairSMTP(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
fn is_ascii_bs_or_sp(byte: u8) -> bool {
|
||||
matches!(byte, 32..=126)
|
||||
}
|
||||
|
||||
let parser = tuple((tag("\\"), take_while_m_n(1, 1, is_ascii_bs_or_sp)));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Within a quoted string, any ASCII graphic or space is permitted
|
||||
/// without blackslash-quoting except double-quote and the backslash itself.
|
||||
///
|
||||
/// qtextSMTP = %d32-33 / %d35-91 / %d93-126
|
||||
pub fn is_qtextSMTP(byte: u8) -> bool {
|
||||
matches!(byte, 32..=33 | 35..=91 | 93..=126)
|
||||
}
|
||||
|
||||
/// String = Atom / Quoted-string
|
||||
pub fn String(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = alt((Atom, Quoted_string));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
// Not used?
|
||||
/// Argument = Atom
|
||||
//pub fn Argument(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
// Atom(input)
|
||||
//}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{ehlo, helo, mail, sub_domain};
|
||||
use super::{ehlo, helo, mail};
|
||||
use crate::types::Command;
|
||||
|
||||
#[test]
|
||||
fn test_subdomain() {
|
||||
let (rem, parsed) = sub_domain(b"example???").unwrap();
|
||||
assert_eq!(parsed, b"example");
|
||||
assert_eq!(rem, b"???");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ehlo() {
|
||||
let (rem, parsed) = ehlo(b"EHLO [123.123.123.123]\r\n???").unwrap();
|
||||
assert_eq!(
|
||||
parsed,
|
||||
Command::Ehlo {
|
||||
fqdn_or_address_literal: b"123.123.123.123".to_vec()
|
||||
fqdn_or_address_literal: "123.123.123.123".into()
|
||||
}
|
||||
);
|
||||
assert_eq!(rem, b"???");
|
||||
|
@ -548,7 +373,7 @@ mod test {
|
|||
assert_eq!(
|
||||
parsed,
|
||||
Command::Helo {
|
||||
fqdn_or_address_literal: b"example.com".to_vec()
|
||||
fqdn_or_address_literal: "example.com".into()
|
||||
}
|
||||
);
|
||||
assert_eq!(rem, b"???");
|
||||
|
@ -560,8 +385,8 @@ mod test {
|
|||
assert_eq!(
|
||||
parsed,
|
||||
Command::Mail {
|
||||
reverse_path: b"<userx@y.foo.org>".to_vec(),
|
||||
parameters: None
|
||||
reverse_path: "userx@y.foo.org".into(),
|
||||
parameters: Vec::default(),
|
||||
}
|
||||
);
|
||||
assert_eq!(rem, b"???");
|
||||
|
|
138
src/parse/mod.rs
138
src/parse/mod.rs
|
@ -1,15 +1,17 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use abnf_core::streaming::{is_ALPHA, is_DIGIT};
|
||||
use crate::{parse::imf::atom::is_atext, types::AtomOrQuoted, utils::unescape_quoted};
|
||||
use abnf_core::streaming::{is_ALPHA, is_DIGIT, DQUOTE};
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::streaming::{tag, take_while},
|
||||
bytes::streaming::{tag, take_while, take_while1, take_while_m_n},
|
||||
character::streaming::digit1,
|
||||
combinator::{map_res, opt, recognize},
|
||||
sequence::tuple,
|
||||
combinator::{map, map_res, opt, recognize},
|
||||
multi::{many0, separated_list1},
|
||||
sequence::{delimited, tuple},
|
||||
IResult,
|
||||
};
|
||||
use std::str::from_utf8;
|
||||
use std::{borrow::Cow, str::from_utf8};
|
||||
|
||||
pub mod address;
|
||||
pub mod command;
|
||||
|
@ -19,10 +21,6 @@ pub mod response;
|
|||
pub mod trace;
|
||||
pub mod utils;
|
||||
|
||||
fn is_base64_char(i: u8) -> bool {
|
||||
is_ALPHA(i) || is_DIGIT(i) || i == b'+' || i == b'/'
|
||||
}
|
||||
|
||||
pub fn base64(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
let mut parser = map_res(
|
||||
recognize(tuple((
|
||||
|
@ -37,6 +35,128 @@ pub fn base64(input: &[u8]) -> IResult<&[u8], &str> {
|
|||
Ok((remaining, base64))
|
||||
}
|
||||
|
||||
fn is_base64_char(i: u8) -> bool {
|
||||
is_ALPHA(i) || is_DIGIT(i) || i == b'+' || i == b'/'
|
||||
}
|
||||
|
||||
pub fn number(input: &[u8]) -> IResult<&[u8], u32> {
|
||||
map_res(map_res(digit1, from_utf8), str::parse::<u32>)(input) // FIXME(perf): use from_utf8_unchecked
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
/// String = Atom / Quoted-string
|
||||
pub fn String(input: &[u8]) -> IResult<&[u8], AtomOrQuoted> {
|
||||
alt((
|
||||
map(Atom, |atom| AtomOrQuoted::Atom(atom.into())),
|
||||
map(Quoted_string, |quoted| AtomOrQuoted::Quoted(quoted.into())),
|
||||
))(input)
|
||||
}
|
||||
|
||||
/// Atom = 1*atext
|
||||
pub fn Atom(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
map_res(take_while1(is_atext), std::str::from_utf8)(input)
|
||||
}
|
||||
|
||||
/// Quoted-string = DQUOTE *QcontentSMTP DQUOTE
|
||||
pub fn Quoted_string(input: &[u8]) -> IResult<&[u8], Cow<'_, str>> {
|
||||
map(
|
||||
delimited(
|
||||
DQUOTE,
|
||||
map_res(recognize(many0(QcontentSMTP)), std::str::from_utf8),
|
||||
DQUOTE,
|
||||
),
|
||||
|s| unescape_quoted(s),
|
||||
)(input)
|
||||
}
|
||||
|
||||
/// QcontentSMTP = qtextSMTP / quoted-pairSMTP
|
||||
pub fn QcontentSMTP(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = alt((take_while_m_n(1, 1, is_qtextSMTP), quoted_pairSMTP));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Within a quoted string, any ASCII graphic or space is permitted
|
||||
/// without blackslash-quoting except double-quote and the backslash itself.
|
||||
///
|
||||
/// qtextSMTP = %d32-33 / %d35-91 / %d93-126
|
||||
pub fn is_qtextSMTP(byte: u8) -> bool {
|
||||
matches!(byte, 32..=33 | 35..=91 | 93..=126)
|
||||
}
|
||||
|
||||
/// Backslash followed by any ASCII graphic (including itself) or SPace
|
||||
///
|
||||
/// quoted-pairSMTP = %d92 %d32-126
|
||||
///
|
||||
/// FIXME: How should e.g. "\a" be interpreted?
|
||||
pub fn quoted_pairSMTP(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
//fn is_value(byte: u8) -> bool {
|
||||
// matches!(byte, 32..=126)
|
||||
//}
|
||||
|
||||
// FIXME: Only allow "\\" and "\"" for now ...
|
||||
fn is_value(byte: u8) -> bool {
|
||||
byte == b'\\' || byte == b'\"'
|
||||
}
|
||||
|
||||
let parser = tuple((tag("\\"), take_while_m_n(1, 1, is_value)));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
/// Domain = sub-domain *("." sub-domain)
|
||||
pub fn Domain(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
let parser = separated_list1(tag(b"."), sub_domain);
|
||||
|
||||
let (remaining, parsed) = map_res(recognize(parser), std::str::from_utf8)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// sub-domain = Let-dig [Ldh-str]
|
||||
pub fn sub_domain(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((take_while_m_n(1, 1, is_Let_dig), opt(Ldh_str)));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Let-dig = ALPHA / DIGIT
|
||||
pub fn is_Let_dig(byte: u8) -> bool {
|
||||
is_ALPHA(byte) || is_DIGIT(byte)
|
||||
}
|
||||
|
||||
/// Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig
|
||||
pub fn Ldh_str(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = many0(alt((
|
||||
take_while_m_n(1, 1, is_ALPHA),
|
||||
take_while_m_n(1, 1, is_DIGIT),
|
||||
recognize(tuple((tag(b"-"), take_while_m_n(1, 1, is_Let_dig)))),
|
||||
)));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::sub_domain;
|
||||
|
||||
#[test]
|
||||
fn test_subdomain() {
|
||||
let (rem, parsed) = sub_domain(b"example???").unwrap();
|
||||
assert_eq!(parsed, b"example");
|
||||
assert_eq!(rem, b"???");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! 4.2. SMTP Replies (RFC 5321)
|
||||
|
||||
use crate::{
|
||||
parse::command::{address_literal, Domain},
|
||||
parse::{address::address_literal, Domain},
|
||||
types::Greeting as GreetingType,
|
||||
};
|
||||
use abnf_core::streaming::{CRLF, SP};
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use crate::{parse::command::Domain, types::EhloOkResp};
|
||||
use crate::{
|
||||
parse::{number, Domain},
|
||||
types::{AuthMechanism, Capability, EhloOkResp},
|
||||
};
|
||||
use abnf_core::streaming::{is_ALPHA, is_DIGIT, CRLF, SP};
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::streaming::{tag, take_while, take_while1, take_while_m_n},
|
||||
combinator::{map, map_res, opt, recognize},
|
||||
bytes::streaming::{tag, tag_no_case, take_while, take_while1, take_while_m_n},
|
||||
combinator::{map, map_res, opt, recognize, value},
|
||||
multi::{many0, separated_list0},
|
||||
sequence::{delimited, preceded, tuple},
|
||||
IResult,
|
||||
|
@ -36,27 +39,11 @@ pub fn ehlo_ok_rsp(input: &[u8]) -> IResult<&[u8], EhloOkResp> {
|
|||
ehlo_line,
|
||||
CRLF,
|
||||
)),
|
||||
|(_, domain, maybe_ehlo, _, lines, _, (keyword, params), _)| EhloOkResp {
|
||||
|(_, domain, maybe_ehlo, _, mut lines, _, line, _)| EhloOkResp {
|
||||
domain: domain.to_owned(),
|
||||
greet: maybe_ehlo.map(|ehlo| ehlo.to_owned()),
|
||||
lines: {
|
||||
let mut lines = lines
|
||||
.iter()
|
||||
.map(|(keyword, params)| {
|
||||
let params = params
|
||||
.iter()
|
||||
.map(|param| param.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
(keyword.to_string(), params)
|
||||
})
|
||||
.collect::<Vec<(String, Vec<String>)>>();
|
||||
lines.push((
|
||||
keyword.to_string(),
|
||||
params
|
||||
.iter()
|
||||
.map(|param| param.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
));
|
||||
lines.push(line);
|
||||
lines
|
||||
},
|
||||
},
|
||||
|
@ -82,8 +69,14 @@ pub fn ehlo_greet(input: &[u8]) -> IResult<&[u8], &str> {
|
|||
/// ehlo-line = ehlo-keyword *( SP ehlo-param )
|
||||
///
|
||||
/// TODO: SMTP servers often respond with "AUTH=LOGIN PLAIN". Why?
|
||||
pub fn ehlo_line(input: &[u8]) -> IResult<&[u8], (&str, Vec<&str>)> {
|
||||
let mut parser = tuple((
|
||||
pub fn ehlo_line(input: &[u8]) -> IResult<&[u8], Capability> {
|
||||
let auth = tuple((
|
||||
tag_no_case("AUTH"),
|
||||
alt((tag_no_case(" "), tag_no_case("="))),
|
||||
separated_list0(SP, auth_mechanism),
|
||||
));
|
||||
|
||||
let other = tuple((
|
||||
map_res(ehlo_keyword, std::str::from_utf8),
|
||||
opt(preceded(
|
||||
alt((SP, tag("="))), // TODO: For Outlook?
|
||||
|
@ -91,9 +84,56 @@ pub fn ehlo_line(input: &[u8]) -> IResult<&[u8], (&str, Vec<&str>)> {
|
|||
)),
|
||||
));
|
||||
|
||||
let (remaining, (ehlo_keyword, ehlo_params)) = parser(input)?;
|
||||
alt((
|
||||
value(Capability::EXPN, tag_no_case("EXPN")),
|
||||
value(Capability::Help, tag_no_case("HELP")),
|
||||
value(Capability::EightBitMIME, tag_no_case("8BITMIME")),
|
||||
map(preceded(tag_no_case("SIZE "), number), Capability::Size),
|
||||
value(Capability::Chunking, tag_no_case("CHUNKING")),
|
||||
value(Capability::BinaryMIME, tag_no_case("BINARYMIME")),
|
||||
value(Capability::Checkpoint, tag_no_case("CHECKPOINT")),
|
||||
value(Capability::DeliverBy, tag_no_case("DELIVERBY")),
|
||||
value(Capability::Pipelining, tag_no_case("PIPELINING")),
|
||||
value(Capability::DSN, tag_no_case("DSN")),
|
||||
value(Capability::ETRN, tag_no_case("ETRN")),
|
||||
value(
|
||||
Capability::EnhancedStatusCodes,
|
||||
tag_no_case("ENHANCEDSTATUSCODES"),
|
||||
),
|
||||
value(Capability::StartTLS, tag_no_case("STARTTLS")),
|
||||
// FIXME: NO-SOLICITING
|
||||
value(Capability::MTRK, tag_no_case("MTRK")),
|
||||
value(Capability::ATRN, tag_no_case("ATRN")),
|
||||
map(auth, |(_, _, mechanisms)| Capability::Auth(mechanisms)),
|
||||
value(Capability::BURL, tag_no_case("BURL")),
|
||||
// FIXME: FUTURERELEASE
|
||||
// FIXME: CONPERM
|
||||
// FIXME: CONNEG
|
||||
value(Capability::SMTPUTF8, tag_no_case("SMTPUTF8")),
|
||||
// FIXME: MT-PRIORITY
|
||||
value(Capability::RRVS, tag_no_case("RRVS")),
|
||||
value(Capability::RequireTLS, tag_no_case("REQUIRETLS")),
|
||||
map(other, |(keyword, params)| Capability::Other {
|
||||
keyword: keyword.into(),
|
||||
params: params
|
||||
.map(|v| v.iter().map(|s| s.to_string()).collect())
|
||||
.unwrap_or_default(),
|
||||
}),
|
||||
))(input)
|
||||
}
|
||||
|
||||
Ok((remaining, (ehlo_keyword, ehlo_params.unwrap_or_default())))
|
||||
pub fn auth_mechanism(input: &[u8]) -> IResult<&[u8], AuthMechanism> {
|
||||
alt((
|
||||
value(AuthMechanism::Login, tag_no_case("LOGIN")),
|
||||
value(AuthMechanism::Plain, tag_no_case("PLAIN")),
|
||||
value(AuthMechanism::CramMD5, tag_no_case("CRAM-MD5")),
|
||||
value(AuthMechanism::CramSHA1, tag_no_case("CRAM-SHA1")),
|
||||
value(AuthMechanism::DigestMD5, tag_no_case("DIGEST-MD5")),
|
||||
value(AuthMechanism::ScramMD5, tag_no_case("SCRAM-MD5")),
|
||||
value(AuthMechanism::GSSAPI, tag_no_case("GSSAPI")),
|
||||
value(AuthMechanism::NTLM, tag_no_case("NTLM")),
|
||||
map(ehlo_param, |param| AuthMechanism::Other(param.to_string())),
|
||||
))(input)
|
||||
}
|
||||
|
||||
/// Additional syntax of ehlo-params depends on ehlo-keyword
|
||||
|
@ -125,6 +165,7 @@ pub fn ehlo_param(input: &[u8]) -> IResult<&[u8], &str> {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::types::AuthMechanism;
|
||||
|
||||
#[test]
|
||||
fn test_ehlo_ok_rsp() {
|
||||
|
@ -144,17 +185,19 @@ mod test {
|
|||
domain: "example.org".into(),
|
||||
greet: Some("hello".into()),
|
||||
lines: vec![
|
||||
(
|
||||
"AUTH".into(),
|
||||
vec!["LOGIN".into(), "CRAM-MD5".into(), "PLAIN".into()]
|
||||
),
|
||||
(
|
||||
"AUTH".into(),
|
||||
vec!["LOGIN".into(), "CRAM-MD5".into(), "PLAIN".into()]
|
||||
),
|
||||
("STARTTLS".into(), vec![]),
|
||||
("SIZE".into(), vec!["12345".into()]),
|
||||
("8BITMIME".into(), vec![]),
|
||||
Capability::Auth(vec![
|
||||
AuthMechanism::Login,
|
||||
AuthMechanism::CramMD5,
|
||||
AuthMechanism::Plain
|
||||
]),
|
||||
Capability::Auth(vec![
|
||||
AuthMechanism::Login,
|
||||
AuthMechanism::CramMD5,
|
||||
AuthMechanism::Plain
|
||||
]),
|
||||
Capability::StartTLS,
|
||||
Capability::Size(12345),
|
||||
Capability::EightBitMIME,
|
||||
],
|
||||
}
|
||||
);
|
||||
|
@ -162,9 +205,8 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_ehlo_line() {
|
||||
let (rem, (keyword, params)) = ehlo_line(b"SIZE 123456\r\n").unwrap();
|
||||
let (rem, capability) = ehlo_line(b"SIZE 123456\r\n").unwrap();
|
||||
assert_eq!(rem, b"\r\n");
|
||||
assert_eq!(keyword, "SIZE");
|
||||
assert_eq!(params, &["123456"]);
|
||||
assert_eq!(capability, Capability::Size(123456));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
use crate::parse::{
|
||||
command::{address_literal, Atom, Domain, Mailbox, Path, Reverse_path, String},
|
||||
address::address_literal,
|
||||
command::{Mailbox, Path, Reverse_path},
|
||||
imf::{
|
||||
datetime::date_time,
|
||||
folding_ws_and_comment::{CFWS, FWS},
|
||||
identification::msg_id,
|
||||
},
|
||||
Atom, Domain, String,
|
||||
};
|
||||
/// 4.4. Trace Information (RFC 5321)
|
||||
use abnf_core::streaming::CRLF;
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::streaming::{tag, tag_no_case},
|
||||
combinator::{opt, recognize},
|
||||
combinator::{map_res, opt, recognize},
|
||||
multi::many1,
|
||||
sequence::tuple,
|
||||
IResult,
|
||||
|
@ -145,7 +147,12 @@ pub fn With(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
/// 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, alt((Atom, msg_id))));
|
||||
let parser = tuple((
|
||||
CFWS,
|
||||
tag_no_case(b"ID"),
|
||||
FWS,
|
||||
recognize(alt((recognize(Atom), msg_id))),
|
||||
));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
|
@ -154,7 +161,12 @@ pub fn ID(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
|
||||
/// 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((Path, Mailbox))));
|
||||
let parser = tuple((
|
||||
CFWS,
|
||||
tag_no_case(b"FOR"),
|
||||
FWS,
|
||||
alt((recognize(Path), Mailbox)),
|
||||
));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
|
@ -174,12 +186,8 @@ pub fn Additional_Registered_Clauses(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
}
|
||||
|
||||
/// Link = "TCP" / Addtl-Link
|
||||
pub fn Link(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = alt((tag_no_case(b"TCP"), Addtl_Link));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
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
|
||||
|
@ -187,21 +195,17 @@ pub fn Link(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
/// SHOULD NOT use unregistered names.
|
||||
///
|
||||
/// Addtl-Link = Atom
|
||||
pub fn Addtl_Link(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = Atom;
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
pub fn Addtl_Link(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
Atom(input)
|
||||
}
|
||||
|
||||
/// Protocol = "ESMTP" / "SMTP" / Attdl-Protocol
|
||||
pub fn Protocol(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = alt((tag_no_case(b"ESMTP"), tag_no_case(b"SMTP"), Attdl_Protocol));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
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
|
||||
|
@ -209,10 +213,6 @@ pub fn Protocol(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
/// use unregistered names.
|
||||
///
|
||||
/// Attdl-Protocol = Atom
|
||||
pub fn Attdl_Protocol(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = Atom;
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
pub fn Attdl_Protocol(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
Atom(input)
|
||||
}
|
||||
|
|
319
src/types.rs
319
src/types.rs
|
@ -1,21 +1,21 @@
|
|||
use crate::escape;
|
||||
use crate::utils::escape_quoted;
|
||||
use std::io::Write;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Command {
|
||||
Ehlo {
|
||||
fqdn_or_address_literal: Vec<u8>,
|
||||
fqdn_or_address_literal: String,
|
||||
},
|
||||
Helo {
|
||||
fqdn_or_address_literal: Vec<u8>,
|
||||
fqdn_or_address_literal: String,
|
||||
},
|
||||
Mail {
|
||||
reverse_path: Vec<u8>,
|
||||
parameters: Option<Vec<u8>>,
|
||||
reverse_path: String,
|
||||
parameters: Vec<Parameter>,
|
||||
},
|
||||
Rcpt {
|
||||
forward_path: Vec<u8>,
|
||||
parameters: Option<Vec<u8>>,
|
||||
forward_path: String,
|
||||
parameters: Vec<Parameter>,
|
||||
},
|
||||
Data,
|
||||
Rset,
|
||||
|
@ -26,7 +26,7 @@ pub enum Command {
|
|||
/// This command has no effect on the reverse-path buffer, the forward-
|
||||
/// path buffer, or the mail data buffer.
|
||||
Vrfy {
|
||||
user_or_mailbox: Vec<u8>,
|
||||
user_or_mailbox: AtomOrQuoted,
|
||||
},
|
||||
/// This command asks the receiver to confirm that the argument
|
||||
/// identifies a mailing list, and if so, to return the membership of
|
||||
|
@ -38,7 +38,7 @@ pub enum Command {
|
|||
/// path buffer, or the mail data buffer, and it may be issued at any
|
||||
/// time.
|
||||
Expn {
|
||||
mailing_list: Vec<u8>,
|
||||
mailing_list: AtomOrQuoted,
|
||||
},
|
||||
/// This command causes the server to send helpful information to the
|
||||
/// client. The command MAY take an argument (e.g., any command name)
|
||||
|
@ -51,7 +51,7 @@ pub enum Command {
|
|||
/// path buffer, or the mail data buffer, and it may be issued at any
|
||||
/// time.
|
||||
Help {
|
||||
argument: Option<Vec<u8>>,
|
||||
argument: Option<AtomOrQuoted>,
|
||||
},
|
||||
/// This command does not affect any parameters or previously entered
|
||||
/// commands. It specifies no action other than that the receiver send a
|
||||
|
@ -63,7 +63,7 @@ pub enum Command {
|
|||
/// path buffer, or the mail data buffer, and it may be issued at any
|
||||
/// time.
|
||||
Noop {
|
||||
argument: Option<Vec<u8>>,
|
||||
argument: Option<AtomOrQuoted>,
|
||||
},
|
||||
/// This command specifies that the receiver MUST send a "221 OK" reply,
|
||||
/// and then close the transmission channel.
|
||||
|
@ -90,6 +90,18 @@ pub enum Command {
|
|||
AuthPlain(Option<String>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Parameter {
|
||||
keyword: String,
|
||||
value: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AtomOrQuoted {
|
||||
Atom(String),
|
||||
Quoted(String),
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
|
@ -112,60 +124,7 @@ impl Command {
|
|||
Command::AuthPlain(_) => "AUTHPLAIN",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: try to derive(Debug) instead
|
||||
impl std::fmt::Debug for Command {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||
use Command::*;
|
||||
|
||||
match self {
|
||||
Ehlo {
|
||||
fqdn_or_address_literal,
|
||||
} => write!(f, "Ehlo({})", escape(fqdn_or_address_literal)),
|
||||
Helo {
|
||||
fqdn_or_address_literal,
|
||||
} => write!(f, "Helo({})", escape(fqdn_or_address_literal)),
|
||||
Mail {
|
||||
reverse_path: path,
|
||||
parameters: None,
|
||||
} => write!(f, "Mail({})", escape(path)),
|
||||
Mail {
|
||||
reverse_path: path,
|
||||
parameters: Some(params),
|
||||
} => write!(f, "Mail({}, {})", escape(path), escape(params)),
|
||||
Rcpt {
|
||||
forward_path: data,
|
||||
parameters: None,
|
||||
} => write!(f, "Rcpt({})", escape(data)),
|
||||
Rcpt {
|
||||
forward_path: data,
|
||||
parameters: Some(params),
|
||||
} => write!(f, "Rcpt({}, {})", escape(data), escape(params)),
|
||||
Data => write!(f, "Data"),
|
||||
Rset => write!(f, "Rset"),
|
||||
Vrfy { user_or_mailbox } => write!(f, "Vrfy({})", escape(user_or_mailbox)),
|
||||
Expn { mailing_list } => write!(f, "Expn({})", escape(mailing_list)),
|
||||
Help { argument: None } => write!(f, "Help"),
|
||||
Help {
|
||||
argument: Some(data),
|
||||
} => write!(f, "Help({})", escape(data)),
|
||||
Noop { argument: None } => write!(f, "Noop"),
|
||||
Noop {
|
||||
argument: Some(data),
|
||||
} => write!(f, "Noop({})", escape(data)),
|
||||
Quit => write!(f, "Quit"),
|
||||
// Extensions
|
||||
StartTLS => write!(f, "StartTLS"),
|
||||
// TODO: SMTP Auth
|
||||
AuthLogin(data) => write!(f, "AuthLogin({:?})", data),
|
||||
// TODO: SMTP Auth
|
||||
AuthPlain(data) => write!(f, "AuthPlain({:?})", data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn serialize(&self, writer: &mut impl Write) -> std::io::Result<()> {
|
||||
use Command::*;
|
||||
|
||||
|
@ -175,50 +134,42 @@ impl Command {
|
|||
fqdn_or_address_literal,
|
||||
} => {
|
||||
writer.write_all(b"HELO ")?;
|
||||
writer.write_all(fqdn_or_address_literal)?;
|
||||
writer.write_all(fqdn_or_address_literal.as_bytes())?;
|
||||
}
|
||||
// ehlo = "EHLO" SP ( Domain / address-literal ) CRLF
|
||||
Ehlo {
|
||||
fqdn_or_address_literal,
|
||||
} => {
|
||||
writer.write_all(b"EHLO ")?;
|
||||
writer.write_all(fqdn_or_address_literal)?;
|
||||
writer.write_all(fqdn_or_address_literal.as_bytes())?;
|
||||
}
|
||||
// mail = "MAIL FROM:" Reverse-path [SP Mail-parameters] CRLF
|
||||
Mail {
|
||||
reverse_path,
|
||||
parameters: None,
|
||||
parameters,
|
||||
} => {
|
||||
writer.write_all(b"MAIL FROM:<")?;
|
||||
writer.write_all(reverse_path)?;
|
||||
writer.write_all(reverse_path.as_bytes())?;
|
||||
writer.write_all(b">")?;
|
||||
}
|
||||
Mail {
|
||||
reverse_path,
|
||||
parameters: Some(parameters),
|
||||
} => {
|
||||
writer.write_all(b"MAIL FROM:<")?;
|
||||
writer.write_all(reverse_path)?;
|
||||
writer.write_all(b"> ")?;
|
||||
writer.write_all(parameters)?;
|
||||
|
||||
for parameter in parameters {
|
||||
writer.write_all(b" ")?;
|
||||
parameter.serialize(writer)?;
|
||||
}
|
||||
}
|
||||
// rcpt = "RCPT TO:" ( "<Postmaster@" Domain ">" / "<Postmaster>" / Forward-path ) [SP Rcpt-parameters] CRLF
|
||||
Rcpt {
|
||||
forward_path,
|
||||
parameters: None,
|
||||
parameters,
|
||||
} => {
|
||||
writer.write_all(b"RCPT TO:<")?;
|
||||
writer.write_all(forward_path)?;
|
||||
writer.write_all(forward_path.as_bytes())?;
|
||||
writer.write_all(b">")?;
|
||||
}
|
||||
Rcpt {
|
||||
forward_path,
|
||||
parameters: Some(parameters),
|
||||
} => {
|
||||
writer.write_all(b"RCPT TO:<")?;
|
||||
writer.write_all(forward_path)?;
|
||||
writer.write_all(b"> ")?;
|
||||
writer.write_all(parameters)?;
|
||||
|
||||
for parameter in parameters {
|
||||
writer.write_all(b" ")?;
|
||||
parameter.serialize(writer)?;
|
||||
}
|
||||
}
|
||||
// data = "DATA" CRLF
|
||||
Data => writer.write_all(b"DATA")?,
|
||||
|
@ -227,12 +178,12 @@ impl Command {
|
|||
// vrfy = "VRFY" SP String CRLF
|
||||
Vrfy { user_or_mailbox } => {
|
||||
writer.write_all(b"VRFY ")?;
|
||||
writer.write_all(user_or_mailbox)?;
|
||||
user_or_mailbox.serialize(writer)?;
|
||||
}
|
||||
// expn = "EXPN" SP String CRLF
|
||||
Expn { mailing_list } => {
|
||||
writer.write_all(b"EXPN ")?;
|
||||
writer.write_all(mailing_list)?;
|
||||
mailing_list.serialize(writer)?;
|
||||
}
|
||||
// help = "HELP" [ SP String ] CRLF
|
||||
Help { argument: None } => writer.write_all(b"HELP")?,
|
||||
|
@ -240,7 +191,7 @@ impl Command {
|
|||
argument: Some(data),
|
||||
} => {
|
||||
writer.write_all(b"HELP ")?;
|
||||
writer.write_all(data)?;
|
||||
data.serialize(writer)?;
|
||||
}
|
||||
// noop = "NOOP" [ SP String ] CRLF
|
||||
Noop { argument: None } => writer.write_all(b"NOOP")?,
|
||||
|
@ -248,7 +199,7 @@ impl Command {
|
|||
argument: Some(data),
|
||||
} => {
|
||||
writer.write_all(b"NOOP ")?;
|
||||
writer.write_all(data)?;
|
||||
data.serialize(writer)?;
|
||||
}
|
||||
// quit = "QUIT" CRLF
|
||||
Quit => writer.write_all(b"QUIT")?,
|
||||
|
@ -277,6 +228,45 @@ impl Command {
|
|||
}
|
||||
}
|
||||
|
||||
impl Parameter {
|
||||
pub fn new<K: Into<String>, V: Into<String>>(keyword: K, value: Option<V>) -> Parameter {
|
||||
Parameter {
|
||||
keyword: keyword.into(),
|
||||
value: value.map(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize(&self, writer: &mut impl Write) -> std::io::Result<()> {
|
||||
writer.write_all(self.keyword.as_bytes())?;
|
||||
|
||||
if let Some(ref value) = self.value {
|
||||
writer.write_all(b"=")?;
|
||||
writer.write_all(value.as_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AtomOrQuoted {
|
||||
pub fn serialize(&self, writer: &mut impl Write) -> std::io::Result<()> {
|
||||
match self {
|
||||
AtomOrQuoted::Atom(atom) => {
|
||||
writer.write_all(atom.as_bytes())?;
|
||||
}
|
||||
AtomOrQuoted::Quoted(quoted) => {
|
||||
writer.write_all(b"\"")?;
|
||||
writer.write_all(escape_quoted(quoted).as_bytes())?;
|
||||
writer.write_all(b"\"")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Greeting {
|
||||
pub domain: String,
|
||||
|
@ -289,7 +279,146 @@ pub struct Greeting {
|
|||
pub struct EhloOkResp {
|
||||
pub domain: String,
|
||||
pub greet: Option<String>,
|
||||
pub lines: Vec<EhloLine>,
|
||||
pub lines: Vec<Capability>,
|
||||
}
|
||||
|
||||
pub type EhloLine = (String, Vec<String>);
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Capability {
|
||||
// Send as mail [RFC821]
|
||||
// The description of SEND was updated by [RFC1123] and then its actual use was deprecated in [RFC2821]
|
||||
// SEND,
|
||||
|
||||
// Send as mail or to terminal [RFC821]
|
||||
// The description of SOML was updated by [RFC1123] and then its actual use was deprecated in [RFC2821]
|
||||
// SOML,
|
||||
|
||||
// Send as mail and to terminal [RFC821]
|
||||
// The description of SAML was updated by [RFC1123] and then its actual use was deprecated in [RFC2821]
|
||||
// SAML,
|
||||
|
||||
// Interchange the client and server roles [RFC821]
|
||||
// The actual use of TURN was deprecated in [RFC2821]
|
||||
// TURN,
|
||||
|
||||
// SMTP Responsible Submitter [RFC4405]
|
||||
// Deprecated by [https://datatracker.ietf.org/doc/status-change-change-sender-id-to-historic].
|
||||
// SUBMITTER,
|
||||
|
||||
// Internationalized email address [RFC5336]
|
||||
// Experimental; deprecated in [RFC6531].
|
||||
// UTF8SMTP,
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
/// Verbose [Eric Allman]
|
||||
// VERB,
|
||||
|
||||
/// One message transaction only [Eric Allman]
|
||||
// ONEX,
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
/// Expand the mailing list [RFC821]
|
||||
/// Command description updated by [RFC5321]
|
||||
EXPN,
|
||||
/// Supply helpful information [RFC821]
|
||||
/// Command description updated by [RFC5321]
|
||||
Help,
|
||||
|
||||
/// SMTP and Submit transport of 8bit MIME content [RFC6152]
|
||||
EightBitMIME,
|
||||
|
||||
/// Message size declaration [RFC1870]
|
||||
Size(u32),
|
||||
|
||||
/// Chunking [RFC3030]
|
||||
Chunking,
|
||||
|
||||
/// Binary MIME [RFC3030]
|
||||
BinaryMIME,
|
||||
|
||||
/// Checkpoint/Restart [RFC1845]
|
||||
Checkpoint,
|
||||
|
||||
/// Deliver By [RFC2852]
|
||||
DeliverBy,
|
||||
|
||||
/// Command Pipelining [RFC2920]
|
||||
Pipelining,
|
||||
|
||||
/// Delivery Status Notification [RFC3461]
|
||||
DSN,
|
||||
|
||||
/// Extended Turn [RFC1985]
|
||||
/// SMTP [RFC5321] only. Not for use on Submit port 587.
|
||||
ETRN,
|
||||
|
||||
/// Enhanced Status Codes [RFC2034]
|
||||
EnhancedStatusCodes,
|
||||
|
||||
/// Start TLS [RFC3207]
|
||||
StartTLS,
|
||||
|
||||
/// Notification of no soliciting [RFC3865]
|
||||
// NoSoliciting,
|
||||
|
||||
/// Message Tracking [RFC3885]
|
||||
MTRK,
|
||||
|
||||
/// Authenticated TURN [RFC2645]
|
||||
/// SMTP [RFC5321] only. Not for use on Submit port 587.
|
||||
ATRN,
|
||||
|
||||
/// Authentication [RFC4954]
|
||||
Auth(Vec<AuthMechanism>),
|
||||
|
||||
/// Remote Content [RFC4468]
|
||||
/// Submit [RFC6409] only. Not for use with SMTP on port 25.
|
||||
BURL,
|
||||
|
||||
/// Future Message Release [RFC4865]
|
||||
// FutureRelease,
|
||||
|
||||
/// Content Conversion Permission [RFC4141]
|
||||
// ConPerm,
|
||||
|
||||
/// Content Conversion Negotiation [RFC4141]
|
||||
// ConNeg,
|
||||
|
||||
/// Internationalized email address [RFC6531]
|
||||
SMTPUTF8,
|
||||
|
||||
/// Priority Message Handling [RFC6710]
|
||||
// MTPRIORITY,
|
||||
|
||||
/// Require Recipient Valid Since [RFC7293]
|
||||
RRVS,
|
||||
|
||||
/// Require TLS [RFC8689]
|
||||
RequireTLS,
|
||||
|
||||
// Observed ...
|
||||
// TIME,
|
||||
// XACK,
|
||||
// VERP,
|
||||
// VRFY,
|
||||
/// Other
|
||||
Other {
|
||||
keyword: String,
|
||||
params: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AuthMechanism {
|
||||
Plain,
|
||||
Login,
|
||||
GSSAPI,
|
||||
|
||||
CramMD5,
|
||||
CramSHA1,
|
||||
ScramMD5,
|
||||
DigestMD5,
|
||||
NTLM,
|
||||
|
||||
Other(String),
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
pub(crate) fn escape_quoted(unescaped: &str) -> Cow<str> {
|
||||
let mut escaped = Cow::Borrowed(unescaped);
|
||||
|
||||
if escaped.contains('\\') {
|
||||
escaped = Cow::Owned(escaped.replace("\\", "\\\\"));
|
||||
}
|
||||
|
||||
if escaped.contains('\"') {
|
||||
escaped = Cow::Owned(escaped.replace("\"", "\\\""));
|
||||
}
|
||||
|
||||
escaped
|
||||
}
|
||||
|
||||
pub(crate) fn unescape_quoted(escaped: &str) -> Cow<str> {
|
||||
let mut unescaped = Cow::Borrowed(escaped);
|
||||
|
||||
if unescaped.contains("\\\\") {
|
||||
unescaped = Cow::Owned(unescaped.replace("\\\\", "\\"));
|
||||
}
|
||||
|
||||
if unescaped.contains("\\\"") {
|
||||
unescaped = Cow::Owned(unescaped.replace("\\\"", "\""));
|
||||
}
|
||||
|
||||
unescaped
|
||||
}
|
Loading…
Reference in New Issue