API updates.
This commit is contained in:
parent
5765b4b9fe
commit
97092b606c
5 changed files with 105 additions and 102 deletions
|
@ -209,12 +209,11 @@ pub enum Capability {
|
|||
Verb,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum MtPriority {
|
||||
Mixer,
|
||||
Stanag4406,
|
||||
Nsep,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
@ -254,7 +253,8 @@ pub enum Category {
|
|||
pub enum Error {
|
||||
NeedsMoreData { bytes_left: usize },
|
||||
UnknownCommand,
|
||||
InvalidAddress,
|
||||
InvalidSenderAddress,
|
||||
InvalidRecipientAddress,
|
||||
SyntaxError { syntax: &'static str },
|
||||
InvalidParameter { param: &'static str },
|
||||
UnsupportedParameter { param: String },
|
||||
|
|
|
@ -28,7 +28,7 @@ impl ReceiverParser for Request<String> {
|
|||
}
|
||||
} else {
|
||||
parser.seek_lf()?;
|
||||
return Err(Error::InvalidAddress);
|
||||
return Err(Error::InvalidRecipientAddress);
|
||||
}
|
||||
}
|
||||
parser.seek_lf()?;
|
||||
|
@ -51,7 +51,7 @@ impl ReceiverParser for Request<String> {
|
|||
}
|
||||
} else {
|
||||
parser.seek_lf()?;
|
||||
return Err(Error::InvalidAddress);
|
||||
return Err(Error::InvalidSenderAddress);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1479,26 +1479,29 @@ mod tests {
|
|||
parameters: vec![],
|
||||
}),
|
||||
),
|
||||
("MAIL FROM:<@invalid>", Err(Error::InvalidAddress)),
|
||||
("MAIL FROM:<hi@@invalid.org>", Err(Error::InvalidAddress)),
|
||||
("MAIL FROM:<@invalid>", Err(Error::InvalidSenderAddress)),
|
||||
(
|
||||
"MAIL FROM:<hi@@invalid.org>",
|
||||
Err(Error::InvalidSenderAddress),
|
||||
),
|
||||
(
|
||||
"MAIL FROM:<hi..there@invalid.org>",
|
||||
Err(Error::InvalidAddress),
|
||||
Err(Error::InvalidSenderAddress),
|
||||
),
|
||||
(
|
||||
"MAIL FROM:<hi.there@invalid..org>",
|
||||
Err(Error::InvalidAddress),
|
||||
Err(Error::InvalidSenderAddress),
|
||||
),
|
||||
(
|
||||
"MAIL FROM:<hi.there@.invalid.org>",
|
||||
Err(Error::InvalidAddress),
|
||||
Err(Error::InvalidSenderAddress),
|
||||
),
|
||||
(
|
||||
"MAIL FROM:<.hi.there@invalid.org>",
|
||||
Err(Error::InvalidAddress),
|
||||
Err(Error::InvalidSenderAddress),
|
||||
),
|
||||
("MAIL FROM:<@>", Err(Error::InvalidAddress)),
|
||||
("MAIL FROM:<.@.>", Err(Error::InvalidAddress)),
|
||||
("MAIL FROM:<@>", Err(Error::InvalidSenderAddress)),
|
||||
("MAIL FROM:<.@.>", Err(Error::InvalidSenderAddress)),
|
||||
(
|
||||
"RCPT TO:<孫子@áéíóú.org>",
|
||||
Ok(Request::Rcpt {
|
||||
|
|
|
@ -12,17 +12,24 @@ pub trait ReceiverParser: Sized {
|
|||
}
|
||||
|
||||
pub struct DataReceiver {
|
||||
pub buf: Vec<u8>,
|
||||
crlf_dot: bool,
|
||||
last_ch: u8,
|
||||
prev_last_ch: u8,
|
||||
}
|
||||
|
||||
pub struct BdatReceiver {
|
||||
pub buf: Vec<u8>,
|
||||
pub is_last: bool,
|
||||
bytes_left: usize,
|
||||
}
|
||||
|
||||
pub struct DummyDataReceiver {
|
||||
is_bdat: bool,
|
||||
bdat_bytes_left: usize,
|
||||
crlf_dot: bool,
|
||||
last_ch: u8,
|
||||
prev_last_ch: u8,
|
||||
}
|
||||
|
||||
impl<T: ReceiverParser> Default for Receiver<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
@ -60,28 +67,27 @@ impl DataReceiver {
|
|||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
buf: Vec::with_capacity(1024),
|
||||
crlf_dot: false,
|
||||
last_ch: 0,
|
||||
prev_last_ch: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ingest(&mut self, bytes: &mut Iter<'_, u8>) -> bool {
|
||||
pub fn ingest(&mut self, bytes: &mut Iter<'_, u8>, buf: &mut Vec<u8>) -> bool {
|
||||
for &ch in bytes {
|
||||
match ch {
|
||||
b'.' if self.last_ch == b'\n' && self.prev_last_ch == b'\r' => {
|
||||
self.crlf_dot = true;
|
||||
}
|
||||
b'\n' if self.crlf_dot && self.last_ch == b'\r' => {
|
||||
self.buf.truncate(self.buf.len() - 3);
|
||||
buf.truncate(buf.len() - 3);
|
||||
return true;
|
||||
}
|
||||
b'\r' => {
|
||||
self.buf.push(ch);
|
||||
buf.push(ch);
|
||||
}
|
||||
_ => {
|
||||
self.buf.push(ch);
|
||||
buf.push(ch);
|
||||
self.crlf_dot = false;
|
||||
}
|
||||
}
|
||||
|
@ -94,22 +100,78 @@ impl DataReceiver {
|
|||
}
|
||||
|
||||
impl BdatReceiver {
|
||||
pub fn new(bytes_left: usize) -> Self {
|
||||
pub fn new(chunk_size: usize, is_last: bool) -> Self {
|
||||
Self {
|
||||
buf: Vec::with_capacity(bytes_left),
|
||||
bytes_left,
|
||||
bytes_left: chunk_size,
|
||||
is_last,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ingest(&mut self, bytes: &mut Iter<'_, u8>, buf: &mut Vec<u8>) -> bool {
|
||||
while self.bytes_left > 0 {
|
||||
if let Some(&ch) = bytes.next() {
|
||||
buf.push(ch);
|
||||
self.bytes_left -= 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl DummyDataReceiver {
|
||||
pub fn new_bdat(chunk_size: usize) -> Self {
|
||||
Self {
|
||||
bdat_bytes_left: chunk_size,
|
||||
is_bdat: true,
|
||||
crlf_dot: false,
|
||||
last_ch: 0,
|
||||
prev_last_ch: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_data(data: &DataReceiver) -> Self {
|
||||
Self {
|
||||
is_bdat: false,
|
||||
bdat_bytes_left: 0,
|
||||
crlf_dot: data.crlf_dot,
|
||||
last_ch: data.last_ch,
|
||||
prev_last_ch: data.prev_last_ch,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ingest(&mut self, bytes: &mut Iter<'_, u8>) -> bool {
|
||||
if !self.is_bdat {
|
||||
for &ch in bytes {
|
||||
self.buf.push(ch);
|
||||
if self.buf.len() == self.bytes_left {
|
||||
match ch {
|
||||
b'.' if self.last_ch == b'\n' && self.prev_last_ch == b'\r' => {
|
||||
self.crlf_dot = true;
|
||||
}
|
||||
b'\n' if self.crlf_dot && self.last_ch == b'\r' => {
|
||||
return true;
|
||||
}
|
||||
b'\r' => {}
|
||||
_ => {
|
||||
self.crlf_dot = false;
|
||||
}
|
||||
}
|
||||
self.prev_last_ch = self.last_ch;
|
||||
self.last_ch = ch;
|
||||
}
|
||||
|
||||
false
|
||||
} else {
|
||||
while self.bdat_bytes_left > 0 {
|
||||
if bytes.next().is_some() {
|
||||
self.bdat_bytes_left -= 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,9 +194,10 @@ mod tests {
|
|||
),
|
||||
] {
|
||||
let mut r = DataReceiver::new();
|
||||
let mut buf = Vec::new();
|
||||
for data in &data {
|
||||
if r.ingest(&mut data.as_bytes().iter()) {
|
||||
assert_eq!(message, String::from_utf8(r.buf).unwrap());
|
||||
if r.ingest(&mut data.as_bytes().iter(), &mut buf) {
|
||||
assert_eq!(message, String::from_utf8(buf).unwrap());
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,13 @@ use std::{
|
|||
use crate::*;
|
||||
|
||||
impl<T: Display> EhloResponse<T> {
|
||||
pub fn new(hostname: T) -> Self {
|
||||
Self {
|
||||
hostname,
|
||||
capabilities: Vec::with_capacity(20),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self, mut writer: impl Write) -> io::Result<()> {
|
||||
write!(writer, "250-{} says hello\r\n", self.hostname)?;
|
||||
let len = self.capabilities.len();
|
||||
|
@ -58,7 +65,6 @@ impl<T: Display> EhloResponse<T> {
|
|||
MtPriority::Mixer => "MIXER",
|
||||
MtPriority::Stanag4406 => "STANAG4406",
|
||||
MtPriority::Nsep => "NSEP",
|
||||
MtPriority::None => "MIXER",
|
||||
}
|
||||
),
|
||||
Capability::Mtrk => write!(writer, "MTRK\r\n"),
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::slice::Iter;
|
|||
|
||||
use crate::{
|
||||
request::{parser::Rfc5321Parser, receiver::ReceiverParser},
|
||||
Capability, EhloResponse, Error, IntoString, MtPriority, Response, LF,
|
||||
*,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
@ -299,75 +299,6 @@ impl Response<String> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Capability {
|
||||
pub fn parse(value: &[u8]) -> Option<Capability> {
|
||||
if value.eq_ignore_ascii_case(b"8BITMIME") {
|
||||
Capability::EightBitMime.into()
|
||||
} else if value.eq_ignore_ascii_case(b"ATRN") {
|
||||
Capability::Atrn.into()
|
||||
} else if value.eq_ignore_ascii_case(b"AUTH") {
|
||||
Capability::Auth { mechanisms: 0 }.into()
|
||||
} else if value.eq_ignore_ascii_case(b"BINARYMIME") {
|
||||
Capability::BinaryMime.into()
|
||||
} else if value.eq_ignore_ascii_case(b"BURL") {
|
||||
Capability::Burl.into()
|
||||
} else if value.eq_ignore_ascii_case(b"CHECKPOINT") {
|
||||
Capability::Checkpoint.into()
|
||||
} else if value.eq_ignore_ascii_case(b"CHUNKING") {
|
||||
Capability::Chunking.into()
|
||||
} else if value.eq_ignore_ascii_case(b"CONNEG") {
|
||||
Capability::Conneg.into()
|
||||
} else if value.eq_ignore_ascii_case(b"CONPERM") {
|
||||
Capability::Conperm.into()
|
||||
} else if value.eq_ignore_ascii_case(b"DELIVERBY") {
|
||||
Capability::DeliverBy { min: 0 }.into()
|
||||
} else if value.eq_ignore_ascii_case(b"DSN") {
|
||||
Capability::Dsn.into()
|
||||
} else if value.eq_ignore_ascii_case(b"ENHANCEDSTATUSCODES") {
|
||||
Capability::EnhancedStatusCodes.into()
|
||||
} else if value.eq_ignore_ascii_case(b"ETRN") {
|
||||
Capability::Etrn.into()
|
||||
} else if value.eq_ignore_ascii_case(b"EXPN") {
|
||||
Capability::Expn.into()
|
||||
} else if value.eq_ignore_ascii_case(b"FUTURERELEASE") {
|
||||
Capability::FutureRelease {
|
||||
max_interval: 0,
|
||||
max_datetime: 0,
|
||||
}
|
||||
.into()
|
||||
} else if value.eq_ignore_ascii_case(b"HELP") {
|
||||
Capability::Help.into()
|
||||
} else if value.eq_ignore_ascii_case(b"MT-PRIORITY") {
|
||||
Capability::MtPriority {
|
||||
priority: MtPriority::Mixer,
|
||||
}
|
||||
.into()
|
||||
} else if value.eq_ignore_ascii_case(b"MTRK") {
|
||||
Capability::Mtrk.into()
|
||||
} else if value.eq_ignore_ascii_case(b"NO-SOLICITING") {
|
||||
Capability::NoSoliciting { keywords: None }.into()
|
||||
} else if value.eq_ignore_ascii_case(b"ONEX") {
|
||||
Capability::Onex.into()
|
||||
} else if value.eq_ignore_ascii_case(b"PIPELINING") {
|
||||
Capability::Pipelining.into()
|
||||
} else if value.eq_ignore_ascii_case(b"REQUIRETLS") {
|
||||
Capability::RequireTls.into()
|
||||
} else if value.eq_ignore_ascii_case(b"RRVS") {
|
||||
Capability::Rrvs.into()
|
||||
} else if value.eq_ignore_ascii_case(b"SIZE") {
|
||||
Capability::Size { size: 0 }.into()
|
||||
} else if value.eq_ignore_ascii_case(b"SMTPUTF8") {
|
||||
Capability::SmtpUtf8.into()
|
||||
} else if value.eq_ignore_ascii_case(b"STARTTLS") {
|
||||
Capability::StartTls.into()
|
||||
} else if value.eq_ignore_ascii_case(b"VERB") {
|
||||
Capability::Verb.into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
|
|
Loading…
Reference in a new issue