1use std::borrow::Cow;
8use std::char;
9use std::slice::Iter;
10
11use crate::*;
12
13use super::*;
14
15const MAX_ADDRESS_LEN: usize = 256;
16const MAX_DOMAIN_LEN: usize = 255;
17
18impl<'a> Request<Cow<'a, str>> {
19 pub fn parse(bytes: &mut Iter<'a, u8>) -> Result<Request<Cow<'a, str>>, Error> {
20 let mut parser = Rfc5321Parser::new(bytes);
21 let command = parser.hashed_value()?;
22 if !parser.stop_char.is_ascii_whitespace() {
23 parser.seek_lf()?;
24 return Err(Error::UnknownCommand);
25 }
26
27 match command {
28 RCPT => {
29 if !(parser.stop_char == LF
30 || parser.hashed_value()? != TO
31 || parser.stop_char != b':' && parser.next_char()? != b':')
32 && parser.next_char()? == b'<'
33 {
34 if let Some(to) = parser.address()? {
35 if parser.stop_char == b'>' {
36 return Ok(Request::Rcpt {
37 to: parser.rcpt_to_parameters(to)?,
38 });
39 }
40 } else {
41 parser.seek_lf()?;
42 return Err(Error::InvalidRecipientAddress);
43 }
44 }
45 parser.seek_lf()?;
46 Err(Error::SyntaxError {
47 syntax: "RCPT TO:<forward-path> [parameters]",
48 })
49 }
50 MAIL => {
51 if !(parser.stop_char == LF
52 || parser.hashed_value()? != FROM
53 || parser.stop_char != b':' && parser.next_char()? != b':')
54 && parser.next_char()? == b'<'
55 {
56 if let Some(from) = parser.address()? {
57 if parser.stop_char == b'>' {
58 return Ok(Request::Mail {
59 from: parser.mail_from_parameters(from)?,
60 });
61 }
62 } else {
63 parser.seek_lf()?;
64 return Err(Error::InvalidSenderAddress);
65 }
66 }
67
68 parser.seek_lf()?;
69 Err(Error::SyntaxError {
70 syntax: "MAIL FROM:<reverse-path> [parameters]",
71 })
72 }
73 DATA => {
74 parser.seek_lf()?;
75 Ok(Request::Data)
76 }
77 EHLO => {
78 if parser.stop_char != LF {
79 let host = parser.text()?;
80 parser.seek_lf()?;
81 if (1..=MAX_DOMAIN_LEN).contains(&host.len()) {
82 return Ok(Request::Ehlo { host });
83 }
84 }
85 Err(Error::SyntaxError {
86 syntax: "EHLO domain",
87 })
88 }
89 BDAT => {
90 if parser.stop_char != LF {
91 let chunk_size = parser.size()?;
92 if chunk_size != usize::MAX && parser.stop_char.is_ascii_whitespace() {
93 if parser.stop_char != LF {
94 match parser.hashed_value()? {
95 LAST => {
96 parser.seek_lf()?;
97 return Ok(Request::Bdat {
98 chunk_size,
99 is_last: true,
100 });
101 }
102 0 => {
103 parser.seek_lf()?;
104 return Ok(Request::Bdat {
105 chunk_size,
106 is_last: false,
107 });
108 }
109 _ => (),
110 }
111 } else {
112 return Ok(Request::Bdat {
113 chunk_size,
114 is_last: false,
115 });
116 }
117 }
118 }
119 parser.seek_lf()?;
120 Err(Error::SyntaxError {
121 syntax: "BDAT chunk-size [LAST]",
122 })
123 }
124 AUTH => {
125 if parser.stop_char != LF
126 && let Some(mechanism) = parser.mechanism()?
127 {
128 let initial_response = if parser.stop_char != LF {
129 parser.text()?
130 } else {
131 Cow::Borrowed("")
132 };
133 parser.seek_lf()?;
134 return Ok(Request::Auth {
135 mechanism,
136 initial_response,
137 });
138 }
139 Err(Error::SyntaxError {
140 syntax: "AUTH mechanism [initial-response]",
141 })
142 }
143 EXPN => {
144 if parser.stop_char != LF {
145 let value = parser.string()?;
146 parser.seek_lf()?;
147 if (1..=MAX_ADDRESS_LEN).contains(&value.len()) {
148 return Ok(Request::Expn { value });
149 }
150 }
151 Err(Error::SyntaxError {
152 syntax: "EXPN string",
153 })
154 }
155 NOOP => {
156 if parser.stop_char != LF {
157 let value = parser.string()?;
158 parser.seek_lf()?;
159 Ok(Request::Noop { value })
160 } else {
161 Ok(Request::Noop {
162 value: Cow::Borrowed(""),
163 })
164 }
165 }
166 QUIT => {
167 parser.seek_lf()?;
168 Ok(Request::Quit)
169 }
170 LHLO => {
171 if parser.stop_char != LF {
172 let host = parser.text()?;
173 parser.seek_lf()?;
174 if (1..=MAX_DOMAIN_LEN).contains(&host.len()) {
175 return Ok(Request::Lhlo { host });
176 }
177 }
178 Err(Error::SyntaxError {
179 syntax: "LHLO domain",
180 })
181 }
182 RSET => {
183 parser.seek_lf()?;
184 Ok(Request::Rset)
185 }
186 VRFY => {
187 if parser.stop_char != LF {
188 let value = parser.string()?;
189 parser.seek_lf()?;
190 if (1..=MAX_ADDRESS_LEN).contains(&value.len()) {
191 return Ok(Request::Vrfy { value });
192 }
193 }
194 Err(Error::SyntaxError {
195 syntax: "VRFY string",
196 })
197 }
198 HELP => {
199 if parser.stop_char != LF {
200 let value = parser.string()?;
201 parser.seek_lf()?;
202 Ok(Request::Help { value })
203 } else {
204 Ok(Request::Help {
205 value: Cow::Borrowed(""),
206 })
207 }
208 }
209 STARTTLS => {
210 parser.seek_lf()?;
211 Ok(Request::StartTls)
212 }
213 ETRN => {
214 if parser.stop_char != LF {
215 let name = parser.string()?;
216 parser.seek_lf()?;
217 if !name.is_empty() {
218 return Ok(Request::Etrn { name });
219 }
220 }
221 Err(Error::SyntaxError {
222 syntax: "ETRN name",
223 })
224 }
225 ATRN => {
226 if parser.stop_char != LF {
227 let mut domains = Vec::new();
228 loop {
229 let domain = parser.seek_char(b',')?;
230 if !domain.is_empty() {
231 domains.push(domain);
232 }
233 if parser.stop_char != b',' {
234 parser.seek_lf()?;
235 if !domains.is_empty() {
236 return Ok(Request::Atrn { domains });
237 } else {
238 break;
239 }
240 }
241 }
242 }
243 Err(Error::SyntaxError {
244 syntax: "ATRN domain[,domain]",
245 })
246 }
247 BURL => {
248 if parser.stop_char != LF {
249 let uri = parser.text()?;
250 if !uri.is_empty() && parser.stop_char.is_ascii_whitespace() {
251 if parser.stop_char != LF {
252 match parser.hashed_value()? {
253 LAST => {
254 parser.seek_lf()?;
255 return Ok(Request::Burl { uri, is_last: true });
256 }
257 0 => {
258 parser.seek_lf()?;
259 return Ok(Request::Burl {
260 uri,
261 is_last: false,
262 });
263 }
264 _ => (),
265 }
266 } else {
267 return Ok(Request::Burl {
268 uri,
269 is_last: false,
270 });
271 }
272 }
273 }
274 parser.seek_lf()?;
275 Err(Error::SyntaxError {
276 syntax: "BURL absolute-uri [LAST]",
277 })
278 }
279 HELO => {
280 if parser.stop_char != LF {
281 let host = parser.text()?;
282 parser.seek_lf()?;
283 if (1..=MAX_DOMAIN_LEN).contains(&host.len()) {
284 return Ok(Request::Helo { host });
285 }
286 }
287 Err(Error::SyntaxError {
288 syntax: "HELO domain",
289 })
290 }
291 _ => {
292 parser.seek_lf()?;
293 Err(Error::UnknownCommand)
294 }
295 }
296 }
297}
298
299pub struct Rfc5321Parser<'x, 'y> {
300 bytes: &'x mut Iter<'y, u8>,
301 pub stop_char: u8,
302 pub bytes_left: usize,
303}
304
305impl<'x, 'y> Rfc5321Parser<'x, 'y> {
306 pub fn new(bytes: &'x mut Iter<'y, u8>) -> Self {
307 let (bytes_left, _) = bytes.size_hint();
308 Rfc5321Parser {
309 bytes,
310 bytes_left,
311 stop_char: 0,
312 }
313 }
314
315 #[allow(clippy::while_let_on_iterator)]
316 pub fn hashed_value(&mut self) -> Result<u64, Error> {
317 let mut value: u64 = 0;
318 let mut shift = 0;
319
320 while let Some(&ch) = self.bytes.next() {
321 match ch {
322 b'A'..=b'Z' | b'0'..=b'9' | b'-' if shift < 64 => {
323 value |= (ch as u64) << shift;
324 shift += 8;
325 }
326 b'a'..=b'z' if shift < 64 => {
327 value |= ((ch - b'a' + b'A') as u64) << shift;
328 shift += 8;
329 }
330 b'\r' => (),
331 b' ' => {
332 if value != 0 {
333 self.stop_char = ch;
334 return Ok(value);
335 }
336 }
337 _ => {
338 self.stop_char = ch;
339 return Ok(value);
340 }
341 }
342 }
343
344 Err(Error::NeedsMoreData {
345 bytes_left: self.bytes_left,
346 })
347 }
348
349 #[allow(clippy::while_let_on_iterator)]
350 pub fn hashed_value_long(&mut self) -> Result<u128, Error> {
351 let mut value: u128 = 0;
352 let mut shift = 0;
353
354 while let Some(&ch) = self.bytes.next() {
355 match ch {
356 b'A'..=b'Z' | b'0'..=b'9' | b'-' if shift < 128 => {
357 value |= (ch as u128) << shift;
358 shift += 8;
359 }
360 b'a'..=b'z' if shift < 128 => {
361 value |= ((ch - b'a' + b'A') as u128) << shift;
362 shift += 8;
363 }
364 b' ' => {
365 if value != 0 {
366 self.stop_char = b' ';
367 return Ok(value);
368 }
369 }
370 b'\r' => (),
371 _ => {
372 self.stop_char = ch;
373 return Ok(value);
374 }
375 }
376 }
377
378 Err(Error::NeedsMoreData {
379 bytes_left: self.bytes_left,
380 })
381 }
382
383 pub fn address(&mut self) -> Result<Option<Cow<'y, str>>, Error> {
384 let mut value = self.start_zero_copy();
385 let mut last_ch = 0;
386 let mut in_quote = false;
387 let mut at_count = 0;
388 let mut lp_len = 0;
389
390 while let Some(&ch) = self.bytes.next() {
391 match ch {
392 b'0'..=b'9'
393 | b'a'..=b'z'
394 | b'A'..=b'Z'
395 | b'!'
396 | b'#'
397 | b'$'
398 | b'%'
399 | b'&'
400 | b'\''
401 | b'*'
402 | b'+'
403 | b'-'
404 | b'/'
405 | b'='
406 | b'?'
407 | b'^'
408 | b'_'
409 | b'`'
410 | b'{'
411 | b'|'
412 | b'}'
413 | b'~'
414 | 0x7f..=u8::MAX => {}
415 b'.' if !in_quote => {
416 if last_ch == b'.' || last_ch == b'@' || self.len_excluding_current(&value) == 0
417 {
418 self.stop_char = ch;
419 return Ok(None);
420 }
421 }
422 b'@' if !in_quote => {
423 at_count += 1;
424 lp_len = self.len_excluding_current(&value);
425 }
426 b'>' if !in_quote => {
427 self.stop_char = ch;
428 self.flush_excluding_current(&mut value);
429 let value = value.data;
430
431 return Ok(match value.len() {
432 1..=MAX_ADDRESS_LEN if at_count == 1 && lp_len > 0 => Some(value),
433 10 if at_count == 0 && value.eq_ignore_ascii_case("postmaster") => {
434 Some("".into())
435 }
436 0 => Some("".into()),
437 _ => None,
438 });
439 }
440 b'\r' => self.flush_excluding_current(&mut value),
441 b':' if !in_quote && self.first_excluding_current(&value) == Some(b'@') => {
442 value = self.start_zero_copy();
444 at_count = 0;
445 lp_len = 0;
446 }
447 b',' if !in_quote && self.first_excluding_current(&value) == Some(b'@') => (),
451 b' ' if !in_quote => {
452 self.flush_excluding_current(&mut value);
453 if !value.data.is_empty() {
454 self.stop_char = b' ';
455 let value = value.data;
456
457 let is_valid = value.is_empty()
458 || value.len() <= MAX_ADDRESS_LEN && at_count == 1 && lp_len > 0;
459
460 return Ok(is_valid.then_some(value));
461 }
462 }
463 b'\n' => {
464 self.stop_char = b'\n';
465 self.flush_excluding_current(&mut value);
466 let value = value.data;
467
468 let is_valid = value.is_empty()
469 || value.len() <= MAX_ADDRESS_LEN && at_count == 1 && lp_len > 0;
470
471 return Ok(is_valid.then_some(value));
472 }
473 b'\"' if !in_quote || last_ch != b'\\' => {
474 in_quote = !in_quote;
475 self.flush_excluding_current(&mut value);
476 }
477 b'\\' if in_quote => {
478 if last_ch != b'\\' {
479 self.flush_excluding_current(&mut value);
480 } else {
481 last_ch = 0;
483 continue;
484 }
485 }
486 _ if in_quote => {}
487 _ => {
488 self.stop_char = ch;
489 return Ok(None);
490 }
491 }
492
493 last_ch = ch;
494 }
495
496 Err(Error::NeedsMoreData {
497 bytes_left: self.bytes_left,
498 })
499 }
500
501 pub fn string(&mut self) -> Result<Cow<'y, str>, Error> {
502 let mut in_quote = false;
503 let mut value = self.start_zero_copy();
504 let mut last_ch = 0;
505
506 while let Some(&ch) = self.bytes.next() {
507 match ch {
508 b' ' if !in_quote => {
509 self.flush_excluding_current(&mut value);
510 if !value.data.is_empty() {
511 self.stop_char = b' ';
512 return Ok(value.data);
513 }
514 }
515 b'\n' => {
516 self.flush_excluding_current(&mut value);
517 self.stop_char = b'\n';
518 return Ok(value.data);
519 }
520 b'\"' if !in_quote || last_ch != b'\\' => {
521 in_quote = !in_quote;
522 self.flush_excluding_current(&mut value);
523 }
524 b'\\' if in_quote => {
525 if last_ch != b'\\' {
526 self.flush_excluding_current(&mut value);
527 } else {
528 last_ch = 0;
529 continue;
530 }
531 }
532 b'\r' => self.flush_excluding_current(&mut value),
533 _ => {}
534 }
535
536 last_ch = ch;
537 }
538
539 Err(Error::NeedsMoreData {
540 bytes_left: self.bytes_left,
541 })
542 }
543
544 #[allow(clippy::while_let_on_iterator)]
545 pub fn text(&mut self) -> Result<Cow<'y, str>, Error> {
546 let mut value = self.start_zero_copy();
547 while let Some(&ch) = self.bytes.next() {
548 match ch {
549 b'\n' | b' ' => {
550 self.flush_excluding_current(&mut value);
551 self.stop_char = ch;
552 return Ok(value.data);
553 }
554 b'\r' => self.flush_excluding_current(&mut value),
555 _ => {}
556 }
557 }
558
559 Err(Error::NeedsMoreData {
560 bytes_left: self.bytes_left,
561 })
562 }
563
564 #[allow(clippy::while_let_on_iterator)]
565 pub fn xtext(&mut self) -> Result<Cow<'y, str>, Error> {
566 let mut value = self.start_zero_copy();
567 while let Some(&ch) = self.bytes.next() {
568 match ch {
569 b'\n' | b' ' => {
570 self.flush_excluding_current(&mut value);
571 self.stop_char = ch;
572 return Ok(value.data);
573 }
574 b'+' => {
575 self.flush_excluding_current(&mut value);
576
577 let mut hex1 = None;
578
579 while let Some(&ch) = self.bytes.next() {
580 if let Some(digit) = char::from(ch).to_digit(16) {
581 if let Some(hex1) = hex1 {
582 let data = value.data.to_mut();
583 data.push(char::from(((hex1 as u8) << 4) | digit as u8));
584 break;
585 } else {
586 hex1 = Some(digit);
587 }
588 } else if ch == LF {
589 self.stop_char = b'\n';
590 return Ok(value.data);
591 } else {
592 break;
593 }
594 }
595
596 self.drop_extra(&mut value);
597 }
598 b'\r' => self.flush_excluding_current(&mut value),
599 _ => {}
600 }
601 }
602
603 Err(Error::NeedsMoreData {
604 bytes_left: self.bytes_left,
605 })
606 }
607
608 #[allow(clippy::while_let_on_iterator)]
609 pub fn seek_char(&mut self, stop_char: u8) -> Result<Cow<'y, str>, Error> {
610 let mut value = self.start_zero_copy();
611 while let Some(&ch) = self.bytes.next() {
612 match ch {
613 b'\n' => {
614 self.flush_excluding_current(&mut value);
615 self.stop_char = b'\n';
616 return Ok(value.data);
617 }
618 b' ' => {
619 self.flush_excluding_current(&mut value);
620 if !value.data.is_empty() {
621 self.stop_char = b' ';
622 return Ok(value.data);
623 }
624 }
625 b'\r' => self.flush_excluding_current(&mut value),
626 _ if ch == stop_char => {
627 self.flush_excluding_current(&mut value);
628 self.stop_char = ch;
629 return Ok(value.data);
630 }
631 _ => {}
632 }
633 }
634
635 Err(Error::NeedsMoreData {
636 bytes_left: self.bytes_left,
637 })
638 }
639
640 #[inline(always)]
641 pub fn seek_lf(&mut self) -> Result<(), Error> {
642 if self.stop_char != LF {
643 for &ch in &mut self.bytes {
644 if ch == LF {
645 return Ok(());
646 }
647 }
648 Err(Error::NeedsMoreData {
649 bytes_left: self.bytes_left,
650 })
651 } else {
652 Ok(())
653 }
654 }
655
656 #[inline(always)]
657 pub fn next_char(&mut self) -> Result<u8, Error> {
658 for &ch in &mut self.bytes {
659 match ch {
660 b' ' | b'\r' => (),
661 _ => {
662 self.stop_char = ch;
663 return Ok(ch);
664 }
665 }
666 }
667 Err(Error::NeedsMoreData {
668 bytes_left: self.bytes_left,
669 })
670 }
671
672 #[inline(always)]
673 pub fn read_char(&mut self) -> Result<u8, Error> {
674 self.bytes.next().copied().ok_or(Error::NeedsMoreData {
675 bytes_left: self.bytes_left,
676 })
677 }
678
679 pub fn size(&mut self) -> Result<usize, Error> {
680 let mut value = usize::MAX;
681 for &ch in &mut self.bytes {
682 match ch {
683 b'0'..=b'9' => {
684 value = if value != usize::MAX {
685 value
686 .saturating_mul(10)
687 .saturating_add((ch - b'0') as usize)
688 } else {
689 (ch - b'0') as usize
690 };
691 }
692 b'\r' => (),
693 b' ' => {
694 if value != usize::MAX {
695 self.stop_char = b' ';
696 return Ok(value);
697 }
698 }
699 _ => {
700 self.stop_char = ch;
701 return Ok(value);
702 }
703 }
704 }
705 Err(Error::NeedsMoreData {
706 bytes_left: self.bytes_left,
707 })
708 }
709
710 pub fn integer(&mut self) -> Result<i64, Error> {
711 let mut value = i64::MAX;
712 let mut multiplier = 1;
713
714 for &ch in &mut self.bytes {
715 match ch {
716 b'0'..=b'9' => {
717 value = if value != i64::MAX {
718 value.saturating_mul(10).saturating_add((ch - b'0') as i64)
719 } else {
720 (ch - b'0') as i64
721 };
722 }
723 b' ' => {
724 if value != i64::MAX {
725 self.stop_char = b' ';
726 return Ok(value * multiplier);
727 }
728 }
729 b'-' if value == i64::MAX => {
730 multiplier = -1;
731 }
732 b'+' if value == i64::MAX => (),
733 b'\r' => (),
734 _ => {
735 self.stop_char = ch;
736 return Ok(if value != i64::MAX {
737 value * multiplier
738 } else {
739 i64::MAX
740 });
741 }
742 }
743 }
744 Err(Error::NeedsMoreData {
745 bytes_left: self.bytes_left,
746 })
747 }
748
749 pub fn timestamp(&mut self) -> Result<i64, Error> {
750 let mut dt = [0u32; 8];
751 let mut zone_multiplier = 1;
752 let mut pos = 0;
753
754 for &ch in &mut self.bytes {
755 match ch {
756 b'0'..=b'9' if pos < 8 => {
757 dt[pos] = dt[pos]
758 .saturating_mul(10)
759 .saturating_add((ch - b'0') as u32);
760 }
761 b'-' if pos <= 1 || pos == 5 => {
762 pos += 1;
763 }
764 b'+' if pos == 5 => {
765 zone_multiplier = -1;
766 pos += 1;
767 }
768 b'T' if pos == 2 => {
769 pos += 1;
770 }
771 b':' if pos == 3 || pos == 4 || pos == 6 => {
772 pos += 1;
773 }
774 b'Z' if pos == 5 => {
775 pos = 8;
776 }
777 _ => {
778 self.stop_char = ch;
779 return Ok(if pos >= 7 {
780 let month = dt[1];
782 let year_base = 4800; let m_adj = month.wrapping_sub(3); let carry = i64::from(m_adj > month);
785 let adjust = if carry > 0 { 12 } else { 0 };
786 let y_adj = dt[0] as i64 + year_base - carry;
787 let month_days =
788 ((m_adj.wrapping_add(adjust)).wrapping_mul(62719) + 769) / 2048;
789 let leap_days = y_adj / 4 - y_adj / 100 + y_adj / 400;
790 (y_adj * 365 + leap_days + month_days as i64 + (dt[2] as i64 - 1) - 2472632)
791 * 86400
792 + dt[3] as i64 * 3600
793 + dt[4] as i64 * 60
794 + dt[5] as i64
795 + ((dt[6] as i64 * 3600 + dt[7] as i64 * 60) * zone_multiplier)
796 } else {
797 i64::MAX
798 });
799 }
800 }
801 }
802
803 Err(Error::NeedsMoreData {
804 bytes_left: self.bytes_left,
805 })
806 }
807
808 pub fn mail_from_parameters(
809 &mut self,
810 address: Cow<'y, str>,
811 ) -> Result<MailFrom<Cow<'y, str>>, Error> {
812 let mut params = MailFrom {
813 address,
814 flags: 0,
815 size: 0,
816 trans_id: None,
817 by: 0,
818 env_id: None,
819 solicit: None,
820 mtrk: None,
821 auth: None,
822 hold_for: 0,
823 hold_until: 0,
824 mt_priority: 0,
825 };
826 while self.stop_char != LF {
827 let key = self.hashed_value_long()?;
828 match key {
829 SMTPUTF8 if self.stop_char.is_ascii_whitespace() => {
830 params.flags |= MAIL_SMTPUTF8;
831 }
832 BODY if self.stop_char == b'=' => {
833 params.flags |= match self.hashed_value_long()? {
834 EIGHBITMIME if self.stop_char.is_ascii_whitespace() => MAIL_BODY_8BITMIME,
835 BINARYMIME if self.stop_char.is_ascii_whitespace() => MAIL_BODY_BINARYMIME,
836 SEVENBIT if self.stop_char.is_ascii_whitespace() => MAIL_BODY_7BIT,
837 _ => {
838 self.seek_lf()?;
839 return Err(Error::InvalidParameter { param: "BODY" });
840 }
841 }
842 }
843 SIZE if self.stop_char == b'=' => {
844 let size = self.size()?;
845 if size != usize::MAX && self.stop_char.is_ascii_whitespace() {
846 params.size = size;
847 } else {
848 self.seek_lf()?;
849 return Err(Error::InvalidParameter { param: "SIZE" });
850 }
851 }
852 BY if self.stop_char == b'=' => {
853 let time = self.integer()?;
854 if time != i64::MAX && self.stop_char == b';' {
855 params.flags |= match self.hashed_value()? {
856 N if self.stop_char.is_ascii_whitespace() => MAIL_BY_NOTIFY,
857 NT if self.stop_char.is_ascii_whitespace() => {
858 MAIL_BY_NOTIFY | MAIL_BY_TRACE
859 }
860 R if self.stop_char.is_ascii_whitespace() => MAIL_BY_RETURN,
861 RT if self.stop_char.is_ascii_whitespace() => {
862 MAIL_BY_RETURN | MAIL_BY_TRACE
863 }
864 _ => {
865 self.seek_lf()?;
866 return Err(Error::InvalidParameter { param: "BY" });
867 }
868 };
869 params.by = time;
870 } else {
871 self.seek_lf()?;
872 return Err(Error::InvalidParameter { param: "BY" });
873 }
874 }
875 HOLDUNTIL if self.stop_char == b'=' => {
876 let hold = self.size()?;
877 if hold != usize::MAX && self.stop_char.is_ascii_whitespace() {
878 params.hold_until = hold as u64;
879 } else {
880 self.seek_lf()?;
881 return Err(Error::InvalidParameter { param: "HOLDUNTIL" });
882 }
883 }
884 HOLDFOR if self.stop_char == b'=' => {
885 let hold = self.size()?;
886 if hold != usize::MAX && self.stop_char.is_ascii_whitespace() {
887 params.hold_for = hold as u64;
888 } else {
889 self.seek_lf()?;
890 return Err(Error::InvalidParameter { param: "HOLDFOR" });
891 }
892 }
893 RET if self.stop_char == b'=' => {
894 params.flags |= match self.hashed_value()? {
895 FULL if self.stop_char.is_ascii_whitespace() => MAIL_RET_FULL,
896 HDRS if self.stop_char.is_ascii_whitespace() => MAIL_RET_HDRS,
897 _ => {
898 self.seek_lf()?;
899 return Err(Error::InvalidParameter { param: "RET" });
900 }
901 };
902 }
903 ENVID if self.stop_char == b'=' => {
904 let env_id = self.xtext()?;
905 if self.stop_char.is_ascii_whitespace() && (1..=100).contains(&env_id.len()) {
906 params.env_id = env_id.into();
907 } else {
908 self.seek_lf()?;
909 return Err(Error::InvalidParameter { param: "ENVID" });
910 }
911 }
912 REQUIRETLS if self.stop_char.is_ascii_whitespace() => {
913 params.flags |= MAIL_REQUIRETLS;
914 }
915 SOLICIT if self.stop_char == b'=' => {
916 let solicit = self.text()?;
917 if !solicit.is_empty() && self.stop_char.is_ascii_whitespace() {
918 params.solicit = solicit.into();
919 } else {
920 self.seek_lf()?;
921 return Err(Error::InvalidParameter { param: "SOLICIT" });
922 }
923 }
924 TRANSID if self.stop_char == b'=' => {
925 if self.next_char()? == b'<' {
926 let transid = self.seek_char(b'>')?;
927 if self.stop_char == b'>' && !transid.is_empty() {
928 params.trans_id = transid.into();
929 self.stop_char = SP;
930 continue;
931 }
932 }
933 self.seek_lf()?;
934 return Err(Error::InvalidParameter { param: "TRANSID" });
935 }
936 MTRK if self.stop_char == b'=' => {
937 let certifier = self.seek_char(b':')?;
938 let timeout = if self.stop_char == b':' {
939 self.size()?
940 } else {
941 0
942 };
943
944 if !certifier.is_empty()
945 && self.stop_char.is_ascii_whitespace()
946 && timeout != usize::MAX
947 {
948 params.mtrk = Mtrk {
949 certifier,
950 timeout: timeout as u64,
951 }
952 .into();
953 } else {
954 self.seek_lf()?;
955 return Err(Error::InvalidParameter { param: "MTRK" });
956 }
957 }
958 AUTH_ if self.stop_char == b'=' => {
959 let mailbox = self.xtext()?;
960 if (1..=MAX_ADDRESS_LEN).contains(&mailbox.len())
961 && self.stop_char.is_ascii_whitespace()
962 {
963 params.auth = mailbox.into();
964 } else {
965 self.seek_lf()?;
966 return Err(Error::InvalidParameter { param: "AUTH" });
967 }
968 }
969 MT_PRIORITY if self.stop_char == b'=' => {
970 let priority = self.integer()?;
971 if priority != i64::MAX && self.stop_char.is_ascii_whitespace() {
972 params.mt_priority = priority;
973 } else {
974 self.seek_lf()?;
975 return Err(Error::InvalidParameter {
976 param: "MT-PRIORITY",
977 });
978 }
979 }
980 CONPERM if self.stop_char.is_ascii_whitespace() => {
981 params.flags |= MAIL_CONPERM;
982 }
983 0 => {
984 if !self.stop_char.is_ascii_whitespace() {
990 self.seek_lf()?;
991 return Err(Error::SyntaxError {
992 syntax: "MAIL FROM:<reverse-path> [parameters]",
993 });
994 }
995 }
996 unknown => {
997 let mut param = Vec::with_capacity(16);
998 for ch in unknown.to_le_bytes() {
999 if ch != 0 {
1000 param.push(ch.to_ascii_uppercase());
1001 }
1002 }
1003 if !self.stop_char.is_ascii_whitespace() {
1004 param.push(self.stop_char.to_ascii_uppercase());
1005 for &ch in &mut self.bytes {
1006 if !ch.is_ascii_whitespace() {
1007 param.push(ch.to_ascii_uppercase());
1008 } else {
1009 self.stop_char = ch;
1010 break;
1011 }
1012 }
1013 }
1014
1015 self.seek_lf()?;
1016 return Err(Error::UnsupportedParameter {
1017 param: param.into_string(),
1018 });
1019 }
1020 }
1021 }
1022
1023 Ok(params)
1024 }
1025
1026 pub fn rcpt_to_parameters(
1027 &mut self,
1028 address: Cow<'y, str>,
1029 ) -> Result<RcptTo<Cow<'y, str>>, Error> {
1030 let mut params = RcptTo {
1031 address,
1032 orcpt: None,
1033 rrvs: 0,
1034 flags: 0,
1035 };
1036 while self.stop_char != LF {
1037 let key = self.hashed_value_long()?;
1038 match key {
1039 NOTIFY if self.stop_char == b'=' => loop {
1040 match self.hashed_value_long()? {
1041 NEVER
1042 if (params.flags
1043 & (RCPT_NOTIFY_NEVER
1044 | RCPT_NOTIFY_SUCCESS
1045 | RCPT_NOTIFY_FAILURE
1046 | RCPT_NOTIFY_DELAY))
1047 == 0 =>
1048 {
1049 params.flags |= RCPT_NOTIFY_NEVER;
1050 }
1051 SUCCESS => {
1052 params.flags |= RCPT_NOTIFY_SUCCESS;
1053 }
1054 FAILURE => {
1055 params.flags |= RCPT_NOTIFY_FAILURE;
1056 }
1057 DELAY => {
1058 params.flags |= RCPT_NOTIFY_DELAY;
1059 }
1060 _ => {
1061 self.seek_lf()?;
1062 return Err(Error::InvalidParameter { param: "NOTIFY" });
1063 }
1064 }
1065 if self.stop_char.is_ascii_whitespace() {
1066 break;
1067 } else if self.stop_char != b',' {
1068 self.seek_lf()?;
1069 return Err(Error::InvalidParameter { param: "NOTIFY" });
1070 }
1071 },
1072 ORCPT if self.stop_char == b'=' => {
1073 let v = self.hashed_value()?;
1074 if v != RFC822 || self.stop_char != b';' {
1075 self.seek_lf()?;
1076 return Err(Error::InvalidParameter { param: "ORCPT" });
1077 }
1078 let addr = self.xtext()?;
1079 if self.stop_char.is_ascii_whitespace()
1080 && (1..=MAX_ADDRESS_LEN).contains(&addr.len())
1081 {
1082 params.orcpt = addr.into();
1083 } else {
1084 self.seek_lf()?;
1085 return Err(Error::InvalidParameter { param: "ORCPT" });
1086 }
1087 }
1088 RRVS if self.stop_char == b'=' => {
1089 let time = self.timestamp()?;
1090 if time != i64::MAX && self.stop_char.is_ascii_whitespace()
1091 || self.stop_char == b';'
1092 {
1093 let is_reject = self.stop_char != b';'
1094 || match self.hashed_value()? {
1095 C if self.stop_char.is_ascii_whitespace() => false,
1096 R if self.stop_char.is_ascii_whitespace() => true,
1097 _ => {
1098 self.seek_lf()?;
1099 return Err(Error::InvalidParameter { param: "RRVS" });
1100 }
1101 };
1102 params.rrvs = time;
1103 params.flags |= if is_reject {
1104 RCPT_RRVS_REJECT
1105 } else {
1106 RCPT_RRVS_CONTINUE
1107 };
1108 } else {
1109 self.seek_lf()?;
1110 return Err(Error::InvalidParameter { param: "RRVS" });
1111 }
1112 }
1113 CONNEG if self.stop_char.is_ascii_whitespace() => {
1114 params.flags |= RCPT_CONNEG;
1115 }
1116 0 => {
1117 if !self.stop_char.is_ascii_whitespace() {
1118 self.seek_lf()?;
1119 return Err(Error::SyntaxError {
1120 syntax: "RCPT TO:<forward-path> [parameters]",
1121 });
1122 }
1123 }
1124 unknown => {
1125 let mut param = Vec::with_capacity(16);
1126 for ch in unknown.to_le_bytes() {
1127 if ch != 0 {
1128 param.push(ch.to_ascii_uppercase());
1129 }
1130 }
1131 if !self.stop_char.is_ascii_whitespace() {
1132 param.push(self.stop_char.to_ascii_uppercase());
1133 for &ch in &mut self.bytes {
1134 if !ch.is_ascii_whitespace() {
1135 param.push(ch.to_ascii_uppercase());
1136 } else {
1137 self.stop_char = ch;
1138 break;
1139 }
1140 }
1141 }
1142
1143 self.seek_lf()?;
1144 return Err(Error::UnsupportedParameter {
1145 param: param.into_string(),
1146 });
1147 }
1148 }
1149 }
1150
1151 Ok(params)
1152 }
1153
1154 pub fn mechanism(&mut self) -> Result<Option<u64>, Error> {
1155 let mut trailing_chars = [0u8; 8];
1156 let mut pos = 0;
1157 let mechanism = self.hashed_value_long()?;
1158 if !self.stop_char.is_ascii_whitespace() {
1159 trailing_chars[0] = self.stop_char;
1160 pos += 1;
1161 for &ch in &mut self.bytes {
1162 if !ch.is_ascii_whitespace() {
1163 if let Some(tch) = trailing_chars.get_mut(pos) {
1164 *tch = ch.to_ascii_uppercase();
1165 }
1166 pos += 1;
1167 } else {
1168 self.stop_char = ch;
1169 break;
1170 }
1171 }
1172 if !self.stop_char.is_ascii_whitespace() {
1173 return Err(Error::NeedsMoreData {
1174 bytes_left: self.bytes_left,
1175 });
1176 } else if pos > 8 {
1177 return Ok(0.into());
1178 }
1179 }
1180 Ok(match (mechanism, &trailing_chars[..pos]) {
1181 (_9798_M_DSA_SHA1, b"") => AUTH_9798_M_DSA_SHA1.into(),
1182 (_9798_M_ECDSA_SHA, b"1") => AUTH_9798_M_ECDSA_SHA1.into(),
1183 (_9798_M_RSA_SHA1_, b"ENC") => AUTH_9798_M_RSA_SHA1_ENC.into(),
1184 (_9798_U_DSA_SHA1, b"") => AUTH_9798_U_DSA_SHA1.into(),
1185 (_9798_U_ECDSA_SHA, b"1") => AUTH_9798_U_ECDSA_SHA1.into(),
1186 (_9798_U_RSA_SHA1_, b"ENC") => AUTH_9798_U_RSA_SHA1_ENC.into(),
1187 (ANONYMOUS, b"") => AUTH_ANONYMOUS.into(),
1188 (CRAM_MD5, b"") => AUTH_CRAM_MD5.into(),
1189 (DIGEST_MD5, b"") => AUTH_DIGEST_MD5.into(),
1190 (EAP_AES128, b"") => AUTH_EAP_AES128.into(),
1191 (EAP_AES128_PLUS, b"") => AUTH_EAP_AES128_PLUS.into(),
1192 (ECDH_X25519_CHAL, b"LENGE") => AUTH_ECDH_X25519_CHALLENGE.into(),
1193 (ECDSA_NIST256P_C, b"HALLENGE") => AUTH_ECDSA_NIST256P_CHALLENGE.into(),
1194 (EXTERNAL, b"") => AUTH_EXTERNAL.into(),
1195 (GS2_KRB5, b"") => AUTH_GS2_KRB5.into(),
1196 (GS2_KRB5_PLUS, b"") => AUTH_GS2_KRB5_PLUS.into(),
1197 (GSS_SPNEGO, b"") => AUTH_GSS_SPNEGO.into(),
1198 (GSSAPI, b"") => AUTH_GSSAPI.into(),
1199 (KERBEROS_V4, b"") => AUTH_KERBEROS_V4.into(),
1200 (KERBEROS_V5, b"") => AUTH_KERBEROS_V5.into(),
1201 (LOGIN, b"") => AUTH_LOGIN.into(),
1202 (NMAS_SAMBA_AUTH, b"") => AUTH_NMAS_SAMBA_AUTH.into(),
1203 (NMAS_AUTHEN, b"") => AUTH_NMAS_AUTHEN.into(),
1204 (NMAS_LOGIN, b"") => AUTH_NMAS_LOGIN.into(),
1205 (NTLM, b"") => AUTH_NTLM.into(),
1206 (OAUTH10A, b"") => AUTH_OAUTH10A.into(),
1207 (OAUTHBEARER, b"") => AUTH_OAUTHBEARER.into(),
1208 (OPENID20, b"") => AUTH_OPENID20.into(),
1209 (OTP, b"") => AUTH_OTP.into(),
1210 (PLAIN, b"") => AUTH_PLAIN.into(),
1211 (SAML20, b"") => AUTH_SAML20.into(),
1212 (SCRAM_SHA_1, b"") => AUTH_SCRAM_SHA_1.into(),
1213 (SCRAM_SHA_1_PLUS, b"") => AUTH_SCRAM_SHA_1_PLUS.into(),
1214 (SCRAM_SHA_256, b"") => AUTH_SCRAM_SHA_256.into(),
1215 (SCRAM_SHA_256_PL, b"US") => AUTH_SCRAM_SHA_256_PLUS.into(),
1216 (SECURID, b"") => AUTH_SECURID.into(),
1217 (SKEY, b"") => AUTH_SKEY.into(),
1218 (SPNEGO, b"") => AUTH_SPNEGO.into(),
1219 (SPNEGO_PLUS, b"") => AUTH_SPNEGO_PLUS.into(),
1220 (SXOVER_PLUS, b"") => AUTH_SXOVER_PLUS.into(),
1221 (XOAUTH, b"") => AUTH_XOAUTH.into(),
1222 (XOAUTH2, b"") => AUTH_XOAUTH2.into(),
1223 (0, b"") => None,
1224 _ => 0.into(),
1225 })
1226 }
1227
1228 #[inline(always)]
1230 fn start_zero_copy(&self) -> MaybeZeroCopy<'y> {
1231 MaybeZeroCopy {
1232 remaining: self.bytes.as_slice(),
1233 data: Cow::Borrowed(""),
1234 }
1235 }
1236
1237 fn extra_before_current(&self, out: &MaybeZeroCopy<'y>) -> &'y [u8] {
1241 let new_len = self.bytes.as_slice().as_ptr().addr() - out.remaining.as_ptr().addr() - 1;
1244 &out.remaining[..new_len]
1245 }
1246
1247 fn len_excluding_current(&self, out: &MaybeZeroCopy<'y>) -> usize {
1249 out.data.len() + self.extra_before_current(out).len()
1250 }
1251
1252 fn first_excluding_current(&self, out: &MaybeZeroCopy<'y>) -> Option<u8> {
1254 out.data
1255 .bytes()
1256 .next()
1257 .or_else(|| self.extra_before_current(out).first().copied())
1258 }
1259
1260 #[inline(always)]
1262 fn drop_extra(&self, out: &mut MaybeZeroCopy<'y>) {
1263 out.remaining = self.bytes.as_slice();
1264 }
1265
1266 fn flush_excluding_current(&self, out: &mut MaybeZeroCopy<'y>) {
1268 let extra = self.extra_before_current(out);
1269 self.drop_extra(out);
1270
1271 if out.data.is_empty() {
1272 out.data = String::from_utf8_lossy(extra);
1273 } else {
1274 if let Cow::Borrowed(s) = out.data {
1275 let mut buf = String::with_capacity(32);
1276 buf.push_str(s);
1277 out.data = Cow::Owned(buf);
1278 }
1279
1280 let out = out.data.to_mut();
1283 for chunk in extra.utf8_chunks() {
1284 out.push_str(chunk.valid());
1285 if !chunk.invalid().is_empty() {
1286 out.push(char::REPLACEMENT_CHARACTER);
1287 }
1288 }
1289 }
1290 }
1291}
1292
1293struct MaybeZeroCopy<'y> {
1295 remaining: &'y [u8],
1297 data: Cow<'y, str>,
1299}
1300
1301#[cfg(test)]
1302mod tests {
1303 use std::borrow::Cow;
1304
1305 use crate::*;
1306
1307 #[test]
1308 fn parse_request() {
1309 for item in [
1310 (
1312 "EHLO bar.com",
1313 Ok(Request::Ehlo {
1314 host: Cow::Borrowed("bar.com"),
1315 }),
1316 ),
1317 (
1318 "EHLO",
1319 Err(Error::SyntaxError {
1320 syntax: "EHLO domain",
1321 }),
1322 ),
1323 (
1324 "HELO bar.com",
1325 Ok(Request::Helo {
1326 host: "bar.com".into(),
1327 }),
1328 ),
1329 (
1330 "HELO",
1331 Err(Error::SyntaxError {
1332 syntax: "HELO domain",
1333 }),
1334 ),
1335 (
1336 "LHLO bar.com",
1337 Ok(Request::Lhlo {
1338 host: "bar.com".into(),
1339 }),
1340 ),
1341 (
1342 "LHLO",
1343 Err(Error::SyntaxError {
1344 syntax: "LHLO domain",
1345 }),
1346 ),
1347 (
1349 "VRFY Hello",
1350 Ok(Request::Vrfy {
1351 value: "Hello".into(),
1352 }),
1353 ),
1354 (
1355 "VRFY \"Hello\\\" Wo\\\\rld\"",
1356 Ok(Request::Vrfy {
1357 value: "Hello\" Wo\\rld".into(),
1358 }),
1359 ),
1360 (
1361 "VRFY \"\"",
1362 Err(Error::SyntaxError {
1363 syntax: "VRFY string",
1364 }),
1365 ),
1366 (
1367 "VRFY",
1368 Err(Error::SyntaxError {
1369 syntax: "VRFY string",
1370 }),
1371 ),
1372 (
1374 "EXPN Hello",
1375 Ok(Request::Expn {
1376 value: "Hello".into(),
1377 }),
1378 ),
1379 (
1380 "EXPN \"Hello\\\" Wo\\\\rld\"",
1381 Ok(Request::Expn {
1382 value: "Hello\" Wo\\rld".into(),
1383 }),
1384 ),
1385 (
1386 "EXPN \"\"",
1387 Err(Error::SyntaxError {
1388 syntax: "EXPN string",
1389 }),
1390 ),
1391 (
1392 "EXPN",
1393 Err(Error::SyntaxError {
1394 syntax: "EXPN string",
1395 }),
1396 ),
1397 ("NOOP", Ok(Request::Noop { value: "".into() })),
1399 (
1400 "NOOP Hello",
1401 Ok(Request::Noop {
1402 value: "Hello".into(),
1403 }),
1404 ),
1405 ("HELP", Ok(Request::Help { value: "".into() })),
1407 (
1408 "HELP Hello",
1409 Ok(Request::Help {
1410 value: "Hello".into(),
1411 }),
1412 ),
1413 ("DATA", Ok(Request::Data)),
1415 ("QUIT", Ok(Request::Quit)),
1416 ("RSET", Ok(Request::Rset)),
1417 ("STARTTLS", Ok(Request::StartTls)),
1418 (
1420 "BDAT 0",
1421 Ok(Request::Bdat {
1422 chunk_size: 0,
1423 is_last: false,
1424 }),
1425 ),
1426 (
1427 "BDAT 123456",
1428 Ok(Request::Bdat {
1429 chunk_size: 123456,
1430 is_last: false,
1431 }),
1432 ),
1433 (
1434 "BDAT 123456 LAST",
1435 Ok(Request::Bdat {
1436 chunk_size: 123456,
1437 is_last: true,
1438 }),
1439 ),
1440 (
1441 "BDAT",
1442 Err(Error::SyntaxError {
1443 syntax: "BDAT chunk-size [LAST]",
1444 }),
1445 ),
1446 (
1447 "BDAT 123LAST",
1448 Err(Error::SyntaxError {
1449 syntax: "BDAT chunk-size [LAST]",
1450 }),
1451 ),
1452 (
1453 "BDAT 123x LAST",
1454 Err(Error::SyntaxError {
1455 syntax: "BDAT chunk-size [LAST]",
1456 }),
1457 ),
1458 (
1459 "BDAT LAST",
1460 Err(Error::SyntaxError {
1461 syntax: "BDAT chunk-size [LAST]",
1462 }),
1463 ),
1464 (
1466 "AUTH GSSAPI",
1467 Ok(Request::Auth {
1468 mechanism: AUTH_GSSAPI,
1469 initial_response: "".into(),
1470 }),
1471 ),
1472 (
1473 "AUTH ECDSA-NIST256P-CHALLENGE =",
1474 Ok(Request::Auth {
1475 mechanism: AUTH_ECDSA_NIST256P_CHALLENGE,
1476 initial_response: "=".into(),
1477 }),
1478 ),
1479 (
1480 "AUTH SCRAM-SHA-256-PLUS base64_goes_here",
1481 Ok(Request::Auth {
1482 mechanism: AUTH_SCRAM_SHA_256_PLUS,
1483 initial_response: "base64_goes_here".into(),
1484 }),
1485 ),
1486 (
1487 "AUTH ECDSA-NIST256P-CHALLENGE100 abcde",
1488 Ok(Request::Auth {
1489 mechanism: 0,
1490 initial_response: "abcde".into(),
1491 }),
1492 ),
1493 (
1494 "AUTH",
1495 Err(Error::SyntaxError {
1496 syntax: "AUTH mechanism [initial-response]",
1497 }),
1498 ),
1499 (
1501 "ETRN Hello",
1502 Ok(Request::Etrn {
1503 name: "Hello".into(),
1504 }),
1505 ),
1506 (
1507 "ETRN \"Hello\\\" Wo\\\\rld\"",
1508 Ok(Request::Etrn {
1509 name: "Hello\" Wo\\rld".into(),
1510 }),
1511 ),
1512 (
1513 "ETRN \"\"",
1514 Err(Error::SyntaxError {
1515 syntax: "ETRN name",
1516 }),
1517 ),
1518 (
1519 "ETRN",
1520 Err(Error::SyntaxError {
1521 syntax: "ETRN name",
1522 }),
1523 ),
1524 (
1526 "ATRN example.org",
1527 Ok(Request::Atrn {
1528 domains: vec!["example.org".into()],
1529 }),
1530 ),
1531 (
1532 "ATRN example.org,example.com,example.net",
1533 Ok(Request::Atrn {
1534 domains: vec![
1535 "example.org".into(),
1536 "example.com".into(),
1537 "example.net".into(),
1538 ],
1539 }),
1540 ),
1541 (
1542 "ATRN example.org, example.com, example.net",
1543 Ok(Request::Atrn {
1544 domains: vec![
1545 "example.org".into(),
1546 "example.com".into(),
1547 "example.net".into(),
1548 ],
1549 }),
1550 ),
1551 (
1552 "ATRN",
1553 Err(Error::SyntaxError {
1554 syntax: "ATRN domain[,domain]",
1555 }),
1556 ),
1557 (
1559 concat!(
1560 "BURL imap://harry@gryffindor.example.com/outbox",
1561 ";uidvalidity=1078863300/;uid=25;urlauth=submit+harry",
1562 ":internal:91354a473744909de610943775f92038 LAST"
1563 ),
1564 Ok(Request::Burl {
1565 uri: concat!(
1566 "imap://harry@gryffindor.example.com/outbox",
1567 ";uidvalidity=1078863300/;uid=25;urlauth=submit+harry",
1568 ":internal:91354a473744909de610943775f92038"
1569 )
1570 .into(),
1571 is_last: true,
1572 }),
1573 ),
1574 (
1575 "BURL imap:://test.example.org",
1576 Ok(Request::Burl {
1577 uri: "imap:://test.example.org".into(),
1578 is_last: false,
1579 }),
1580 ),
1581 (
1582 "BURL",
1583 Err(Error::SyntaxError {
1584 syntax: "BURL absolute-uri [LAST]",
1585 }),
1586 ),
1587 (
1589 "MAIL FROM:<JQP@bar.com>",
1590 Ok(Request::Mail {
1591 from: "JQP@bar.com".into(),
1592 }),
1593 ),
1594 (
1595 "MAIL FROM:<@a,@b:user@d>",
1596 Ok(Request::Mail {
1597 from: "user@d".into(),
1598 }),
1599 ),
1600 (
1601 "MAIL FROM:<\"@a,@b:<user>\"@d>",
1602 Ok(Request::Mail {
1603 from: "@a,@b:<user>@d".into(),
1604 }),
1605 ),
1606 (
1607 "MAIL FROM: <\" hi there! \"@d>",
1608 Ok(Request::Mail {
1609 from: " hi there! @d".into(),
1610 }),
1611 ),
1612 ("MAIL FROM : <>", Ok(Request::Mail { from: "".into() })),
1613 (
1614 "MAIL FROM : <postmaster>",
1615 Ok(Request::Mail { from: "".into() }),
1616 ),
1617 ("MAIL FROM : < >", Ok(Request::Mail { from: "".into() })),
1618 (
1619 "MAIL FROM:<hi.there@valid.org>",
1620 Ok(Request::Mail {
1621 from: "hi.there@valid.org".into(),
1622 }),
1623 ),
1624 ("MAIL FROM:<@invalid>", Err(Error::InvalidSenderAddress)),
1625 (
1626 "MAIL FROM:<hi@@invalid.org>",
1627 Err(Error::InvalidSenderAddress),
1628 ),
1629 (
1630 "MAIL FROM:<hi..there@invalid.org>",
1631 Err(Error::InvalidSenderAddress),
1632 ),
1633 (
1634 "MAIL FROM:<hi.there@invalid..org>",
1635 Err(Error::InvalidSenderAddress),
1636 ),
1637 (
1638 "MAIL FROM:<hi.there@.invalid.org>",
1639 Err(Error::InvalidSenderAddress),
1640 ),
1641 (
1642 "MAIL FROM:<.hi.there@invalid.org>",
1643 Err(Error::InvalidSenderAddress),
1644 ),
1645 ("MAIL FROM:<@>", Err(Error::InvalidSenderAddress)),
1646 ("MAIL FROM:<.@.>", Err(Error::InvalidSenderAddress)),
1647 (
1648 "RCPT TO:<孫子@áéíóú.org>",
1649 Ok(Request::Rcpt {
1650 to: "孫子@áéíóú.org".into(),
1651 }),
1652 ),
1653 (
1655 "RCPT TO:<Jones@XYZ.COM>",
1656 Ok(Request::Rcpt {
1657 to: "Jones@XYZ.COM".into(),
1658 }),
1659 ),
1660 ("RCPT TO:<>", Ok(Request::Rcpt { to: "".into() })),
1661 ("", Err(Error::UnknownCommand)),
1663 ("X-SPECIAL", Err(Error::UnknownCommand)),
1664 ("DATA_", Err(Error::UnknownCommand)),
1665 (
1667 "MAIL FROM:<> HELLO=WORLD",
1668 Err(Error::UnsupportedParameter {
1669 param: "HELLO=WORLD".to_string(),
1670 }),
1671 ),
1672 (
1673 "MAIL FROM:<> VERY_LONG_AND_INVALID=PARAM",
1674 Err(Error::UnsupportedParameter {
1675 param: "VERY_LONG_AND_INVALID=PARAM".to_string(),
1676 }),
1677 ),
1678 (
1679 "MAIL FROM:<> SMTPUTF8=YES",
1680 Err(Error::UnsupportedParameter {
1681 param: "SMTPUTF8=YES".to_string(),
1682 }),
1683 ),
1684 (
1685 "MAIL FROM:<> SMTPUTF8=YES",
1686 Err(Error::UnsupportedParameter {
1687 param: "SMTPUTF8=YES".to_string(),
1688 }),
1689 ),
1690 (
1694 "MAIL FROM:<>>>>>",
1695 Err(Error::SyntaxError {
1696 syntax: "MAIL FROM:<reverse-path> [parameters]",
1697 }),
1698 ),
1699 (
1700 "MAIL FROM:<a@b.com>>>",
1701 Err(Error::SyntaxError {
1702 syntax: "MAIL FROM:<reverse-path> [parameters]",
1703 }),
1704 ),
1705 (
1706 "RCPT TO:<>>>>>",
1707 Err(Error::SyntaxError {
1708 syntax: "RCPT TO:<forward-path> [parameters]",
1709 }),
1710 ),
1711 (
1713 "MAIL FROM:<> SMTPUTF8",
1714 Ok(Request::Mail {
1715 from: MailFrom {
1716 address: "".into(),
1717 flags: MAIL_SMTPUTF8,
1718 ..Default::default()
1719 },
1720 }),
1721 ),
1722 (
1723 "MAIL FROM:<> SMTPUTF8 REQUIRETLS CONPERM",
1724 Ok(Request::Mail {
1725 from: MailFrom {
1726 address: "".into(),
1727 flags: MAIL_SMTPUTF8 | MAIL_REQUIRETLS | MAIL_CONPERM,
1728 ..Default::default()
1729 },
1730 }),
1731 ),
1732 (
1733 "RCPT TO:<> CONNEG",
1734 Ok(Request::Rcpt {
1735 to: RcptTo {
1736 address: "".into(),
1737 flags: RCPT_CONNEG,
1738 ..Default::default()
1739 },
1740 }),
1741 ),
1742 (
1743 "MAIL FROM:<> BODY=BINARYMIME BODY=7BIT BODY=8BITMIME",
1744 Ok(Request::Mail {
1745 from: MailFrom {
1746 address: "".into(),
1747 flags: MAIL_BODY_7BIT | MAIL_BODY_8BITMIME | MAIL_BODY_BINARYMIME,
1748 ..Default::default()
1749 },
1750 }),
1751 ),
1752 (
1753 "MAIL FROM:<> BODY=OTHER",
1754 Err(Error::InvalidParameter { param: "BODY" }),
1755 ),
1756 (
1757 "MAIL FROM:<> SIZE=500000",
1758 Ok(Request::Mail {
1759 from: MailFrom {
1760 address: "".into(),
1761 size: 500000,
1762 ..Default::default()
1763 },
1764 }),
1765 ),
1766 (
1767 "MAIL FROM:<> SIZE=ABC",
1768 Err(Error::InvalidParameter { param: "SIZE" }),
1769 ),
1770 (
1771 "MAIL FROM:<> SIZE=-100",
1772 Err(Error::InvalidParameter { param: "SIZE" }),
1773 ),
1774 (
1775 "MAIL FROM:<> SIZE=",
1776 Err(Error::InvalidParameter { param: "SIZE" }),
1777 ),
1778 (
1779 "MAIL FROM:<> BY=120;R",
1780 Ok(Request::Mail {
1781 from: MailFrom {
1782 address: "".into(),
1783 by: 120,
1784 flags: MAIL_BY_RETURN,
1785 ..Default::default()
1786 },
1787 }),
1788 ),
1789 (
1790 "MAIL FROM:<> BY=0;N",
1791 Ok(Request::Mail {
1792 from: MailFrom {
1793 address: "".into(),
1794 by: 0,
1795 flags: MAIL_BY_NOTIFY,
1796 ..Default::default()
1797 },
1798 }),
1799 ),
1800 (
1801 "MAIL FROM:<> BY=-10;RT",
1802 Ok(Request::Mail {
1803 from: MailFrom {
1804 address: "".into(),
1805 by: -10,
1806 flags: MAIL_BY_RETURN | MAIL_BY_TRACE,
1807 ..Default::default()
1808 },
1809 }),
1810 ),
1811 (
1812 "MAIL FROM:<> BY=+22;NT",
1813 Ok(Request::Mail {
1814 from: MailFrom {
1815 address: "".into(),
1816 by: 22,
1817 flags: MAIL_BY_NOTIFY | MAIL_BY_TRACE,
1818 ..Default::default()
1819 },
1820 }),
1821 ),
1822 (
1823 "MAIL FROM:<> BY=120",
1824 Err(Error::InvalidParameter { param: "BY" }),
1825 ),
1826 (
1827 "MAIL FROM:<> BY=120;T",
1828 Err(Error::InvalidParameter { param: "BY" }),
1829 ),
1830 (
1831 "MAIL FROM:<> BY=120;",
1832 Err(Error::InvalidParameter { param: "BY" }),
1833 ),
1834 (
1835 "MAIL FROM:<> BY=120;0",
1836 Err(Error::InvalidParameter { param: "BY" }),
1837 ),
1838 (
1839 "MAIL FROM:<> BY=120;;",
1840 Err(Error::InvalidParameter { param: "BY" }),
1841 ),
1842 (
1843 "MAIL FROM:<> BY=;",
1844 Err(Error::InvalidParameter { param: "BY" }),
1845 ),
1846 (
1847 "MAIL FROM:<> BY=;R",
1848 Err(Error::InvalidParameter { param: "BY" }),
1849 ),
1850 (
1851 "MAIL FROM:<> BY=",
1852 Err(Error::InvalidParameter { param: "BY" }),
1853 ),
1854 (
1855 "MAIL FROM:<> HOLDUNTIL=12345 HOLDFOR=67890",
1856 Ok(Request::Mail {
1857 from: MailFrom {
1858 address: "".into(),
1859 hold_for: 67890,
1860 hold_until: 12345,
1861 ..Default::default()
1862 },
1863 }),
1864 ),
1865 (
1866 "MAIL FROM:<> HOLDUNTIL=0ABC",
1867 Err(Error::InvalidParameter { param: "HOLDUNTIL" }),
1868 ),
1869 (
1870 "MAIL FROM:<> HOLDUNTIL=",
1871 Err(Error::InvalidParameter { param: "HOLDUNTIL" }),
1872 ),
1873 (
1874 "MAIL FROM:<> HOLDFOR=XYZ",
1875 Err(Error::InvalidParameter { param: "HOLDFOR" }),
1876 ),
1877 (
1878 "MAIL FROM:<> HOLDFOR=",
1879 Err(Error::InvalidParameter { param: "HOLDFOR" }),
1880 ),
1881 (
1882 "RCPT TO:<> NOTIFY=FAILURE",
1883 Ok(Request::Rcpt {
1884 to: RcptTo {
1885 address: "".into(),
1886 flags: RCPT_NOTIFY_FAILURE,
1887 ..Default::default()
1888 },
1889 }),
1890 ),
1891 (
1892 "RCPT TO:<> NOTIFY=FAILURE,DELAY",
1893 Ok(Request::Rcpt {
1894 to: RcptTo {
1895 address: "".into(),
1896 flags: RCPT_NOTIFY_FAILURE | RCPT_NOTIFY_DELAY,
1897 ..Default::default()
1898 },
1899 }),
1900 ),
1901 (
1902 "RCPT TO:<> NOTIFY=SUCCESS,FAILURE,DELAY",
1903 Ok(Request::Rcpt {
1904 to: RcptTo {
1905 address: "".into(),
1906 flags: RCPT_NOTIFY_FAILURE | RCPT_NOTIFY_DELAY | RCPT_NOTIFY_SUCCESS,
1907 ..Default::default()
1908 },
1909 }),
1910 ),
1911 (
1912 "RCPT TO:<> NOTIFY=NEVER",
1913 Ok(Request::Rcpt {
1914 to: RcptTo {
1915 address: "".into(),
1916 flags: RCPT_NOTIFY_NEVER,
1917 ..Default::default()
1918 },
1919 }),
1920 ),
1921 (
1922 "RCPT TO:<> NOTIFY=",
1923 Err(Error::InvalidParameter { param: "NOTIFY" }),
1924 ),
1925 (
1926 "RCPT TO:<> NOTIFY=FAILURE,NEVER",
1927 Err(Error::InvalidParameter { param: "NOTIFY" }),
1928 ),
1929 (
1930 "RCPT TO:<> NOTIFY=CHIMICHANGA",
1931 Err(Error::InvalidParameter { param: "NOTIFY" }),
1932 ),
1933 (
1934 "RCPT TO:<> ORCPT=rfc822;Bob@Example.COM",
1935 Ok(Request::Rcpt {
1936 to: RcptTo {
1937 address: "".into(),
1938 orcpt: Some("Bob@Example.COM".into()),
1939 ..Default::default()
1940 },
1941 }),
1942 ),
1943 (
1944 concat!("RCPT TO:<> ", "ORCPT=rfc822;George+20@Tax-+20ME+20.GOV"),
1945 Ok(Request::Rcpt {
1946 to: RcptTo {
1947 address: "".into(),
1948 orcpt: Some("George @Tax- ME .GOV".into()),
1949 ..Default::default()
1950 },
1951 }),
1952 ),
1953 (
1954 "RCPT TO:<> ORCPT=",
1955 Err(Error::InvalidParameter { param: "ORCPT" }),
1956 ),
1957 (
1958 "RCPT TO:<> ORCPT=;hello@domain.org",
1959 Err(Error::InvalidParameter { param: "ORCPT" }),
1960 ),
1961 (
1962 "RCPT TO:<> ORCPT=rfc822;",
1963 Err(Error::InvalidParameter { param: "ORCPT" }),
1964 ),
1965 (
1966 "RCPT TO:<> ORCPT=;",
1967 Err(Error::InvalidParameter { param: "ORCPT" }),
1968 ),
1969 (
1970 "MAIL FROM:<> RET=HDRS RET=FULL",
1971 Ok(Request::Mail {
1972 from: MailFrom {
1973 address: "".into(),
1974 flags: MAIL_RET_FULL | MAIL_RET_HDRS,
1975 ..Default::default()
1976 },
1977 }),
1978 ),
1979 (
1980 "MAIL FROM:<> RET=",
1981 Err(Error::InvalidParameter { param: "RET" }),
1982 ),
1983 (
1984 "MAIL FROM:<> RET=ENCHILADA",
1985 Err(Error::InvalidParameter { param: "RET" }),
1986 ),
1987 (
1988 "MAIL FROM:<> ENVID=QQ314159",
1989 Ok(Request::Mail {
1990 from: MailFrom {
1991 address: "".into(),
1992 env_id: Some("QQ314159".into()),
1993 ..Default::default()
1994 },
1995 }),
1996 ),
1997 (
1998 "MAIL FROM:<> ENVID=hi+20there",
1999 Ok(Request::Mail {
2000 from: MailFrom {
2001 address: "".into(),
2002 env_id: Some("hi there".into()),
2003 ..Default::default()
2004 },
2005 }),
2006 ),
2007 (
2008 "MAIL FROM:<> ENVID=",
2009 Err(Error::InvalidParameter { param: "ENVID" }),
2010 ),
2011 (
2012 "MAIL FROM:<> SOLICIT=org.example:ADV:ADLT",
2013 Ok(Request::Mail {
2014 from: MailFrom {
2015 address: "".into(),
2016 solicit: Some("org.example:ADV:ADLT".into()),
2017 ..Default::default()
2018 },
2019 }),
2020 ),
2021 (
2022 concat!(
2023 "MAIL FROM:<> ",
2024 " SOLICIT=net.example:ADV,org.example:ADV:ADLT"
2025 ),
2026 Ok(Request::Mail {
2027 from: MailFrom {
2028 address: "".into(),
2029 solicit: Some("net.example:ADV,org.example:ADV:ADLT".into()),
2030 ..Default::default()
2031 },
2032 }),
2033 ),
2034 (
2035 "MAIL FROM:<> SOLICIT=",
2036 Err(Error::InvalidParameter { param: "SOLICIT" }),
2037 ),
2038 (
2039 "MAIL FROM:<> TRANSID=<12345@claremont.edu>",
2040 Ok(Request::Mail {
2041 from: MailFrom {
2042 address: "".into(),
2043 trans_id: Some("12345@claremont.edu".into()),
2044 ..Default::default()
2045 },
2046 }),
2047 ),
2048 (
2049 "MAIL FROM:<> TRANSID=",
2050 Err(Error::InvalidParameter { param: "TRANSID" }),
2051 ),
2052 (
2053 "MAIL FROM:<> MTRK=my-ceritifier",
2054 Ok(Request::Mail {
2055 from: MailFrom {
2056 address: "".into(),
2057 mtrk: Mtrk {
2058 certifier: "my-ceritifier".into(),
2059 timeout: 0,
2060 }
2061 .into(),
2062 ..Default::default()
2063 },
2064 }),
2065 ),
2066 (
2067 "MAIL FROM:<> MTRK=other-certifier:1234",
2068 Ok(Request::Mail {
2069 from: MailFrom {
2070 address: "".into(),
2071 mtrk: Mtrk {
2072 certifier: "other-certifier".into(),
2073 timeout: 1234,
2074 }
2075 .into(),
2076 ..Default::default()
2077 },
2078 }),
2079 ),
2080 (
2081 "MAIL FROM:<> MTRK=",
2082 Err(Error::InvalidParameter { param: "MTRK" }),
2083 ),
2084 (
2085 "MAIL FROM:<> MTRK=:",
2086 Err(Error::InvalidParameter { param: "MTRK" }),
2087 ),
2088 (
2089 "MAIL FROM:<> MTRK=:998",
2090 Err(Error::InvalidParameter { param: "MTRK" }),
2091 ),
2092 (
2093 "MAIL FROM:<> MTRK=abc:",
2094 Err(Error::InvalidParameter { param: "MTRK" }),
2095 ),
2096 (
2097 "MAIL FROM:<> MTRK=abc:abc",
2098 Err(Error::InvalidParameter { param: "MTRK" }),
2099 ),
2100 (
2101 "MAIL FROM:<> AUTH=<>",
2102 Ok(Request::Mail {
2103 from: MailFrom {
2104 address: "".into(),
2105 auth: Some("<>".into()),
2106 ..Default::default()
2107 },
2108 }),
2109 ),
2110 (
2111 "MAIL FROM:<> AUTH=e+3Dmc2@example.com",
2112 Ok(Request::Mail {
2113 from: MailFrom {
2114 address: "".into(),
2115 auth: Some("e=mc2@example.com".into()),
2116 ..Default::default()
2117 },
2118 }),
2119 ),
2120 (
2121 "MAIL FROM:<> AUTH=",
2122 Err(Error::InvalidParameter { param: "AUTH" }),
2123 ),
2124 (
2125 "MAIL FROM:<> MT-PRIORITY=3",
2126 Ok(Request::Mail {
2127 from: MailFrom {
2128 address: "".into(),
2129 mt_priority: 3,
2130 ..Default::default()
2131 },
2132 }),
2133 ),
2134 (
2135 "MAIL FROM:<> MT-PRIORITY=-6",
2136 Ok(Request::Mail {
2137 from: MailFrom {
2138 address: "".into(),
2139 mt_priority: -6,
2140 ..Default::default()
2141 },
2142 }),
2143 ),
2144 (
2145 "MAIL FROM:<> MT-PRIORITY=",
2146 Err(Error::InvalidParameter {
2147 param: "MT-PRIORITY",
2148 }),
2149 ),
2150 (
2151 "MAIL FROM:<> MT-PRIORITY=ab",
2152 Err(Error::InvalidParameter {
2153 param: "MT-PRIORITY",
2154 }),
2155 ),
2156 (
2157 "MAIL FROM:<> MT-PRIORITY=-",
2158 Err(Error::InvalidParameter {
2159 param: "MT-PRIORITY",
2160 }),
2161 ),
2162 (
2163 "RCPT TO:<> RRVS=2014-04-03T23:01:00Z",
2164 Ok(Request::Rcpt {
2165 to: RcptTo {
2166 address: "".into(),
2167 rrvs: 1396566060,
2168 flags: RCPT_RRVS_REJECT,
2169 ..Default::default()
2170 },
2171 }),
2172 ),
2173 (
2174 "RCPT TO:<> RRVS=1997-11-24T14:22:01-08:00;C",
2175 Ok(Request::Rcpt {
2176 to: RcptTo {
2177 address: "".into(),
2178 rrvs: 880410121,
2179 flags: RCPT_RRVS_CONTINUE,
2180 ..Default::default()
2181 },
2182 }),
2183 ),
2184 (
2185 "RCPT TO:<> RRVS=2003-07-01T10:52:37+02:00;R",
2186 Ok(Request::Rcpt {
2187 to: RcptTo {
2188 address: "".into(),
2189 rrvs: 1057049557,
2190 flags: RCPT_RRVS_REJECT,
2191 ..Default::default()
2192 },
2193 }),
2194 ),
2195 (
2196 "RCPT TO:<> RRVS=",
2197 Err(Error::InvalidParameter { param: "RRVS" }),
2198 ),
2199 (
2200 "RCPT TO:<> RRVS=2022-01-02",
2201 Err(Error::InvalidParameter { param: "RRVS" }),
2202 ),
2203 (
2204 "RCPT TO:<> RRVS=2022-01-02T01:01:01",
2205 Err(Error::InvalidParameter { param: "RRVS" }),
2206 ),
2207 (
2208 "RCPT TO:<> RRVS=2022-01-02T01:01:01ZZ",
2209 Err(Error::InvalidParameter { param: "RRVS" }),
2210 ),
2211 (
2212 "RCPT TO:<> RRVS=ABC",
2213 Err(Error::InvalidParameter { param: "RRVS" }),
2214 ),
2215 (
2216 r#"MAIL FROM:<foo@bar"aa\\" hello world".com>"#,
2217 Err(Error::SyntaxError {
2218 syntax: "MAIL FROM:<reverse-path> [parameters]",
2219 }),
2220 ),
2221 (
2222 r#"MAIL FROM:<foo@bar"aa\" hello world".com>"#,
2223 Ok(Request::Mail {
2224 from: r#"foo@baraa" hello world.com"#.into(),
2225 }),
2226 ),
2227 ] {
2228 let (request, parsed_request): (&str, Result<Request<Cow<'_, str>>, Error>) = item;
2229
2230 for extra in ["\n", "\r\n", " \n", " \r\n"] {
2231 let request = format!("{request}{extra}");
2232 assert_eq!(
2233 parsed_request,
2234 Request::parse(&mut request.as_bytes().iter()),
2235 "failed for {request:?}"
2236 );
2237 }
2238 }
2239 }
2240
2241 #[test]
2242 fn request_name() {
2243 assert_eq!(Request::<String>::Quit.name(), "QUIT");
2244 assert_eq!(Request::<String>::Data.name(), "DATA");
2245 assert_eq!(Request::<String>::Rset.name(), "RSET");
2246 assert_eq!(Request::<String>::StartTls.name(), "STARTTLS");
2247 assert_eq!(
2248 Request::Ehlo::<Cow<'_, str>> {
2249 host: "test.com".into()
2250 }
2251 .name(),
2252 "EHLO"
2253 );
2254 assert_eq!(
2255 Request::Helo::<Cow<'_, str>> {
2256 host: "test.com".into()
2257 }
2258 .name(),
2259 "HELO"
2260 );
2261 assert_eq!(
2262 Request::Mail {
2263 from: MailFrom::<Cow<'_, str>>::default()
2264 }
2265 .name(),
2266 "MAIL"
2267 );
2268 assert_eq!(
2269 Request::Rcpt {
2270 to: RcptTo::<Cow<'_, str>>::default()
2271 }
2272 .name(),
2273 "RCPT"
2274 );
2275 }
2276
2277 impl<'a> From<&'a str> for MailFrom<Cow<'a, str>> {
2278 fn from(value: &'a str) -> Self {
2279 Self {
2280 address: value.into(),
2281 ..Default::default()
2282 }
2283 }
2284 }
2285
2286 impl<'a> From<&'a str> for RcptTo<Cow<'a, str>> {
2287 fn from(value: &'a str) -> Self {
2288 Self {
2289 address: value.into(),
2290 ..Default::default()
2291 }
2292 }
2293 }
2294}