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}