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,
|
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 },
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
if !self.is_bdat {
|
||||||
for &ch in bytes {
|
for &ch in bytes {
|
||||||
self.buf.push(ch);
|
match ch {
|
||||||
if self.buf.len() == self.bytes_left {
|
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;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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::{
|
||||||
|
|
Loading…
Reference in a new issue