rust-smtp-server 0.1.1

A rust smtp server library.
Documentation
use std::{pin::Pin, future::Future, task::Poll};

use tokio::io::{self, AsyncBufRead, AsyncRead, AsyncReadExt};

use crate::{backend::{Backend}};

pub type EnhancedCode = [i8; 3];

pub struct SMTPError {
    code: u16,
    enhanced_code: EnhancedCode,
    message: String,
}

impl std::fmt::Display for SMTPError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{} {}", self.code, self.message)
    }
}

pub const NO_ENHANCED_CODE: EnhancedCode = [-1, -1, -1];

pub const ENHANCED_CODE_NOT_SET: EnhancedCode = [0, 0, 0];

#[derive(PartialEq)]
enum State {
    BeginLine,
    Dot,
    DotCR,
    CR,
    Data,
    EOF,
}


impl SMTPError {
    pub fn err_data_too_large() -> Self {
        return SMTPError {
            code: 552,
            enhanced_code: ENHANCED_CODE_NOT_SET,
            message: "Requested mail action aborted: exceeded storage allocation".to_string(),
        };
    }

    pub fn err_auth_required() -> Self {
        return SMTPError {
            code: 502,
            enhanced_code: [5, 7, 0],
            message: "Please authenticate first".to_string(),
        };
    }

    pub fn err_auth_unsupported() -> Self {
        return SMTPError {
            code: 502,
            enhanced_code: [5, 7, 0],
            message: "Authentication not supported".to_string(),
        };
    }

    pub fn error(&self) -> String {
        self.message.clone()
    }

    fn is_temporary(&self) -> bool {
        self.code >= 400 && self.code < 500
    }
}

pub struct DataReader<R: AsyncRead + Unpin> {
    pub r: R,
    state: State,
    pub limited: bool,
    n: usize,
}

impl<R: AsyncBufRead + Unpin> DataReader<R> {
    pub fn new<B: Backend>(r: R, max_message_bytes: usize) -> Self {
        DataReader {
            r,
            state: State::BeginLine,
            limited: max_message_bytes > 0,
            n: max_message_bytes,
        }
    }
}

impl<R: AsyncBufRead + Unpin> AsyncRead for DataReader<R> {
    fn poll_read(
        self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
        buf: &mut tokio::io::ReadBuf<'_>,
    ) -> std::task::Poll<std::io::Result<()>> {
        let mut this = self.get_mut();

        if this.n == 0 || this.state == State::EOF {
            return Poll::Ready(Ok(()));
        }

        let mut fut = Box::pin(this.r.read_u8());
        match Pin::new(&mut fut).poll(cx) {
            Poll::Ready(Ok(c)) => {
                match this.state {
                    State::BeginLine => {
                        if c == b'.' {
                            this.state = State::Dot;
                            cx.waker().wake_by_ref();
                            return Poll::Pending;
                        }
                        this.state = State::Data;
                    }
                    State::Dot => {
                        if c == b'\r' {
                            this.state = State::DotCR;
                            cx.waker().wake_by_ref();
                            return Poll::Pending;
                        }
                        if c == b'\n' {
                            this.state = State::EOF;
                            return Poll::Ready(Ok(()));
                        }
    
                        this.state = State::Data;
                    }
                    State::DotCR => {
                        if c == b'\n' {
                            this.state = State::EOF;
                            return Poll::Ready(Ok(()));
                        }
                        this.state = State::Data;
                    }
                    State::CR => {
                        if c == b'\n' {
                            this.state = State::BeginLine;
                            return Poll::Ready(Ok(()));
                        }
                        this.state = State::Data;
                    }
                    State::Data => {
                        if c == b'\r' {
                            this.state = State::CR;
                        }
                        if c == b'\n' {
                            this.state = State::BeginLine;
                        }
                    }
                    State::EOF => {
                        return Poll::Ready(Ok(()));
                    }
                }

                this.n -= 1;
                buf.put_slice(&[c]);
                return Poll::Ready(Ok(()));
            }
            Poll::Ready(Err(e)) => {
                if e.kind() == io::ErrorKind::UnexpectedEof {
                    return Poll::Ready(Ok(()));
                }
                return Poll::Ready(Err(e));
            }
            Poll::Pending => {
                return Poll::Pending;
            }
        }

    }
}