API updates.

This commit is contained in:
Mauro D 2022-12-18 15:32:42 +00:00
parent 5765b4b9fe
commit 97092b606c
5 changed files with 105 additions and 102 deletions

View file

@ -209,12 +209,11 @@ pub enum Capability {
Verb, Verb,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MtPriority { pub enum MtPriority {
Mixer, Mixer,
Stanag4406, Stanag4406,
Nsep, Nsep,
None,
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -254,7 +253,8 @@ pub enum Category {
pub enum Error { pub enum Error {
NeedsMoreData { bytes_left: usize }, NeedsMoreData { bytes_left: usize },
UnknownCommand, UnknownCommand,
InvalidAddress, InvalidSenderAddress,
InvalidRecipientAddress,
SyntaxError { syntax: &'static str }, SyntaxError { syntax: &'static str },
InvalidParameter { param: &'static str }, InvalidParameter { param: &'static str },
UnsupportedParameter { param: String }, UnsupportedParameter { param: String },

View file

@ -28,7 +28,7 @@ impl ReceiverParser for Request<String> {
} }
} else { } else {
parser.seek_lf()?; parser.seek_lf()?;
return Err(Error::InvalidAddress); return Err(Error::InvalidRecipientAddress);
} }
} }
parser.seek_lf()?; parser.seek_lf()?;
@ -51,7 +51,7 @@ impl ReceiverParser for Request<String> {
} }
} else { } else {
parser.seek_lf()?; parser.seek_lf()?;
return Err(Error::InvalidAddress); return Err(Error::InvalidSenderAddress);
} }
} }
@ -1479,26 +1479,29 @@ mod tests {
parameters: vec![], parameters: vec![],
}), }),
), ),
("MAIL FROM:<@invalid>", Err(Error::InvalidAddress)), ("MAIL FROM:<@invalid>", Err(Error::InvalidSenderAddress)),
("MAIL FROM:<hi@@invalid.org>", Err(Error::InvalidAddress)), (
"MAIL FROM:<hi@@invalid.org>",
Err(Error::InvalidSenderAddress),
),
( (
"MAIL FROM:<hi..there@invalid.org>", "MAIL FROM:<hi..there@invalid.org>",
Err(Error::InvalidAddress), Err(Error::InvalidSenderAddress),
), ),
( (
"MAIL FROM:<hi.there@invalid..org>", "MAIL FROM:<hi.there@invalid..org>",
Err(Error::InvalidAddress), Err(Error::InvalidSenderAddress),
), ),
( (
"MAIL FROM:<hi.there@.invalid.org>", "MAIL FROM:<hi.there@.invalid.org>",
Err(Error::InvalidAddress), Err(Error::InvalidSenderAddress),
), ),
( (
"MAIL FROM:<.hi.there@invalid.org>", "MAIL FROM:<.hi.there@invalid.org>",
Err(Error::InvalidAddress), Err(Error::InvalidSenderAddress),
), ),
("MAIL FROM:<@>", Err(Error::InvalidAddress)), ("MAIL FROM:<@>", Err(Error::InvalidSenderAddress)),
("MAIL FROM:<.@.>", Err(Error::InvalidAddress)), ("MAIL FROM:<.@.>", Err(Error::InvalidSenderAddress)),
( (
"RCPT TO:<孫子@áéíóú.org>", "RCPT TO:<孫子@áéíóú.org>",
Ok(Request::Rcpt { Ok(Request::Rcpt {

View file

@ -12,17 +12,24 @@ pub trait ReceiverParser: Sized {
} }
pub struct DataReceiver { pub struct DataReceiver {
pub buf: Vec<u8>,
crlf_dot: bool, crlf_dot: bool,
last_ch: u8, last_ch: u8,
prev_last_ch: u8, prev_last_ch: u8,
} }
pub struct BdatReceiver { pub struct BdatReceiver {
pub buf: Vec<u8>, pub is_last: bool,
bytes_left: usize, 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> { impl<T: ReceiverParser> Default for Receiver<T> {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -60,28 +67,27 @@ impl DataReceiver {
#[allow(clippy::new_without_default)] #[allow(clippy::new_without_default)]
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
buf: Vec::with_capacity(1024),
crlf_dot: false, crlf_dot: false,
last_ch: 0, last_ch: 0,
prev_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 { for &ch in bytes {
match ch { match ch {
b'.' if self.last_ch == b'\n' && self.prev_last_ch == b'\r' => { b'.' if self.last_ch == b'\n' && self.prev_last_ch == b'\r' => {
self.crlf_dot = true; self.crlf_dot = true;
} }
b'\n' if self.crlf_dot && self.last_ch == b'\r' => { 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; return true;
} }
b'\r' => { b'\r' => {
self.buf.push(ch); buf.push(ch);
} }
_ => { _ => {
self.buf.push(ch); buf.push(ch);
self.crlf_dot = false; self.crlf_dot = false;
} }
} }
@ -94,22 +100,78 @@ impl DataReceiver {
} }
impl BdatReceiver { impl BdatReceiver {
pub fn new(bytes_left: usize) -> Self { pub fn new(chunk_size: usize, is_last: bool) -> Self {
Self { Self {
buf: Vec::with_capacity(bytes_left), bytes_left: chunk_size,
bytes_left, 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 { pub fn ingest(&mut self, bytes: &mut Iter<'_, u8>) -> bool {
for &ch in bytes { if !self.is_bdat {
self.buf.push(ch); for &ch in bytes {
if self.buf.len() == self.bytes_left { match ch {
return true; 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 r = DataReceiver::new();
let mut buf = Vec::new();
for data in &data { for data in &data {
if r.ingest(&mut data.as_bytes().iter()) { if r.ingest(&mut data.as_bytes().iter(), &mut buf) {
assert_eq!(message, String::from_utf8(r.buf).unwrap()); assert_eq!(message, String::from_utf8(buf).unwrap());
continue 'outer; continue 'outer;
} }
} }

View file

@ -6,6 +6,13 @@ use std::{
use crate::*; use crate::*;
impl<T: Display> EhloResponse<T> { 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<()> { pub fn write(&self, mut writer: impl Write) -> io::Result<()> {
write!(writer, "250-{} says hello\r\n", self.hostname)?; write!(writer, "250-{} says hello\r\n", self.hostname)?;
let len = self.capabilities.len(); let len = self.capabilities.len();
@ -58,7 +65,6 @@ impl<T: Display> EhloResponse<T> {
MtPriority::Mixer => "MIXER", MtPriority::Mixer => "MIXER",
MtPriority::Stanag4406 => "STANAG4406", MtPriority::Stanag4406 => "STANAG4406",
MtPriority::Nsep => "NSEP", MtPriority::Nsep => "NSEP",
MtPriority::None => "MIXER",
} }
), ),
Capability::Mtrk => write!(writer, "MTRK\r\n"), Capability::Mtrk => write!(writer, "MTRK\r\n"),

View file

@ -2,7 +2,7 @@ use std::slice::Iter;
use crate::{ use crate::{
request::{parser::Rfc5321Parser, receiver::ReceiverParser}, request::{parser::Rfc5321Parser, receiver::ReceiverParser},
Capability, EhloResponse, Error, IntoString, MtPriority, Response, LF, *,
}; };
use super::*; 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)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{