1use std::slice::Iter;
8
9use crate::{request::parser::Rfc5321Parser, *};
10
11use super::*;
12
13pub const MAX_RESPONSE_LENGTH: usize = 4096;
14
15#[derive(Default)]
16pub struct ResponseReceiver {
17 buf: Vec<u8>,
18 code: u16,
19 esc: [u8; 3],
20 esc_cur: [u8; 3],
21 esc_restore: [u8; 10],
22 esc_pos: usize,
23 is_last: bool,
24 pos: usize,
25}
26
27impl ResponseReceiver {
28 pub fn from_code(code: u16) -> Self {
29 Self {
30 code,
31 esc: [0u8; 3],
32 esc_cur: [0u8; 3],
33 esc_restore: [0u8; 10],
34 esc_pos: 0,
35 pos: 3,
36 is_last: false,
37 buf: Vec::new(),
38 }
39 }
40
41 pub fn parse(&mut self, bytes: &mut Iter<'_, u8>) -> Result<Response<String>, Error> {
42 for &ch in bytes {
43 match self.pos {
44 0..=2 => {
45 if ch.is_ascii_digit() {
46 if self.buf.is_empty() {
47 self.code = self
48 .code
49 .saturating_mul(10)
50 .saturating_add((ch - b'0') as u16);
51 }
52 self.pos += 1;
53 } else {
54 return Err(Error::SyntaxError {
55 syntax: "Invalid response code",
56 });
57 }
58 }
59 3 => match ch {
60 b' ' => {
61 self.is_last = true;
62 self.pos += 1;
63 }
64 b'-' => {
65 self.pos += 1;
66 }
67 b'\r' => {
68 continue;
69 }
70 b'\n' => {
71 self.is_last = true;
72 }
73 _ => {
74 return Err(Error::SyntaxError {
75 syntax: "Invalid response separator",
76 });
77 }
78 },
79 4..=6 => {
80 let mut do_restore = false;
81
82 match ch {
83 b'0'..=b'9' => {
84 let code = &mut self.esc_cur[self.pos - 4];
85 if *code < 100 {
86 if let Some(restore) = self.esc_restore.get_mut(self.esc_pos) {
87 *restore = ch;
88 *code = code.saturating_mul(10).saturating_add(ch - b'0');
89 self.esc_pos += 1;
90 } else {
91 do_restore = true;
92 }
93 } else {
94 do_restore = true;
95 }
96 }
97 b'.' if self.pos < 6 => {
98 if let Some(restore) = self.esc_restore.get_mut(self.esc_pos) {
99 *restore = ch;
100 self.pos += 1;
101 self.esc_pos += 1;
102 } else {
103 do_restore = true;
104 }
105 }
106 b' ' | b'\r' | b'\n' if self.pos == 6 => {
107 self.esc = self.esc_cur;
108 self.esc_cur.fill(0);
109 self.pos = 7;
110 self.esc_pos = 0;
111 }
112 _ => {
113 do_restore = true;
114 }
115 }
116 if do_restore {
117 if self.esc_pos > 0 {
119 self.buf
120 .extend_from_slice(&self.esc_restore[..self.esc_pos]);
121 }
122 self.buf.push(ch);
123 self.pos = 7;
124 self.esc_pos = 0;
125 self.esc_cur.fill(0);
126 }
127 }
128 _ => match ch {
129 b'\r' | b'\n' => (),
130 _ => {
131 if self.buf.len() < MAX_RESPONSE_LENGTH {
132 self.buf.push(ch);
133 } else {
134 return Err(Error::ResponseTooLong);
135 }
136 }
137 },
138 }
139
140 if ch == b'\n' {
141 if self.is_last {
142 return Ok(Response {
143 code: self.code,
144 esc: self.esc,
145 message: std::mem::take(&mut self.buf).into_string(),
146 });
147 } else {
148 self.buf.push(b'\n');
149 self.pos = 0;
150 }
151 }
152 }
153
154 Err(Error::NeedsMoreData { bytes_left: 0 })
155 }
156
157 pub fn reset(&mut self) {
158 self.is_last = false;
159 self.code = 0;
160 self.esc.fill(0);
161 self.esc_cur.fill(0);
162 self.pos = 0;
163 self.esc_pos = 0;
164 self.buf.clear();
165 }
166}
167
168impl EhloResponse<String> {
169 pub fn parse(bytes: &mut Iter<'_, u8>) -> Result<Self, Error> {
170 let mut parser = Rfc5321Parser::new(bytes);
171 let mut response = EhloResponse::default();
172 let mut eol = false;
173 let mut is_first_line = true;
174
175 while !eol {
176 let mut code: u16 = 0;
177 for _ in 0..3 {
178 match parser.read_char()? {
179 ch @ b'0'..=b'9' => {
180 code = code.saturating_mul(10).saturating_add((ch - b'0') as u16);
181 }
182 _ => {
183 return Err(Error::SyntaxError {
184 syntax: "unexpected token",
185 });
186 }
187 }
188 }
189
190 if code != 250 {
191 return Err(Error::InvalidResponse { code });
192 }
193
194 match parser.read_char()? {
195 b' ' => {
196 eol = true;
197 }
198 b'-' => (),
199 b'\n' if code < 600 => {
200 break;
201 }
202 _ => {
203 return Err(Error::SyntaxError {
204 syntax: "unexpected token",
205 });
206 }
207 }
208
209 if !is_first_line {
210 response.capabilities |= match parser.hashed_value_long()? {
211 _8BITMIME => EXT_8BIT_MIME,
212 ATRN => EXT_ATRN,
213 AUTH => {
214 while parser.stop_char != LF {
215 if let Some(mechanism) = parser.mechanism()? {
216 response.auth_mechanisms |= mechanism;
217 }
218 }
219
220 EXT_AUTH
221 }
222 BINARYMIME => EXT_BINARY_MIME,
223 BURL => EXT_BURL,
224 CHECKPOINT => EXT_CHECKPOINT,
225 CHUNKING => EXT_CHUNKING,
226 CONNEG => EXT_CONNEG,
227 CONPERM => EXT_CONPERM,
228 DELIVERBY => {
229 response.deliver_by = if parser.stop_char != LF {
230 let db = parser.size()?;
231 if db != usize::MAX { db as u64 } else { 0 }
232 } else {
233 0
234 };
235 EXT_DELIVER_BY
236 }
237 DSN => EXT_DSN,
238 ENHANCEDSTATUSCO
239 if parser.stop_char.eq_ignore_ascii_case(&b'D')
240 && parser.read_char()?.eq_ignore_ascii_case(&b'E')
241 && parser.read_char()?.eq_ignore_ascii_case(&b'S') =>
242 {
243 EXT_ENHANCED_STATUS_CODES
244 }
245 ETRN => EXT_ETRN,
246 EXPN => EXT_EXPN,
247 VRFY => EXT_VRFY,
248 FUTURERELEASE => {
249 let max_interval = if parser.stop_char != LF {
250 parser.size()?
251 } else {
252 0
253 };
254 let max_datetime = if parser.stop_char != LF {
255 parser.size()?
256 } else {
257 0
258 };
259
260 response.future_release_interval = if max_interval != usize::MAX {
261 max_interval as u64
262 } else {
263 0
264 };
265 response.future_release_datetime = if max_datetime != usize::MAX {
266 max_datetime as u64
267 } else {
268 0
269 };
270 EXT_FUTURE_RELEASE
271 }
272 HELP => EXT_HELP,
273 MT_PRIORITY => {
274 response.mt_priority = if parser.stop_char != LF {
275 match parser.hashed_value_long()? {
276 MIXER => MtPriority::Mixer,
277 STANAG4406 => MtPriority::Stanag4406,
278 NSEP => MtPriority::Nsep,
279 _ => MtPriority::Mixer,
280 }
281 } else {
282 MtPriority::Mixer
283 };
284 EXT_MT_PRIORITY
285 }
286 MTRK => EXT_MTRK,
287 NO_SOLICITING => {
288 response.no_soliciting = if parser.stop_char != LF {
289 let text = parser.text()?;
290 if !text.is_empty() {
291 text.into_owned().into()
292 } else {
293 None
294 }
295 } else {
296 None
297 };
298 EXT_NO_SOLICITING
299 }
300 ONEX => EXT_ONEX,
301 PIPELINING => EXT_PIPELINING,
302 REQUIRETLS => EXT_REQUIRE_TLS,
303 RRVS => EXT_RRVS,
304 SIZE => {
305 response.size = if parser.stop_char != LF {
306 let size = parser.size()?;
307 if size != usize::MAX { size } else { 0 }
308 } else {
309 0
310 };
311 EXT_SIZE
312 }
313 SMTPUTF8 => EXT_SMTP_UTF8,
314 STARTTLS => EXT_START_TLS,
315 VERB => EXT_VERB,
316 _ => 0,
317 };
318 parser.seek_lf()?;
319 } else {
320 let mut buf = Vec::with_capacity(16);
321 loop {
322 match parser.read_char()? {
323 b'\n' => break,
324 b'\r' => (),
325 b' ' => {
326 parser.seek_lf()?;
327 break;
328 }
329 ch if buf.len() < MAX_RESPONSE_LENGTH => {
330 buf.push(ch);
331 }
332 _ => return Err(Error::ResponseTooLong),
333 }
334 }
335 is_first_line = false;
336 response.hostname = buf.into_string();
337 }
338 }
339
340 Ok(response)
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use crate::*;
347
348 use super::ResponseReceiver;
349
350 #[test]
351 fn parse_ehlo() {
352 for item in [
353 (
354 concat!(
355 "250-dbc.mtview.ca.us says hello\n",
356 "250-8BITMIME\n",
357 "250-ATRN\n",
358 "250-AUTH GSSAPI DIGEST-MD5 PLAIN\n",
359 "250-BINARYMIME\n",
360 "250-BURL imap\n",
361 "250-CHECKPOINT\n",
362 "250-CHUNKING\n",
363 "250-CONNEG\n",
364 "250-CONPERM\n",
365 "250-DELIVERBY\n",
366 "250-DSN\n",
367 "250-ENHANCEDSTATUSCODES\n",
368 "250-ETRN\n",
369 "250-EXPN\n",
370 "250-VRFY\n",
371 "250-FUTURERELEASE 1234 5678\n",
372 "250-HELP\n",
373 "250-MT-PRIORITY\n",
374 "250-MTRK\n",
375 "250-NO-SOLICITING net.example:ADV\n",
376 "250-PIPELINING\n",
377 "250-REQUIRETLS\n",
378 "250-RRVS\n",
379 "250-SIZE 1000000\n",
380 "250-SMTPUTF8 ignore\n",
381 "250 STARTTLS\n",
382 ),
383 Ok(EhloResponse {
384 hostname: "dbc.mtview.ca.us".to_string(),
385 capabilities: EXT_8BIT_MIME
386 | EXT_ATRN
387 | EXT_AUTH
388 | EXT_BINARY_MIME
389 | EXT_BURL
390 | EXT_CHECKPOINT
391 | EXT_CHUNKING
392 | EXT_CONNEG
393 | EXT_CONPERM
394 | EXT_DELIVER_BY
395 | EXT_DSN
396 | EXT_ENHANCED_STATUS_CODES
397 | EXT_ETRN
398 | EXT_EXPN
399 | EXT_VRFY
400 | EXT_FUTURE_RELEASE
401 | EXT_HELP
402 | EXT_MT_PRIORITY
403 | EXT_MTRK
404 | EXT_NO_SOLICITING
405 | EXT_PIPELINING
406 | EXT_REQUIRE_TLS
407 | EXT_RRVS
408 | EXT_SIZE
409 | EXT_SMTP_UTF8
410 | EXT_START_TLS,
411 auth_mechanisms: AUTH_GSSAPI | AUTH_DIGEST_MD5 | AUTH_PLAIN,
412 deliver_by: 0,
413 future_release_interval: 1234,
414 future_release_datetime: 5678,
415 mt_priority: MtPriority::Mixer,
416 no_soliciting: Some("net.example:ADV".to_string()),
417 size: 1000000,
418 }),
419 ),
420 (
421 concat!(
422 "250-\n",
423 "250-DELIVERBY 240\n",
424 "250-FUTURERELEASE 123\n",
425 "250-MT-PRIORITY MIXER\n",
426 "250-NO-SOLICITING\n",
427 "250-SIZE\n",
428 "250 SMTPUTF8\n",
429 ),
430 Ok(EhloResponse {
431 hostname: "".to_string(),
432 capabilities: EXT_DELIVER_BY
433 | EXT_FUTURE_RELEASE
434 | EXT_MT_PRIORITY
435 | EXT_NO_SOLICITING
436 | EXT_SIZE
437 | EXT_SMTP_UTF8,
438 auth_mechanisms: 0,
439 deliver_by: 240,
440 future_release_interval: 123,
441 future_release_datetime: 0,
442 mt_priority: MtPriority::Mixer,
443 no_soliciting: None,
444 size: 0,
445 }),
446 ),
447 (
448 concat!(
449 "250-dbc.mtview.ca.us says hello\n",
450 "250-FUTURERELEASE\n",
451 "250 MT-PRIORITY STANAG4406\n",
452 ),
453 Ok(EhloResponse {
454 hostname: "dbc.mtview.ca.us".to_string(),
455 capabilities: EXT_FUTURE_RELEASE | EXT_MT_PRIORITY,
456 auth_mechanisms: 0,
457 deliver_by: 0,
458 future_release_interval: 0,
459 future_release_datetime: 0,
460 mt_priority: MtPriority::Stanag4406,
461 no_soliciting: None,
462 size: 0,
463 }),
464 ),
465 (
466 concat!("523-Massive\n", "523-Error\n", "523 Message\n"),
467 Err(Error::InvalidResponse { code: 523 }),
468 ),
469 ] {
470 let (response, parsed_response): (&str, Result<EhloResponse<String>, Error>) = item;
471
472 for replacement in ["", "\r\n", " \n", " \r\n"] {
473 let response = if !replacement.is_empty() && parsed_response.is_ok() {
474 response.replace('\n', replacement)
475 } else {
476 response.to_string()
477 };
478 assert_eq!(
479 parsed_response,
480 EhloResponse::parse(&mut response.as_bytes().iter()),
481 "failed for {response:?}",
482 );
483 }
484 }
485 }
486
487 #[test]
488 fn parse_response() {
489 let mut all_responses = Vec::new();
490 let mut all_parsed_responses = Vec::new();
491
492 for (response, parsed_response, _) in [
493 (
494 "250 2.1.1 Originator <ned@ymir.claremont.edu> ok\n",
495 Response {
496 code: 250,
497 esc: [2, 1, 1],
498 message: "Originator <ned@ymir.claremont.edu> ok".to_string(),
499 },
500 true,
501 ),
502 (
503 concat!(
504 "551-5.7.1 Forwarding to remote hosts disabled\n",
505 "551 5.7.1 Select another host to act as your forwarder\n"
506 ),
507 Response {
508 code: 551,
509 esc: [5, 7, 1],
510 message: concat!(
511 "Forwarding to remote hosts disabled\n",
512 "Select another host to act as your forwarder"
513 )
514 .to_string(),
515 },
516 true,
517 ),
518 (
519 concat!(
520 "550-mailbox unavailable\n",
521 "550 user has moved with no forwarding address\n"
522 ),
523 Response {
524 code: 550,
525 esc: [0, 0, 0],
526 message: "mailbox unavailable\nuser has moved with no forwarding address"
527 .to_string(),
528 },
529 false,
530 ),
531 (
532 concat!(
533 "550-mailbox unavailable\n",
534 "550 user has moved with no forwarding address\n"
535 ),
536 Response {
537 code: 550,
538 esc: [0, 0, 0],
539 message: "mailbox unavailable\nuser has moved with no forwarding address"
540 .to_string(),
541 },
542 true,
543 ),
544 (
545 concat!(
546 "432-6.8.9\n",
547 "432-6.8.9 Hello\n",
548 "432-6.8.9 \n",
549 "432-6.8.9 ,\n",
550 "432-\n",
551 "432-6\n",
552 "432-6.\n",
553 "432-6.8\n",
554 "432-6.8.9\n",
555 "432 6.8.9 World!\n"
556 ),
557 Response {
558 code: 432,
559 esc: [6, 8, 9],
560 message: "\nHello\n\n,\n\n\n6\n\n6.\n\n6.8\n\n\nWorld!".to_string(),
561 },
562 true,
563 ),
564 (
565 "250 2address.org\n",
566 Response {
567 code: 250,
568 esc: [0, 0, 0],
569 message: "2address.org".to_string(),
570 },
571 true,
572 ),
573 (
574 "250 100.address.org\n",
575 Response {
576 code: 250,
577 esc: [0, 0, 0],
578 message: "100.address.org".to_string(),
579 },
580 true,
581 ),
582 (
583 "250 111111111111\n",
584 Response {
585 code: 250,
586 esc: [0, 0, 0],
587 message: "111111111111".to_string(),
588 },
589 true,
590 ),
591 (
592 "250 99999999999999\n",
593 Response {
594 code: 250,
595 esc: [0, 0, 0],
596 message: "99999999999999".to_string(),
597 },
598 true,
599 ),
600 (
601 "250 2.0.0 Message queued for delivery.\r\n",
602 Response {
603 code: 250,
604 esc: [2, 0, 0],
605 message: "Message queued for delivery.".to_string(),
606 },
607 true,
608 ),
609 (
610 concat!("250-Missing space\n", "250\n", "250 Ignore this"),
611 Response {
612 code: 250,
613 esc: [0, 0, 0],
614 message: "Missing space\n".to_string(),
615 },
616 true,
617 ),
618 ] {
619 assert_eq!(
620 parsed_response,
621 ResponseReceiver::default()
622 .parse(&mut response.as_bytes().iter())
623 .unwrap(),
624 "failed for {response:?}",
625 );
626 all_responses.extend_from_slice(response.as_bytes());
627 all_parsed_responses.push(parsed_response);
628 }
629
630 for chunk_size in [5, 10, 20, 30, 40, 50, 60] {
632 let mut receiver = ResponseReceiver::default();
633 let mut parsed_response = all_parsed_responses.clone().into_iter();
634 for chunk in all_responses.chunks(chunk_size) {
635 let mut bytes = chunk.iter();
636 loop {
637 match receiver.parse(&mut bytes) {
638 Ok(response) => {
639 assert_eq!(
640 parsed_response.next(),
641 Some(response),
642 "chunk size {chunk_size}",
643 );
644 receiver.reset();
645 }
646 Err(Error::NeedsMoreData { .. }) => {
647 break;
648 }
649 err => panic!("Unexpected error {err:?} for chunk size {chunk_size}"),
650 }
651 }
652 }
653 }
654 }
655}