1use std::str::from_utf8;
2
3use abnf_core::streaming::{is_ALPHA, is_VCHAR, SP};
4use nom::{
5 branch::alt,
6 bytes::streaming::{tag, tag_no_case, take_while, take_while1, take_while_m_n},
7 character::{
8 is_alphanumeric,
9 streaming::{digit1, line_ending},
10 },
11 combinator::{map_res, opt, recognize},
12 multi::many0,
13 sequence::{preceded, terminated, tuple},
14 IResult,
15};
16
17use crate::{
18 parse::{command::*, response::*},
19 types::{
20 command::Command,
21 response::{
22 Capability, DropListing, Greeting, LanguageListing, MultiLine, Response, ScanListing,
23 SingleLine, UniqueIdListing,
24 },
25 },
26};
27
28mod command;
29mod response;
30
31pub fn greeting(input: &[u8]) -> IResult<&[u8], Greeting> {
33 let mut parser = tuple((
40 tag_no_case("+OK"),
41 opt(preceded(SP, resp_code)),
42 opt(preceded(
43 SP,
44 tuple((
45 map_res(take_while(is_gchar), from_utf8),
46 opt(timestamp),
47 map_res(take_while(is_gchar), from_utf8),
48 )),
49 )),
50 line_ending,
51 ));
52
53 let (rem, (_, maybe_code, maybe_body, _)) = parser(input)?;
54
55 let code = maybe_code
56 .unwrap_or_default()
57 .into_iter()
58 .map(|lvl| lvl.to_owned())
59 .collect();
60
61 let res = match maybe_body {
62 Some((comment1, maybe_timestamp, comment2)) => {
63 let timestamp = maybe_timestamp.map(ToOwned::to_owned);
64
65 let comment = match timestamp.as_ref() {
66 Some(_) => format!("{}<>{}", comment1, comment2),
67 None => format!("{}{}", comment1, comment2),
68 };
69
70 Greeting {
71 code,
72 comment,
73 timestamp,
74 }
75 }
76 None => Greeting {
77 code,
78 comment: "".into(),
79 timestamp: None,
80 },
81 };
82
83 Ok((rem, res))
84}
85
86pub fn command(input: &[u8]) -> IResult<&[u8], Command> {
90 terminated(
91 alt((
92 user, pass, apop, stls, capa, quit, stat, list, retr, dele, noop, rset, top, uidl, auth, utf8, lang, )),
97 line_ending,
98 )(input)
99}
100
101pub fn response_user(input: &[u8]) -> IResult<&[u8], Response<SingleLine, SingleLine>> {
103 single_line(input, head, false)
104}
105
106pub fn response_pass(input: &[u8]) -> IResult<&[u8], Response<SingleLine, SingleLine>> {
108 single_line(input, head, false)
109}
110
111pub fn response_stat(input: &[u8]) -> IResult<&[u8], Response<DropListing, SingleLine>> {
113 single_line(input, drop_listing, true)
114}
115
116pub fn response_list_all(
118 input: &[u8],
119) -> IResult<&[u8], Response<MultiLine<ScanListing>, SingleLine>> {
120 multi_line(input, scan_listing)
121}
122
123pub fn response_list(input: &[u8]) -> IResult<&[u8], Response<ScanListing, SingleLine>> {
125 single_line(input, scan_listing, true)
126}
127
128pub fn response_retr(input: &[u8]) -> IResult<&[u8], Response<MultiLine<String>, SingleLine>> {
130 multi_line(input, dot_stuffed)
131}
132
133pub fn response_dele(input: &[u8]) -> IResult<&[u8], Response<SingleLine, SingleLine>> {
135 single_line(input, head, false)
136}
137
138pub fn response_noop(input: &[u8]) -> IResult<&[u8], Response<SingleLine, SingleLine>> {
140 single_line(input, head, false)
141}
142
143pub fn response_rset(input: &[u8]) -> IResult<&[u8], Response<SingleLine, SingleLine>> {
145 single_line(input, head, false)
146}
147
148pub fn response_quit(input: &[u8]) -> IResult<&[u8], Response<SingleLine, SingleLine>> {
150 single_line(input, head, false)
151}
152
153pub fn response_apop(input: &[u8]) -> IResult<&[u8], Response<SingleLine, SingleLine>> {
155 single_line(input, head, false)
156}
157
158pub fn response_top(input: &[u8]) -> IResult<&[u8], Response<MultiLine<String>, SingleLine>> {
160 multi_line(input, dot_stuffed)
161}
162
163pub fn response_uidl_all(
165 input: &[u8],
166) -> IResult<&[u8], Response<MultiLine<UniqueIdListing>, SingleLine>> {
167 multi_line(input, unique_id_listing)
168}
169
170pub fn response_uidl(input: &[u8]) -> IResult<&[u8], Response<UniqueIdListing, SingleLine>> {
172 single_line(input, unique_id_listing, true)
173}
174
175pub fn response_capa(input: &[u8]) -> IResult<&[u8], Response<MultiLine<Capability>, SingleLine>> {
177 multi_line(input, capability)
179}
180
181pub fn response_stls(input: &[u8]) -> IResult<&[u8], Response<SingleLine, SingleLine>> {
183 single_line(input, head, false)
184}
185
186pub fn response_auth_all(input: &[u8]) -> IResult<&[u8], Response<MultiLine<String>, SingleLine>> {
190 multi_line(input, dot_stuffed)
191}
192
193pub fn response_utf8(input: &[u8]) -> IResult<&[u8], Response<SingleLine, SingleLine>> {
197 single_line(input, head, false)
198}
199
200pub fn response_lang_all(
202 input: &[u8],
203) -> IResult<&[u8], Response<MultiLine<LanguageListing>, SingleLine>> {
204 multi_line(input, language_listing)
205}
206
207pub fn response_lang(input: &[u8]) -> IResult<&[u8], Response<SingleLine, SingleLine>> {
209 single_line(input, head, false)
210}
211
212pub(crate) fn number(input: &[u8]) -> IResult<&[u8], u32> {
215 map_res(map_res(digit1, from_utf8), str::parse::<u32>)(input)
216}
217
218pub(crate) fn language(input: &[u8]) -> IResult<&[u8], &str> {
226 map_res(
227 recognize(tuple((
228 take_while_m_n(1, 8, is_ALPHA),
229 many0(tuple((tag(b"-"), take_while_m_n(1, 8, is_alphanumeric)))),
230 ))),
231 from_utf8,
232 )(input)
233}
234
235pub(crate) fn param(input: &[u8]) -> IResult<&[u8], &str> {
243 map_res(take_while1(is_VCHAR), from_utf8)(input)
244}
245
246#[cfg(test)]
251mod test {
252 use super::*;
253
254 #[test]
255 fn test_greeting() {
256 let tests: &[(&[u8], Greeting)] = &[
257 (
258 b"+OK\r\n",
259 Greeting {
260 code: vec![],
261 comment: "".into(),
262 timestamp: None,
263 },
264 ),
265 (
266 b"+OK \r\n",
267 Greeting {
268 code: vec![],
269 comment: "".into(),
270 timestamp: None,
271 },
272 ),
273 (
274 b"+OK A\r\n",
275 Greeting {
276 code: vec![],
277 comment: "A".into(),
278 timestamp: None,
279 },
280 ),
281 (
282 b"+OK Z\r\n",
283 Greeting {
284 code: vec![],
285 comment: "Z".into(),
286 timestamp: None,
287 },
288 ),
289 (
290 b"+ok Hello World!\r\n",
291 Greeting {
292 code: vec![],
293 comment: "Hello World!".into(),
294 timestamp: None,
295 },
296 ),
297 (
298 b"+ok Hello <123> World!\r\n",
299 Greeting {
300 code: vec![],
301 comment: "Hello <> World!".into(),
302 timestamp: Some("123".into()),
303 },
304 ),
305 (
306 b"+ok [a] Hello World!\r\n",
307 Greeting {
308 code: vec!["a".into()],
309 comment: "Hello World!".into(),
310 timestamp: None,
311 },
312 ),
313 (
314 b"+ok [a] Hello <123> World!\r\n",
315 Greeting {
316 code: vec!["a".into()],
317 comment: "Hello <> World!".into(),
318 timestamp: Some("123".into()),
319 },
320 ),
321 ];
322
323 for (test, expected) in tests {
324 let (rem, got) = greeting(test).unwrap();
325 assert!(rem.is_empty());
326 assert_eq!(*expected, got);
327 }
328 }
329
330 #[test]
331 fn test_command() {
332 let commands = "\
334QUIT
335STAT
336LIST
337LIST 2
338LIST 3
339RETR 1
340DELE 1
341DELE 2
342NOOP
343RSET
344QUIT
345QUIT
346TOP 1 10
347TOP 100 3
348UIDL
349UIDL 2
350UIDL 3
351USER frated
352USER mrose
353USER mrose
354PASS secret
355USER mrose
356PASS secret
357APOP mrose c4c9334bac560ecc979e58001b3e22fb
358APOP mrose c4c9334bac560ecc979e58001b3e22fb
359STAT
360LIST
361RETR 1
362DELE 1
363RETR 2
364DELE 2
365QUIT
366LANG MUL
367LANG
368LANG
369LANG es
370LANG uga
371LANG sv
372LANG *
373UTF8
374";
375
376 for cmd in commands.lines() {
377 let cmd = cmd.to_string() + "\r\n";
378 print!("C: {}", cmd);
379 let (rem, cmd) = super::command(cmd.as_bytes()).unwrap();
380 assert!(rem.is_empty());
381 println!("{:?}\n", cmd);
382 }
383 }
384
385 #[test]
386 fn test_response() {
387 println!(
388 "{:#?}",
389 response_quit(b"+OK dewey POP3 server signing off\r\n")
390 .unwrap()
391 .1
392 );
393 println!("{:#?}", response_stat(b"+OK 2 320\r\n").unwrap().1);
394 println!(
395 "{:#?}",
396 response_list_all(
397 b"+OK 2 messages (320 octets)
3981 120
3992 200
400.
401",
402 )
403 .unwrap()
404 .1
405 );
406 println!("{:#?}", response_list(b"+OK 2 200\r\n").unwrap().1);
407 println!(
408 "{:#?}",
409 response_list(b"-ERR no such message, only 2 messages in maildrop\r\n")
410 .unwrap()
411 .1
412 );
413 println!(
414 "{:#?}",
415 response_retr(
416 b"+OK 120 octets
417<the POP3 server sends the entire message here>
418.
419",
420 )
421 .unwrap()
422 .1
423 );
424 println!(
425 "{:#?}",
426 response_dele(b"+OK message 1 deleted\r\n").unwrap().1
427 );
428 println!(
429 "{:#?}",
430 response_dele(b"-ERR message 2 already deleted\r\n")
431 .unwrap()
432 .1
433 );
434 println!("{:#?}", response_noop(b"+OK\r\n").unwrap().1);
435 println!(
436 "{:#?}",
437 response_rset(b"+OK maildrop has 2 messages (320 octets)\r\n")
438 .unwrap()
439 .1
440 );
441 println!(
442 "{:#?}",
443 response_quit(b"+OK dewey POP3 server signing off (maildrop empty)\r\n")
444 .unwrap()
445 .1
446 );
447 println!(
448 "{:#?}",
449 response_quit(b"+OK dewey POP3 server signing off (2 messages left)\r\n")
450 .unwrap()
451 .1
452 );
453 println!("{:#?}", response_top(b"+OK
454<the POP3 server sends the headers of the message, a blank line, and the first 10 lines of the body of the message>
455.
456").unwrap().1);
457 println!(
458 "{:#?}",
459 response_top(b"-ERR no such message\r\n").unwrap().1
460 );
461 println!(
462 "{:#?}",
463 response_uidl_all(
464 b"+OK
4651 whqtswO00WBw418f9t5JxYwZ
4662 QhdPYR:00WBw1Ph7x7
467.
468",
469 )
470 .unwrap()
471 .1
472 );
473 println!(
474 "{:#?}",
475 response_uidl(b"+OK 2 QhdPYR:00WBw1Ph7x7\r\n").unwrap().1
476 );
477 println!(
478 "{:#?}",
479 response_uidl(b"-ERR no such message, only 2 messages in maildrop\r\n")
480 .unwrap()
481 .1
482 );
483 println!(
484 "{:#?}",
485 response_user(b"-ERR sorry, no mailbox for frated here\r\n")
486 .unwrap()
487 .1
488 );
489 println!(
490 "{:#?}",
491 response_user(b"+OK mrose is a real hoopy frood\r\n")
492 .unwrap()
493 .1
494 );
495 println!(
496 "{:#?}",
497 response_pass(b"-ERR maildrop already locked\r\n")
498 .unwrap()
499 .1
500 );
501 println!(
502 "{:#?}",
503 response_pass(b"+OK mrose's maildrop has 2 messages (320 octets)\r\n")
504 .unwrap()
505 .1
506 );
507 println!(
508 "{:#?}",
509 response_apop(b"+OK maildrop has 1 message (369 octets)\r\n")
510 .unwrap()
511 .1
512 );
513
514 println!(
515 "{:#?}",
516 response_lang(b"-ERR invalid language MUL\r\n").unwrap().1
517 );
518 println!(
519 "{:#?}",
520 response_lang_all(
521 b"+OK Language listing follows:
522en English
523en-boont English Boontling dialect
524de Deutsch
525it Italiano
526es Espanol
527sv Svenska
528i-default Default language
529.
530"
531 )
532 .unwrap()
533 .1
534 );
535 println!(
536 "{:#?}",
537 response_lang_all(b"-ERR Server is unable to list languages\r\n")
538 .unwrap()
539 .1
540 );
541 println!(
542 "{:#?}",
543 response_lang(b"+OK es Idioma cambiado\r\n").unwrap().1
544 );
545 println!(
546 "{:#?}",
547 response_lang(b"-ERR es Idioma <<UGA>> no es conocido\r\n")
548 .unwrap()
549 .1
550 );
551 println!(
552 "{:#?}",
553 response_lang(b"+OK sv Kommandot \"LANG\" lyckades\r\n")
554 .unwrap()
555 .1
556 );
557 println!(
558 "{:#?}",
559 response_lang(b"+OK es Idioma cambiado\r\n").unwrap().1
560 );
561 }
562
563 #[test]
564 fn test_example_session() {
565 let client = b"\
566APOP mrose c4c9334bac560ecc979e58001b3e22fb
567STAT
568LIST
569RETR 1
570DELE 1
571RETR 2
572DELE 2
573QUIT
574";
575
576 let server = b"\
577+OK POP3 server ready <1896.697170952@dbc.mtview.ca.us>
578+OK mrose's maildrop has 2 messages (320 octets)
579+OK 2 320
580+OK 2 messages (320 octets)
5811 120
5822 200
583.
584+OK 120 octets
585<the POP3 server sends message 1>
586.
587+OK message 1 deleted
588+OK 200 octets
589<the POP3 server sends message 2>
590.
591+OK message 2 deleted
592+OK dewey POP3 server signing off (maildrop empty)
593";
594
595 let mut rem_client = client.as_ref();
596 let mut rem_server = server.as_ref();
597
598 let (rem, greeting) = greeting(rem_server).unwrap();
599 println!("{:#?}", greeting);
600 rem_server = rem;
601
602 while !rem_client.is_empty() {
603 let (rem, cmd) = command(rem_client).unwrap();
604 println!("{:#?}", cmd);
605 rem_client = rem;
606
607 use Command::*;
608 match cmd {
609 User(_) => {
610 let (rem, resp) = response_user(rem_server).unwrap();
611 println!("{:#?}", resp);
612 rem_server = rem;
613 }
614 Pass(_) => {
615 let (rem, resp) = response_pass(rem_server).unwrap();
616 println!("{:#?}", resp);
617 rem_server = rem;
618 }
619 Stat => {
620 let (rem, resp) = response_stat(rem_server).unwrap();
621 println!("{:#?}", resp);
622 rem_server = rem;
623 }
624 ListAll => {
625 let (rem, resp) = response_list_all(rem_server).unwrap();
626 println!("{:#?}", resp);
627 rem_server = rem;
628 }
629 List { .. } => {
630 let (rem, resp) = response_list(rem_server).unwrap();
631 println!("{:#?}", resp);
632 rem_server = rem;
633 }
634 Retr { .. } => {
635 let (rem, resp) = response_retr(rem_server).unwrap();
636 println!("{:#?}", resp);
637 rem_server = rem;
638 }
639 Dele { .. } => {
640 let (rem, resp) = response_dele(rem_server).unwrap();
641 println!("{:#?}", resp);
642 rem_server = rem;
643 }
644 Noop => {
645 let (rem, resp) = response_noop(rem_server).unwrap();
646 println!("{:#?}", resp);
647 rem_server = rem;
648 }
649 Rset => {
650 let (rem, resp) = response_rset(rem_server).unwrap();
651 println!("{:#?}", resp);
652 rem_server = rem;
653 }
654 Quit => {
655 let (rem, resp) = response_quit(rem_server).unwrap();
656 println!("{:#?}", resp);
657 rem_server = rem;
658 }
659 Apop { .. } => {
660 let (rem, resp) = response_apop(rem_server).unwrap();
661 println!("{:#?}", resp);
662 rem_server = rem;
663 }
664 Top { .. } => {
665 let (rem, resp) = response_top(rem_server).unwrap();
666 println!("{:#?}", resp);
667 rem_server = rem;
668 }
669 UidlAll => {
670 let (rem, resp) = response_uidl_all(rem_server).unwrap();
671 println!("{:#?}", resp);
672 rem_server = rem;
673 }
674 Uidl { .. } => {
675 let (rem, resp) = response_uidl(rem_server).unwrap();
676 println!("{:#?}", resp);
677 rem_server = rem;
678 }
679 Capa => {
680 let (rem, resp) = response_user(rem_server).unwrap();
681 println!("{:#?}", resp);
682 rem_server = rem;
683 }
684 Stls => {
685 let (rem, resp) = response_stls(rem_server).unwrap();
686 println!("{:#?}", resp);
687 rem_server = rem;
688 }
689 AuthAll => {
690 let (rem, resp) = response_auth_all(rem_server).unwrap();
691 println!("{:#?}", resp);
692 rem_server = rem;
693 }
694 Auth { .. } => {
695 unimplemented!()
696 }
697 Utf8 => {
698 let (rem, resp) = response_utf8(rem_server).unwrap();
699 println!("{:#?}", resp);
700 rem_server = rem;
701 }
702 LangAll => {
703 let (rem, resp) = response_lang_all(rem_server).unwrap();
704 println!("{:#?}", resp);
705 rem_server = rem;
706 }
707 Lang { .. } => {
708 let (rem, resp) = response_lang(rem_server).unwrap();
709 println!("{:#?}", resp);
710 rem_server = rem;
711 }
712 }
713 }
714 }
715}