new_tokio_smtp/
response.rs

1//! Provides access to `Response`, `ResponseCode` and parsing parts (form impl `Cmd`'s)
2/// response of a smtp server
3#[derive(Debug, Clone, Eq, PartialEq, Hash)]
4pub struct Response {
5    code: ResponseCode,
6    lines: Vec<String>,
7}
8
9impl Response {
10    /// crate a new Response from a response code and a number of lines
11    ///
12    /// If lines is empty a single empty line will be pushed to the
13    /// lines `Vec`.
14    pub fn new(code: ResponseCode, mut lines: Vec<String>) -> Self {
15        if lines.is_empty() {
16            lines.push(String::new());
17        }
18        Response { code, lines }
19    }
20
21    /// true if the response code is unknown or indicates an error
22    pub fn is_erroneous(&self) -> bool {
23        self.code.is_erroneous()
24    }
25
26    /// return the response code
27    pub fn code(&self) -> ResponseCode {
28        self.code
29    }
30
31    /// returns the lines of the msg/payload
32    ///
33    /// this will have at last one line, throuhg
34    /// this line might be empty
35    pub fn msg(&self) -> &[String] {
36        &self.lines
37    }
38}
39
40/// The response code of used by smtp server.
41//FIXME impl Display
42//FIXME impl Debug which shows it as byte string, i.e. human readable
43#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
44pub struct ResponseCode([u8; 3]);
45
46impl ResponseCode {
47    /// true if the code starts with `2`
48    pub fn is_positive(self) -> bool {
49        self.0[0] == b'2'
50    }
51
52    /// true if the code starts with `3`
53    pub fn is_intermediate(self) -> bool {
54        self.0[0] == b'3'
55    }
56
57    /// true if the code starts with `4`
58    pub fn is_transient_failure(self) -> bool {
59        self.0[0] == b'4'
60    }
61
62    /// true if the code starts with `5`
63    pub fn is_permanent_failure(self) -> bool {
64        self.0[0] == b'5'
65    }
66
67    /// true if the code doesn't start with `2` or `3`
68    pub fn is_erroneous(self) -> bool {
69        !self.is_positive() && !self.is_intermediate()
70    }
71
72    /// The actual bytes returned as response code.
73    ///
74    /// This could be for example `*b'250'`. I.e. it's
75    /// in ascii characters. It's *not* a triplet of the
76    /// ascii characters converted to integer numbers!
77    //FIXME rename to as_ascii_bytes
78    pub fn as_byte_string(self) -> [u8; 3] {
79        self.0
80    }
81}
82
83pub mod parser {
84    use super::{Response, ResponseCode};
85
86    use std::error::Error;
87    use std::fmt::{self, Display};
88    use std::str::{self, Utf8Error};
89
90    #[derive(Debug, Clone)]
91    pub enum ParseError {
92        LineLength,
93        CodeMsgSeparator,
94        Utf8(Utf8Error),
95        CodeFormat {
96            kind: u8,
97            category: u8,
98            detail: u8,
99        },
100        Code {
101            expected: ResponseCode,
102            got: ResponseCode,
103        },
104    }
105
106    impl Display for ParseError {
107        fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
108            write!(fter, "{:?}", self)
109        }
110    }
111
112    impl Error for ParseError {}
113
114    pub struct ResponseLine {
115        pub code: ResponseCode,
116        pub last_line: bool,
117        pub msg: String,
118    }
119
120    pub fn parse_line(line: &[u8]) -> Result<ResponseLine, ParseError> {
121        if line.len() < 4 {
122            return Err(ParseError::LineLength);
123        }
124        let (code, tail) = line.split_at(3);
125        let (sep, msg) = tail.split_at(1);
126
127        let code = parse_code(code[0], code[1], code[2])?;
128        let last_line = parse_separator(sep[0])?;
129        let msg = parse_msg(msg)?.to_owned();
130
131        Ok(ResponseLine {
132            code,
133            last_line,
134            msg,
135        })
136    }
137
138    /// A non-struct response code parser, as long as the code is made of digits it accepts it
139    ///
140    /// The RFC 5321 grammar is actually a bit more strict, only
141    /// allowing `b'2' ..= b'5'` for the kind, `b'0' ..= b'5'` for category and
142    /// `b'0' ..= b'9'` for the detail part.
143    ///
144    /// The reason why this parser is less strict is following:
145    ///
146    /// 1. there are other RFC's extending SMTP some could (but probably shouldn't)
147    ///    extend the error code
148    /// 2. most use cases either check the kind or the whole error code, so having
149    ///    other categories shouldn't be a problem _and_ wrt. the kind other kinds
150    ///    can just be collapsed into `is_erroneous`
151    /// 3. In the end it allows the command impl. to handle "unexpected" response
152    ///    codes, while making it hardly any harder/more complex to handle the
153    ///    response code for existing commands
154    ///
155    pub fn parse_code(kind: u8, category: u8, detail: u8) -> Result<ResponseCode, ParseError> {
156        // there is hardly any reason to be super struct on the response code
157        // so aslong as it's a number it's fine
158        if kind.is_ascii_digit() && category.is_ascii_digit() && detail.is_ascii_digit() {
159            //FIXME kind-b'0', category-b'0', detail-b'0'
160            Ok(ResponseCode([kind, category, detail]))
161        } else {
162            Err(ParseError::CodeFormat {
163                kind,
164                category,
165                detail,
166            })
167        }
168    }
169
170    fn parse_separator(sep: u8) -> Result<bool, ParseError> {
171        let last_line = match sep {
172            b' ' => true,
173            b'-' => false,
174            _ => return Err(ParseError::CodeMsgSeparator),
175        };
176        Ok(last_line)
177    }
178
179    fn parse_msg(msg: &[u8]) -> Result<&str, ParseError> {
180        str::from_utf8(msg).map_err(ParseError::Utf8)
181    }
182
183    ///
184    /// Ignores the `last_line` field in the iterator, the called is required to
185    /// check if the last line (and no previous line) has the field set to `true`.
186    ///
187    /// # Panics
188    ///
189    /// Panics if the lines iterator does not return at last one line.
190    ///
191    pub fn response_from_parsed_lines<I>(lines: I) -> Result<Response, ParseError>
192    where
193        I: IntoIterator<Item = ResponseLine>,
194    {
195        let mut iter = lines.into_iter();
196        let first = iter.next().expect("called with zero lines");
197        let code = first.code;
198        let mut messages = vec![first.msg];
199
200        for line in iter {
201            if code != line.code {
202                return Err(ParseError::Code {
203                    expected: code,
204                    got: line.code,
205                });
206            }
207
208            messages.push(line.msg);
209        }
210
211        Ok(Response {
212            code,
213            lines: messages,
214        })
215    }
216}
217
218/// Predefined Codes based on RFC 5321
219///
220/// All command documentation starting with "RFC XXX:" is directly quoted from the rfx XXX,
221/// command documentation starting with "RFC XXX;" is not quoted.
222/// In case of "RFC 5321:" the quotes come from Section 4.2.3.
223pub mod codes {
224    use super::ResponseCode;
225
226    /// RFC 5321: System status, or system help reply
227    pub static STATUS_RESPONSE: ResponseCode = ResponseCode(*b"211");
228
229    /// RFC 5321: Help message
230    /// (Information on how to use the receiver or the meaning of a
231    /// particular non-standard command; this reply is useful
232    /// only to the human user)
233    pub static HELP_RESPONSE: ResponseCode = ResponseCode(*b"214");
234
235    /// RFC 5321: <domain> Service ready
236    pub static READY: ResponseCode = ResponseCode(*b"220");
237
238    /// RFC 5321: <domain> Service closing transmission channel
239    pub static CLOSING_CHANNEL: ResponseCode = ResponseCode(*b"221");
240
241    /// RFC 5321: Requested mail action okay, completed
242    pub static OK: ResponseCode = ResponseCode(*b"250");
243
244    /// RFC 5321: User not local; will forward to <forward-path> (See Section 3.4)
245    pub static OK_NOT_LOCAL: ResponseCode = ResponseCode(*b"251");
246
247    /// RFC 5321: Cannot VRFY user, but will accept message and attempt delivery
248    /// (See Section 3.5.3)
249    pub static OK_UNVERIFIED: ResponseCode = ResponseCode(*b"252");
250
251    /// RFC 5321: Start mail input; end with <CRLF>.<CRLF>
252    pub static START_MAIL_DATA: ResponseCode = ResponseCode(*b"354");
253
254    /// RFC 5321: <domain> Service not available, closing transmission channel
255    /// (This may be a reply to any command if the service knows it must
256    /// shut down)
257    pub static SERVICE_UNAVAILABLE: ResponseCode = ResponseCode(*b"421");
258
259    /// RFC 5321: Requested mail action not taken: mailbox unavailable (e.g.,
260    /// mailbox busy or temporarily blocked for policy reasons)
261    pub static MAILBOX_TEMP_UNAVAILABLE: ResponseCode = ResponseCode(*b"450");
262
263    /// RFC 5321: Requested action aborted: local error in processing
264    pub static LOCAL_ERROR: ResponseCode = ResponseCode(*b"451");
265
266    /// RFC 5321: Requested action not taken: insufficient system storage
267    pub static INSUFFICIENT_SYSTEM: ResponseCode = ResponseCode(*b"452");
268
269    /// RFC 5321: Server unable to accommodate parameters
270    pub static UNABLE_TO_ACCOMMODATE_PARAMETERS: ResponseCode = ResponseCode(*b"455");
271
272    /// RFC 5321: Syntax error, command unrecognized (This may include errors such
273    /// as command line too long)
274    pub static SYNTAX_ERROR: ResponseCode = ResponseCode(*b"500");
275
276    /// RFC 5321: Syntax error in parameters or arguments
277    pub static PARAM_SYNTAX_ERROR: ResponseCode = ResponseCode(*b"501");
278
279    /// RFC 5321: Command not implemented (see Section 4.2.4)
280    pub static COMMAND_UNIMPLEMENTED: ResponseCode = ResponseCode(*b"502");
281
282    /// RFC 5321: Bad sequence of commands
283    pub static BAD_COMMAND_SEQUENCE: ResponseCode = ResponseCode(*b"503");
284
285    /// RFC 5321: Command parameter not implemented
286    pub static PARAMETER_NOT_IMPLEMENTED: ResponseCode = ResponseCode(*b"504");
287
288    /// RFC 7504: Server does not accept mail
289    pub static SERVER_DOES_NOT_ACCEPT_MAIL: ResponseCode = ResponseCode(*b"521");
290
291    /// RFC 5321: Requested action not taken: mailbox unavailable (e.g., mailbox
292    /// not found, no access, or command rejected for policy reasons)
293    pub static MAILBOX_UNAVAILABLE: ResponseCode = ResponseCode(*b"550");
294
295    /// RFC 5321: User not local; please try <forward-path> (See Section 3.4)
296    pub static USER_NOT_LOCAL: ResponseCode = ResponseCode(*b"551");
297
298    /// RFC 5321: Requested mail action aborted: exceeded storage allocation
299    pub static EXCEEDED_STORAGE_ALLOCATION: ResponseCode = ResponseCode(*b"552");
300
301    /// RFC 5321: Requested action not taken: mailbox name not allowed (e.g.,
302    /// mailbox syntax incorrect)
303    pub static BAD_MAILBOX_NAME: ResponseCode = ResponseCode(*b"553");
304
305    /// RFC 5321: Transaction failed (Or, in the case of a connection-opening
306    /// response, "No SMTP service here")
307    pub static TRANSACTION_FAILED: ResponseCode = ResponseCode(*b"554");
308
309    /// RFC 5321: MAIL FROM/RCPT TO parameters not recognized or not implemented
310    pub static PARAM_NOT_RECOGNIZED: ResponseCode = ResponseCode(*b"555");
311
312    /// RFC 7504; like 521 but returned when a intermediate gateway knows a server
313    ///  will return 521 when connecting, and therefore does decide not to connect
314    ///  with it at all
315    pub static TARGET_DOES_NOT_ACCEPT_MAIL: ResponseCode = ResponseCode(*b"556");
316}