From 97092b606c68ccfd55735a242b784b4d7745c583 Mon Sep 17 00:00:00 2001 From: Mauro D Date: Sun, 18 Dec 2022 15:32:42 +0000 Subject: [PATCH] API updates. --- src/lib.rs | 6 +-- src/request/parser.rs | 23 ++++++---- src/request/receiver.rs | 99 ++++++++++++++++++++++++++++++++-------- src/response/generate.rs | 8 +++- src/response/parser.rs | 71 +--------------------------- 5 files changed, 105 insertions(+), 102 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a4ba4d7..07598fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 }, diff --git a/src/request/parser.rs b/src/request/parser.rs index daad275..c5678e2 100644 --- a/src/request/parser.rs +++ b/src/request/parser.rs @@ -28,7 +28,7 @@ impl ReceiverParser for Request { } } else { parser.seek_lf()?; - return Err(Error::InvalidAddress); + return Err(Error::InvalidRecipientAddress); } } parser.seek_lf()?; @@ -51,7 +51,7 @@ impl ReceiverParser for Request { } } 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:", Err(Error::InvalidAddress)), + ("MAIL FROM:<@invalid>", Err(Error::InvalidSenderAddress)), + ( + "MAIL FROM:", + Err(Error::InvalidSenderAddress), + ), ( "MAIL FROM:", - Err(Error::InvalidAddress), + Err(Error::InvalidSenderAddress), ), ( "MAIL FROM:", - Err(Error::InvalidAddress), + Err(Error::InvalidSenderAddress), ), ( "MAIL FROM:", - 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 { diff --git a/src/request/receiver.rs b/src/request/receiver.rs index b627212..fb6c026 100644 --- a/src/request/receiver.rs +++ b/src/request/receiver.rs @@ -12,17 +12,24 @@ pub trait ReceiverParser: Sized { } pub struct DataReceiver { - pub buf: Vec, crlf_dot: bool, last_ch: u8, prev_last_ch: u8, } pub struct BdatReceiver { - pub buf: Vec, + 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 Default for Receiver { 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) -> 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) -> 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 { - for &ch in bytes { - self.buf.push(ch); - if self.buf.len() == self.bytes_left { - return true; + if !self.is_bdat { + 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' => { + return true; + } + b'\r' => {} + _ => { + self.crlf_dot = false; + } + } + self.prev_last_ch = self.last_ch; + self.last_ch = ch; } - } - false + 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; } } diff --git a/src/response/generate.rs b/src/response/generate.rs index bdfc82f..8eb6053 100644 --- a/src/response/generate.rs +++ b/src/response/generate.rs @@ -6,6 +6,13 @@ use std::{ use crate::*; impl EhloResponse { + 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 EhloResponse { MtPriority::Mixer => "MIXER", MtPriority::Stanag4406 => "STANAG4406", MtPriority::Nsep => "NSEP", - MtPriority::None => "MIXER", } ), Capability::Mtrk => write!(writer, "MTRK\r\n"), diff --git a/src/response/parser.rs b/src/response/parser.rs index ab861ad..952e38e 100644 --- a/src/response/parser.rs +++ b/src/response/parser.rs @@ -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 { } } -impl Capability { - pub fn parse(value: &[u8]) -> Option { - 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::{