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,
}
#[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 },

View file

@ -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 {

View file

@ -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;
}
}

View file

@ -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"),

View file

@ -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::{