1use crate::ast::{
23 ContinueReq, DataResponse, FetchAttribute, Response, ResponseCode, Status, StatusItem,
24 StatusResponse,
25};
26use crate::error::ParseError;
27
28pub const MAX_LITERAL_SIZE: usize = 64 * 1024 * 1024;
32
33pub type IResult<'a, T> = Result<(&'a [u8], T), ParseError>;
35
36pub fn parse_response(input: &[u8]) -> IResult<'_, Response<'_>> {
42 let mut parser = Parser::new(input);
43 let response = parser.parse_response()?;
44 Ok((parser.remaining(), response))
45}
46
47struct Parser<'a> {
52 input: &'a [u8],
53 pos: usize,
54}
55
56impl<'a> Parser<'a> {
57 fn new(input: &'a [u8]) -> Self {
58 Self { input, pos: 0 }
59 }
60
61 fn remaining(&self) -> &'a [u8] {
62 &self.input[self.pos..]
63 }
64
65 fn is_eof(&self) -> bool {
66 self.pos >= self.input.len()
67 }
68
69 fn peek(&self) -> Result<u8, ParseError> {
70 self.input
71 .get(self.pos)
72 .copied()
73 .ok_or(ParseError::Incomplete)
74 }
75
76 fn peek_at(&self, offset: usize) -> Result<u8, ParseError> {
77 let idx = self.pos.checked_add(offset).ok_or(ParseError::Incomplete)?;
78 self.input.get(idx).copied().ok_or(ParseError::Incomplete)
79 }
80
81 fn consume_byte(&mut self, b: u8) -> Result<(), ParseError> {
82 if self.peek()? == b {
83 self.pos += 1;
84 Ok(())
85 } else {
86 Err(ParseError::InvalidChar(self.pos))
87 }
88 }
89
90 fn try_consume_byte(&mut self, b: u8) -> bool {
91 match self.peek() {
92 Ok(c) if c == b => {
93 self.pos += 1;
94 true
95 }
96 _ => false,
97 }
98 }
99
100 fn matches_ci(&self, expected: &[u8]) -> bool {
103 let end = match self.pos.checked_add(expected.len()) {
104 Some(e) if e <= self.input.len() => e,
105 _ => return false,
106 };
107 self.input[self.pos..end]
108 .iter()
109 .zip(expected.iter())
110 .all(|(a, b)| a.eq_ignore_ascii_case(b))
111 }
112
113 fn try_consume_keyword_ci(&mut self, expected: &[u8]) -> bool {
117 if !self.matches_ci(expected) {
118 return false;
119 }
120 let next = self.input.get(self.pos + expected.len()).copied();
121 let is_boundary = match next {
122 None => true,
123 Some(b) => matches!(b, b' ' | b'\r' | b'\n' | b']' | b')'),
124 };
125 if !is_boundary {
126 return false;
127 }
128 self.pos += expected.len();
129 true
130 }
131
132 fn consume_crlf(&mut self) -> Result<(), ParseError> {
133 if self.peek()? != b'\r' {
134 return Err(ParseError::Malformed("expected CR"));
135 }
136 match self.peek_at(1) {
137 Ok(b'\n') => {
138 self.pos += 2;
139 Ok(())
140 }
141 Ok(_) => Err(ParseError::Malformed("expected LF after CR")),
142 Err(_) => Err(ParseError::Incomplete),
143 }
144 }
145
146 fn consume_sp(&mut self) -> Result<(), ParseError> {
147 self.consume_byte(b' ')
148 }
149
150 fn parse_number(&mut self) -> Result<u32, ParseError> {
152 let start = self.pos;
153 while let Ok(c) = self.peek() {
154 if c.is_ascii_digit() {
155 self.pos += 1;
156 } else {
157 break;
158 }
159 }
160 if start == self.pos {
161 if self.is_eof() {
163 return Err(ParseError::Incomplete);
164 }
165 return Err(ParseError::InvalidNumber);
166 }
167 let bytes = &self.input[start..self.pos];
168 let s = match core::str::from_utf8(bytes) {
170 Ok(s) => s,
171 Err(_) => return Err(ParseError::InvalidNumber),
172 };
173 s.parse::<u32>().map_err(|_| ParseError::InvalidNumber)
174 }
175
176 fn parse_atom(&mut self) -> Result<&'a str, ParseError> {
180 let start = self.pos;
181 while let Ok(c) = self.peek() {
182 if is_atom_char(c) {
183 self.pos += 1;
184 } else {
185 break;
186 }
187 }
188 if start == self.pos {
189 if self.is_eof() {
190 return Err(ParseError::Incomplete);
191 }
192 return Err(ParseError::Malformed("expected atom"));
193 }
194 let bytes = &self.input[start..self.pos];
195 core::str::from_utf8(bytes).map_err(|_| ParseError::Other("Invalid UTF-8"))
196 }
197
198 fn parse_tag(&mut self) -> Result<&'a str, ParseError> {
202 let start = self.pos;
203 while let Ok(c) = self.peek() {
204 if c == b' ' || c == b'\r' || c == b'\n' {
205 break;
206 }
207 self.pos += 1;
208 }
209 if start == self.pos {
210 if self.is_eof() {
211 return Err(ParseError::Incomplete);
212 }
213 return Err(ParseError::Malformed("expected tag"));
214 }
215 core::str::from_utf8(&self.input[start..self.pos])
216 .map_err(|_| ParseError::Other("Invalid UTF-8"))
217 }
218
219 fn parse_quoted(&mut self) -> Result<&'a str, ParseError> {
222 self.consume_byte(b'"')?;
223 let start = self.pos;
224 loop {
225 let c = self.peek()?;
226 match c {
227 b'"' => {
228 let s = core::str::from_utf8(&self.input[start..self.pos])
229 .map_err(|_| ParseError::Other("Invalid UTF-8"))?;
230 self.pos += 1;
231 return Ok(s);
232 }
233 b'\\' => {
234 self.pos += 1;
236 let _escaped = self.peek()?;
237 self.pos += 1;
238 }
239 b'\r' | b'\n' => {
240 return Err(ParseError::Malformed("CR/LF inside quoted string"));
241 }
242 _ => self.pos += 1,
243 }
244 }
245 }
246
247 fn parse_literal(&mut self) -> Result<&'a [u8], ParseError> {
249 self.consume_byte(b'{')?;
250 let n = self.parse_number()? as usize;
251 self.consume_byte(b'}')?;
252 self.consume_crlf()?;
253 if n > MAX_LITERAL_SIZE {
254 return Err(ParseError::LiteralTooLarge {
255 got: n,
256 max: MAX_LITERAL_SIZE,
257 });
258 }
259 let end = self.pos.checked_add(n).ok_or(ParseError::Incomplete)?;
260 if end > self.input.len() {
261 return Err(ParseError::Incomplete);
262 }
263 let data = &self.input[self.pos..end];
264 self.pos = end;
265 Ok(data)
266 }
267
268 fn parse_astring(&mut self) -> Result<&'a str, ParseError> {
272 match self.peek()? {
273 b'"' => self.parse_quoted(),
274 b'{' => {
275 let bytes = self.parse_literal()?;
276 core::str::from_utf8(bytes).map_err(|_| ParseError::Other("Invalid UTF-8"))
277 }
278 _ => self.parse_atom(),
279 }
280 }
281
282 fn parse_nstring(&mut self) -> Result<Option<&'a str>, ParseError> {
284 match self.peek()? {
285 b'"' => Ok(Some(self.parse_quoted()?)),
286 b'{' => {
287 let bytes = self.parse_literal()?;
288 let s =
289 core::str::from_utf8(bytes).map_err(|_| ParseError::Other("Invalid UTF-8"))?;
290 Ok(Some(s))
291 }
292 _ => {
293 if self.try_consume_keyword_ci(b"NIL") {
294 Ok(None)
295 } else {
296 Err(ParseError::Malformed("expected nstring"))
297 }
298 }
299 }
300 }
301
302 fn parse_paren_atom_list(&mut self) -> Result<Vec<&'a str>, ParseError> {
304 self.consume_byte(b'(')?;
305 let mut items = Vec::new();
306 if self.try_consume_byte(b')') {
307 return Ok(items);
308 }
309 loop {
310 items.push(self.parse_flag_or_atom()?);
311 match self.peek()? {
312 b' ' => {
313 self.pos += 1;
314 }
315 b')' => {
316 self.pos += 1;
317 return Ok(items);
318 }
319 _ => return Err(ParseError::Malformed("expected SP or ) in list")),
320 }
321 }
322 }
323
324 fn parse_flag_or_atom(&mut self) -> Result<&'a str, ParseError> {
327 let start = self.pos;
328 if self.peek()? == b'\\' {
329 self.pos += 1;
330 if self.try_consume_byte(b'*') {
332 return core::str::from_utf8(&self.input[start..self.pos])
333 .map_err(|_| ParseError::Other("Invalid UTF-8"));
334 }
335 }
336 while let Ok(c) = self.peek() {
337 if is_atom_char(c) {
338 self.pos += 1;
339 } else {
340 break;
341 }
342 }
343 if start == self.pos {
344 return Err(ParseError::Malformed("expected flag/atom"));
345 }
346 core::str::from_utf8(&self.input[start..self.pos])
347 .map_err(|_| ParseError::Other("Invalid UTF-8"))
348 }
349
350 fn parse_balanced_parens(&mut self) -> Result<&'a [u8], ParseError> {
355 let start = self.pos;
356 self.consume_byte(b'(')?;
357 let mut depth: u32 = 1;
358 while depth > 0 {
359 match self.peek()? {
360 b'(' => {
361 self.pos += 1;
362 depth = depth
363 .checked_add(1)
364 .ok_or(ParseError::Other("paren depth overflow"))?;
365 }
366 b')' => {
367 self.pos += 1;
368 depth -= 1;
369 }
370 b'"' => {
371 self.parse_quoted()?;
372 }
373 b'{' => {
374 self.parse_literal()?;
375 }
376 _ => self.pos += 1,
377 }
378 }
379 Ok(&self.input[start..self.pos])
380 }
381
382 fn parse_text(&mut self) -> Result<&'a str, ParseError> {
385 let start = self.pos;
386 while let Ok(c) = self.peek() {
387 if c == b'\r' || c == b'\n' {
388 break;
389 }
390 self.pos += 1;
391 }
392 core::str::from_utf8(&self.input[start..self.pos])
393 .map_err(|_| ParseError::Other("Invalid UTF-8"))
394 }
395
396 fn parse_response(&mut self) -> Result<Response<'a>, ParseError> {
401 if self.is_eof() {
402 return Err(ParseError::Incomplete);
403 }
404 match self.peek()? {
405 b'+' => self.parse_continue_req(),
406 b'*' => self.parse_untagged(),
407 _ => self.parse_tagged(),
408 }
409 }
410
411 fn parse_continue_req(&mut self) -> Result<Response<'a>, ParseError> {
412 self.consume_byte(b'+')?;
413 self.consume_sp()?;
414 let (code, text) = self.parse_resp_text()?;
415 self.consume_crlf()?;
416 Ok(Response::Continue(ContinueReq { code, text }))
417 }
418
419 fn parse_untagged(&mut self) -> Result<Response<'a>, ParseError> {
420 self.consume_byte(b'*')?;
421 self.consume_sp()?;
422
423 if let Some(status) = self.try_parse_status_keyword() {
425 let (code, text) = self.parse_resp_text_after_keyword()?;
426 self.consume_crlf()?;
427 return Ok(Response::Status(StatusResponse {
428 tag: None,
429 status,
430 code,
431 text,
432 }));
433 }
434
435 if self.peek()?.is_ascii_digit() {
438 return self.parse_numeric_data_response();
439 }
440
441 self.parse_named_data_response()
442 }
443
444 fn parse_tagged(&mut self) -> Result<Response<'a>, ParseError> {
445 let tag = self.parse_tag()?;
446 self.consume_sp()?;
447 let status = match self.try_parse_status_keyword() {
448 Some(s) => s,
449 None => {
450 let rem = &self.input[self.pos..];
454 if could_be_status_keyword_prefix(rem) {
455 return Err(ParseError::Incomplete);
456 }
457 return Err(ParseError::Malformed("expected tagged status keyword"));
458 }
459 };
460 let (code, text) = self.parse_resp_text_after_keyword()?;
461 self.consume_crlf()?;
462 Ok(Response::Status(StatusResponse {
463 tag: Some(tag),
464 status,
465 code,
466 text,
467 }))
468 }
469
470 fn parse_resp_text_after_keyword(
475 &mut self,
476 ) -> Result<(Option<ResponseCode<'a>>, &'a str), ParseError> {
477 if self.try_consume_byte(b' ') {
478 self.parse_resp_text()
479 } else {
480 Ok((None, ""))
483 }
484 }
485
486 fn try_parse_status_keyword(&mut self) -> Option<Status> {
487 if self.try_consume_keyword_ci(b"PREAUTH") {
491 Some(Status::PreAuth)
492 } else if self.try_consume_keyword_ci(b"OK") {
493 Some(Status::Ok)
494 } else if self.try_consume_keyword_ci(b"NO") {
495 Some(Status::No)
496 } else if self.try_consume_keyword_ci(b"BAD") {
497 Some(Status::Bad)
498 } else if self.try_consume_keyword_ci(b"BYE") {
499 Some(Status::Bye)
500 } else {
501 None
502 }
503 }
504
505 fn parse_resp_text(&mut self) -> Result<(Option<ResponseCode<'a>>, &'a str), ParseError> {
507 let code = if self.try_consume_byte(b'[') {
508 let c = self.parse_resp_text_code()?;
509 self.consume_byte(b']')?;
510 let _ = self.try_consume_byte(b' ');
512 Some(c)
513 } else {
514 None
515 };
516 let text = self.parse_text()?;
517 Ok((code, text))
518 }
519
520 fn parse_resp_text_code(&mut self) -> Result<ResponseCode<'a>, ParseError> {
521 let start = self.pos;
523 while let Ok(c) = self.peek() {
524 if c == b']' || c == b' ' {
525 break;
526 }
527 self.pos += 1;
528 }
529 if start == self.pos {
530 return Err(ParseError::Malformed("empty response code"));
531 }
532 let atom_bytes = &self.input[start..self.pos];
533 let atom =
534 core::str::from_utf8(atom_bytes).map_err(|_| ParseError::Other("Invalid UTF-8"))?;
535
536 let code = match () {
537 _ if atom.eq_ignore_ascii_case("ALERT") => ResponseCode::Alert,
538 _ if atom.eq_ignore_ascii_case("PARSE") => ResponseCode::Parse,
539 _ if atom.eq_ignore_ascii_case("READ-ONLY") => ResponseCode::ReadOnly,
540 _ if atom.eq_ignore_ascii_case("READ-WRITE") => ResponseCode::ReadWrite,
541 _ if atom.eq_ignore_ascii_case("TRYCREATE") => ResponseCode::TryCreate,
542 _ if atom.eq_ignore_ascii_case("UIDNEXT") => {
543 self.consume_sp()?;
544 ResponseCode::UidNext(self.parse_number()?)
545 }
546 _ if atom.eq_ignore_ascii_case("UIDVALIDITY") => {
547 self.consume_sp()?;
548 ResponseCode::UidValidity(self.parse_number()?)
549 }
550 _ if atom.eq_ignore_ascii_case("UNSEEN") => {
551 self.consume_sp()?;
552 ResponseCode::Unseen(self.parse_number()?)
553 }
554 _ if atom.eq_ignore_ascii_case("CAPABILITY") => {
555 let mut caps = Vec::new();
556 while self.try_consume_byte(b' ') {
557 caps.push(self.parse_atom()?);
558 }
559 ResponseCode::Capability(caps)
560 }
561 _ if atom.eq_ignore_ascii_case("PERMANENTFLAGS") => {
562 self.consume_sp()?;
563 ResponseCode::PermanentFlags(self.parse_paren_atom_list()?)
564 }
565 _ if atom.eq_ignore_ascii_case("BADCHARSET") => {
566 let charsets = if self.try_consume_byte(b' ') {
567 self.parse_paren_atom_list()?
568 } else {
569 Vec::new()
570 };
571 ResponseCode::BadCharset(charsets)
572 }
573 _ => {
574 let extra = if self.try_consume_byte(b' ') {
575 let extra_start = self.pos;
576 while let Ok(c) = self.peek() {
577 if c == b']' {
578 break;
579 }
580 self.pos += 1;
581 }
582 let bytes = &self.input[extra_start..self.pos];
583 Some(
584 core::str::from_utf8(bytes)
585 .map_err(|_| ParseError::Other("Invalid UTF-8"))?,
586 )
587 } else {
588 None
589 };
590 ResponseCode::Other(atom, extra)
591 }
592 };
593 Ok(code)
594 }
595
596 fn parse_numeric_data_response(&mut self) -> Result<Response<'a>, ParseError> {
601 let n = self.parse_number()?;
602 self.consume_sp()?;
603 let kind = self.parse_atom()?;
604 let resp = if kind.eq_ignore_ascii_case("EXISTS") {
605 DataResponse::Exists(n)
606 } else if kind.eq_ignore_ascii_case("RECENT") {
607 DataResponse::Recent(n)
608 } else if kind.eq_ignore_ascii_case("EXPUNGE") {
609 DataResponse::Expunge(n)
610 } else if kind.eq_ignore_ascii_case("FETCH") {
611 self.consume_sp()?;
612 let attributes = self.parse_fetch_msg_att()?;
613 DataResponse::Fetch { seq: n, attributes }
614 } else {
615 return self.finish_other_data_with_prefix(n, kind);
617 };
618 self.consume_crlf()?;
619 Ok(Response::Data(resp))
620 }
621
622 fn finish_other_data_with_prefix(
625 &mut self,
626 _n: u32,
627 _kind: &'a str,
628 ) -> Result<Response<'a>, ParseError> {
629 let line_start = self.find_current_line_start();
632 while let Ok(c) = self.peek() {
633 if c == b'\r' {
634 break;
635 }
636 self.pos += 1;
637 }
638 let line = &self.input[line_start..self.pos];
639 self.consume_crlf()?;
640 Ok(Response::Data(DataResponse::Other(line)))
641 }
642
643 fn find_current_line_start(&self) -> usize {
644 let mut i = self.pos;
646 while i > 0 && !(i >= 2 && &self.input[i - 2..i] == b"\r\n") {
647 i -= 1;
648 }
649 i
650 }
651
652 fn parse_named_data_response(&mut self) -> Result<Response<'a>, ParseError> {
653 let line_start = self.pos;
654 let name = self.parse_atom()?;
655
656 let resp = if name.eq_ignore_ascii_case("CAPABILITY") {
657 let mut caps = Vec::new();
658 while self.try_consume_byte(b' ') {
659 caps.push(self.parse_atom()?);
660 }
661 DataResponse::Capability(caps)
662 } else if name.eq_ignore_ascii_case("LIST") {
663 self.consume_sp()?;
664 let (flags, delimiter, name) = self.parse_list_lsub_body()?;
665 DataResponse::List {
666 flags,
667 delimiter,
668 name,
669 }
670 } else if name.eq_ignore_ascii_case("LSUB") {
671 self.consume_sp()?;
672 let (flags, delimiter, name) = self.parse_list_lsub_body()?;
673 DataResponse::Lsub {
674 flags,
675 delimiter,
676 name,
677 }
678 } else if name.eq_ignore_ascii_case("STATUS") {
679 self.consume_sp()?;
680 let mailbox = self.parse_astring()?;
681 self.consume_sp()?;
682 let items = self.parse_status_items()?;
683 DataResponse::Status { mailbox, items }
684 } else if name.eq_ignore_ascii_case("SEARCH") {
685 let mut ids = Vec::new();
686 while self.try_consume_byte(b' ') {
687 ids.push(self.parse_number()?);
688 }
689 DataResponse::Search(ids)
690 } else if name.eq_ignore_ascii_case("FLAGS") {
691 self.consume_sp()?;
692 DataResponse::Flags(self.parse_paren_atom_list()?)
693 } else {
694 while let Ok(c) = self.peek() {
696 if c == b'\r' {
697 break;
698 }
699 self.pos += 1;
700 }
701 let line = &self.input[line_start..self.pos];
702 self.consume_crlf()?;
703 return Ok(Response::Data(DataResponse::Other(line)));
704 };
705
706 self.consume_crlf()?;
707 Ok(Response::Data(resp))
708 }
709
710 fn parse_list_lsub_body(
711 &mut self,
712 ) -> Result<(Vec<&'a str>, Option<&'a str>, &'a str), ParseError> {
713 let flags = self.parse_paren_atom_list()?;
714 self.consume_sp()?;
715 let delimiter = self.parse_nstring()?;
716 self.consume_sp()?;
717 let name = self.parse_astring()?;
718 Ok((flags, delimiter, name))
719 }
720
721 fn parse_status_items(&mut self) -> Result<Vec<StatusItem>, ParseError> {
722 self.consume_byte(b'(')?;
723 let mut items = Vec::new();
724 if self.try_consume_byte(b')') {
725 return Ok(items);
726 }
727 loop {
728 let key = self.parse_atom()?;
729 self.consume_sp()?;
730 let value = self.parse_number()?;
731 items.push(if key.eq_ignore_ascii_case("MESSAGES") {
732 StatusItem::Messages(value)
733 } else if key.eq_ignore_ascii_case("RECENT") {
734 StatusItem::Recent(value)
735 } else if key.eq_ignore_ascii_case("UIDNEXT") {
736 StatusItem::UidNext(value)
737 } else if key.eq_ignore_ascii_case("UIDVALIDITY") {
738 StatusItem::UidValidity(value)
739 } else if key.eq_ignore_ascii_case("UNSEEN") {
740 StatusItem::Unseen(value)
741 } else {
742 StatusItem::Other(value)
743 });
744 match self.peek()? {
745 b' ' => {
746 self.pos += 1;
747 }
748 b')' => {
749 self.pos += 1;
750 return Ok(items);
751 }
752 _ => return Err(ParseError::Malformed("expected SP or ) in STATUS items")),
753 }
754 }
755 }
756
757 fn parse_fetch_msg_att(&mut self) -> Result<Vec<FetchAttribute<'a>>, ParseError> {
762 self.consume_byte(b'(')?;
763 let mut atts = Vec::new();
764 if self.try_consume_byte(b')') {
765 return Ok(atts);
766 }
767 loop {
768 atts.push(self.parse_one_fetch_att()?);
769 match self.peek()? {
770 b' ' => {
771 self.pos += 1;
772 }
773 b')' => {
774 self.pos += 1;
775 return Ok(atts);
776 }
777 _ => return Err(ParseError::Malformed("expected SP or ) in FETCH atts")),
778 }
779 }
780 }
781
782 fn parse_one_fetch_att(&mut self) -> Result<FetchAttribute<'a>, ParseError> {
783 let name = self.parse_fetch_att_name()?;
784
785 if name.eq_ignore_ascii_case("UID") {
786 self.consume_sp()?;
787 return Ok(FetchAttribute::Uid(self.parse_number()?));
788 }
789 if name.eq_ignore_ascii_case("FLAGS") {
790 self.consume_sp()?;
791 return Ok(FetchAttribute::Flags(self.parse_paren_atom_list()?));
792 }
793 if name.eq_ignore_ascii_case("INTERNALDATE") {
794 self.consume_sp()?;
795 let s = self.parse_quoted()?;
796 return Ok(FetchAttribute::InternalDate(s));
797 }
798 if name.eq_ignore_ascii_case("RFC822.SIZE") {
799 self.consume_sp()?;
800 return Ok(FetchAttribute::Rfc822Size(self.parse_number()?));
801 }
802 if name.eq_ignore_ascii_case("RFC822") {
803 self.consume_sp()?;
804 return Ok(FetchAttribute::Rfc822(self.parse_nstring_bytes()?));
805 }
806 if name.eq_ignore_ascii_case("RFC822.HEADER") {
807 self.consume_sp()?;
808 return Ok(FetchAttribute::Rfc822Header(self.parse_nstring_bytes()?));
809 }
810 if name.eq_ignore_ascii_case("RFC822.TEXT") {
811 self.consume_sp()?;
812 return Ok(FetchAttribute::Rfc822Text(self.parse_nstring_bytes()?));
813 }
814 if name.eq_ignore_ascii_case("ENVELOPE") {
815 self.consume_sp()?;
816 return Ok(FetchAttribute::Envelope(self.parse_balanced_parens()?));
817 }
818 if name.eq_ignore_ascii_case("BODYSTRUCTURE") {
819 self.consume_sp()?;
820 return Ok(FetchAttribute::BodyStructure(self.parse_balanced_parens()?));
821 }
822 if name.eq_ignore_ascii_case("BODY") {
823 if self.try_consume_byte(b'[') {
826 let section = self.read_until_byte(b']')?;
827 self.consume_byte(b']')?;
828 let origin = if self.try_consume_byte(b'<') {
829 let n = self.parse_number()?;
830 self.consume_byte(b'>')?;
831 Some(n)
832 } else {
833 None
834 };
835 self.consume_sp()?;
836 let data = self.parse_nstring_bytes_optional()?;
837 return Ok(FetchAttribute::BodySection {
838 section: if section.is_empty() {
839 None
840 } else {
841 Some(section)
842 },
843 origin,
844 data,
845 });
846 }
847 self.consume_sp()?;
848 return Ok(FetchAttribute::Body(self.parse_balanced_parens()?));
849 }
850
851 Err(ParseError::Malformed("unknown FETCH attribute"))
853 }
854
855 fn parse_fetch_att_name(&mut self) -> Result<&'a str, ParseError> {
857 let start = self.pos;
858 while let Ok(c) = self.peek() {
859 if c == b' ' || c == b'[' || c == b')' {
860 break;
861 }
862 self.pos += 1;
863 }
864 if start == self.pos {
865 return Err(ParseError::Malformed("expected FETCH att name"));
866 }
867 core::str::from_utf8(&self.input[start..self.pos])
868 .map_err(|_| ParseError::Other("Invalid UTF-8"))
869 }
870
871 fn read_until_byte(&mut self, terminator: u8) -> Result<&'a str, ParseError> {
872 let start = self.pos;
873 while let Ok(c) = self.peek() {
874 if c == terminator {
875 break;
876 }
877 if c == b'\r' || c == b'\n' {
878 return Err(ParseError::Malformed("CR/LF before terminator"));
879 }
880 self.pos += 1;
881 }
882 core::str::from_utf8(&self.input[start..self.pos])
883 .map_err(|_| ParseError::Other("Invalid UTF-8"))
884 }
885
886 fn parse_nstring_bytes(&mut self) -> Result<&'a [u8], ParseError> {
888 match self.peek()? {
889 b'"' => {
890 let s = self.parse_quoted()?;
891 Ok(s.as_bytes())
892 }
893 b'{' => self.parse_literal(),
894 _ => {
895 if self.try_consume_keyword_ci(b"NIL") {
896 Ok(&self.input[self.pos..self.pos])
897 } else {
898 Err(ParseError::Malformed("expected nstring"))
899 }
900 }
901 }
902 }
903
904 fn parse_nstring_bytes_optional(&mut self) -> Result<Option<&'a [u8]>, ParseError> {
906 match self.peek()? {
907 b'"' => {
908 let s = self.parse_quoted()?;
909 Ok(Some(s.as_bytes()))
910 }
911 b'{' => Ok(Some(self.parse_literal()?)),
912 _ => {
913 if self.try_consume_keyword_ci(b"NIL") {
914 Ok(None)
915 } else {
916 Err(ParseError::Malformed("expected nstring"))
917 }
918 }
919 }
920 }
921}
922
923fn could_be_status_keyword_prefix(bytes: &[u8]) -> bool {
927 const KEYWORDS: &[&[u8]] = &[b"OK", b"NO", b"BAD", b"BYE", b"PREAUTH"];
928 for kw in KEYWORDS {
929 if bytes.len() < kw.len()
930 && kw[..bytes.len()]
931 .iter()
932 .zip(bytes.iter())
933 .all(|(a, b)| a.eq_ignore_ascii_case(b))
934 {
935 return true;
936 }
937 }
938 false
939}
940
941fn is_atom_char(c: u8) -> bool {
943 !matches!(
944 c,
945 0x00..=0x1F | 0x7F | b'(' | b')' | b'{' | b' ' | b'%' | b'*' | b'"' | b'\\' | b']'
947 )
948}
949
950#[cfg(test)]
955mod tests {
956 use super::*;
957 use crate::ast::*;
958
959 fn ok<'a>(input: &'a [u8]) -> Response<'a> {
960 let (rem, r) = parse_response(input).unwrap();
961 assert_eq!(rem.len(), 0, "non-empty remainder for input {:?}", input);
962 r
963 }
964
965 #[test]
968 fn test_parse_continue() {
969 let r = ok(b"+ ready for data\r\n");
970 assert_eq!(
971 r,
972 Response::Continue(ContinueReq {
973 code: None,
974 text: "ready for data",
975 })
976 );
977 }
978
979 #[test]
980 fn test_parse_continue_with_code() {
981 let r = ok(b"+ [ALERT] keep going\r\n");
982 if let Response::Continue(c) = r {
983 assert_eq!(c.code, Some(ResponseCode::Alert));
984 assert_eq!(c.text, "keep going");
985 } else {
986 panic!("expected continue");
987 }
988 }
989
990 #[test]
991 fn test_parse_continue_invalid_utf8() {
992 let res = parse_response(b"+ \xFF\r\n");
993 assert!(matches!(res, Err(ParseError::Other("Invalid UTF-8"))));
994 }
995
996 #[test]
997 fn test_parse_continue_expected_crlf() {
998 let res = parse_response(b"+ text\rX");
999 assert!(matches!(res, Err(ParseError::Malformed(_))));
1000 }
1001
1002 #[test]
1005 fn test_parse_untagged_ok() {
1006 let r = ok(b"* OK IMAP4rev1 Service Ready\r\n");
1007 assert_eq!(
1008 r,
1009 Response::Status(StatusResponse {
1010 tag: None,
1011 status: Status::Ok,
1012 code: None,
1013 text: "IMAP4rev1 Service Ready",
1014 })
1015 );
1016 }
1017
1018 #[test]
1019 fn test_parse_untagged_preauth() {
1020 let r = ok(b"* PREAUTH already authenticated\r\n");
1021 if let Response::Status(s) = r {
1022 assert_eq!(s.status, Status::PreAuth);
1023 assert_eq!(s.text, "already authenticated");
1024 } else {
1025 panic!("expected status");
1026 }
1027 }
1028
1029 #[test]
1030 fn test_parse_bye() {
1031 let r = ok(b"* BYE Logging out\r\n");
1032 if let Response::Status(s) = r {
1033 assert_eq!(s.status, Status::Bye);
1034 } else {
1035 panic!("expected status");
1036 }
1037 }
1038
1039 #[test]
1040 fn test_parse_untagged_invalid_utf8() {
1041 let res = parse_response(b"* OK \xFF\r\n");
1042 assert!(matches!(res, Err(ParseError::Other("Invalid UTF-8"))));
1043 }
1044
1045 #[test]
1048 fn test_parse_resp_code_uidvalidity() {
1049 let r = ok(b"* OK [UIDVALIDITY 12345] mailbox open\r\n");
1050 if let Response::Status(s) = r {
1051 assert_eq!(s.code, Some(ResponseCode::UidValidity(12345)));
1052 assert_eq!(s.text, "mailbox open");
1053 } else {
1054 panic!();
1055 }
1056 }
1057
1058 #[test]
1059 fn test_parse_resp_code_uidnext_unseen() {
1060 let r = ok(b"A1 OK [UIDNEXT 7] done\r\n");
1061 if let Response::Status(s) = r {
1062 assert_eq!(s.code, Some(ResponseCode::UidNext(7)));
1063 assert_eq!(s.tag, Some("A1"));
1064 assert_eq!(s.status, Status::Ok);
1065 } else {
1066 panic!();
1067 }
1068 let r = ok(b"A1 OK [UNSEEN 4] done\r\n");
1069 if let Response::Status(s) = r {
1070 assert_eq!(s.code, Some(ResponseCode::Unseen(4)));
1071 } else {
1072 panic!();
1073 }
1074 }
1075
1076 #[test]
1077 fn test_parse_resp_code_alert() {
1078 let r = ok(b"* OK [ALERT] System down at midnight\r\n");
1079 if let Response::Status(s) = r {
1080 assert_eq!(s.code, Some(ResponseCode::Alert));
1081 assert_eq!(s.text, "System down at midnight");
1082 } else {
1083 panic!();
1084 }
1085 }
1086
1087 #[test]
1088 fn test_parse_resp_code_capability() {
1089 let r = ok(b"* OK [CAPABILITY IMAP4rev2 STARTTLS LOGIN] hello\r\n");
1090 if let Response::Status(s) = r {
1091 assert_eq!(
1092 s.code,
1093 Some(ResponseCode::Capability(vec![
1094 "IMAP4rev2",
1095 "STARTTLS",
1096 "LOGIN"
1097 ]))
1098 );
1099 } else {
1100 panic!();
1101 }
1102 }
1103
1104 #[test]
1105 fn test_parse_resp_code_permanentflags() {
1106 let r = ok(b"* OK [PERMANENTFLAGS (\\Seen \\Draft \\*)] limited\r\n");
1107 if let Response::Status(s) = r {
1108 if let Some(ResponseCode::PermanentFlags(flags)) = s.code {
1109 assert_eq!(flags, vec!["\\Seen", "\\Draft", "\\*"]);
1110 } else {
1111 panic!("wrong code");
1112 }
1113 } else {
1114 panic!();
1115 }
1116 }
1117
1118 #[test]
1119 fn test_parse_resp_code_read_states() {
1120 let r = ok(b"A2 OK [READ-WRITE] selected\r\n");
1121 if let Response::Status(s) = r {
1122 assert_eq!(s.code, Some(ResponseCode::ReadWrite));
1123 } else {
1124 panic!();
1125 }
1126 let r = ok(b"A3 OK [READ-ONLY] selected\r\n");
1127 if let Response::Status(s) = r {
1128 assert_eq!(s.code, Some(ResponseCode::ReadOnly));
1129 } else {
1130 panic!();
1131 }
1132 }
1133
1134 #[test]
1135 fn test_parse_resp_code_other() {
1136 let r = ok(b"* OK [APPENDUID 12345 6] appended\r\n");
1137 if let Response::Status(s) = r {
1138 if let Some(ResponseCode::Other(name, extra)) = s.code {
1139 assert_eq!(name, "APPENDUID");
1140 assert_eq!(extra, Some("12345 6"));
1141 } else {
1142 panic!();
1143 }
1144 } else {
1145 panic!();
1146 }
1147 }
1148
1149 #[test]
1152 fn test_parse_tagged_ok() {
1153 let r = ok(b"A1 OK LOGIN completed\r\n");
1154 assert_eq!(
1155 r,
1156 Response::Status(StatusResponse {
1157 tag: Some("A1"),
1158 status: Status::Ok,
1159 code: None,
1160 text: "LOGIN completed",
1161 })
1162 );
1163 }
1164
1165 #[test]
1166 fn test_parse_tagged_no() {
1167 let r = ok(b"A1 NO LOGIN failed\r\n");
1168 if let Response::Status(s) = r {
1169 assert_eq!(s.status, Status::No);
1170 assert_eq!(s.text, "LOGIN failed");
1171 } else {
1172 panic!();
1173 }
1174 }
1175
1176 #[test]
1177 fn test_parse_tagged_bad() {
1178 let r = ok(b"A1 BAD bad command\r\n");
1179 if let Response::Status(s) = r {
1180 assert_eq!(s.status, Status::Bad);
1181 } else {
1182 panic!();
1183 }
1184 }
1185
1186 #[test]
1189 fn test_parse_capability_data() {
1190 let r = ok(b"* CAPABILITY IMAP4rev2 STARTTLS AUTH=PLAIN\r\n");
1191 if let Response::Data(DataResponse::Capability(caps)) = r {
1192 assert_eq!(caps, vec!["IMAP4rev2", "STARTTLS", "AUTH=PLAIN"]);
1193 } else {
1194 panic!();
1195 }
1196 }
1197
1198 #[test]
1199 fn test_parse_list() {
1200 let r = ok(b"* LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n");
1201 if let Response::Data(DataResponse::List {
1202 flags,
1203 delimiter,
1204 name,
1205 }) = r
1206 {
1207 assert_eq!(flags, vec!["\\HasNoChildren"]);
1208 assert_eq!(delimiter, Some("."));
1209 assert_eq!(name, "INBOX");
1210 } else {
1211 panic!();
1212 }
1213 }
1214
1215 #[test]
1216 fn test_parse_list_nil_delimiter() {
1217 let r = ok(b"* LIST () NIL \"INBOX\"\r\n");
1218 if let Response::Data(DataResponse::List {
1219 flags,
1220 delimiter,
1221 name,
1222 }) = r
1223 {
1224 assert!(flags.is_empty());
1225 assert_eq!(delimiter, None);
1226 assert_eq!(name, "INBOX");
1227 } else {
1228 panic!();
1229 }
1230 }
1231
1232 #[test]
1233 fn test_parse_lsub() {
1234 let r = ok(b"* LSUB (\\Noselect) \"/\" \"foo\"\r\n");
1235 if let Response::Data(DataResponse::Lsub { flags, .. }) = r {
1236 assert_eq!(flags, vec!["\\Noselect"]);
1237 } else {
1238 panic!();
1239 }
1240 }
1241
1242 #[test]
1243 fn test_parse_status_response() {
1244 let r = ok(b"* STATUS \"INBOX\" (MESSAGES 10 RECENT 1 UNSEEN 3)\r\n");
1245 if let Response::Data(DataResponse::Status { mailbox, items }) = r {
1246 assert_eq!(mailbox, "INBOX");
1247 assert_eq!(
1248 items,
1249 vec![
1250 StatusItem::Messages(10),
1251 StatusItem::Recent(1),
1252 StatusItem::Unseen(3)
1253 ]
1254 );
1255 } else {
1256 panic!();
1257 }
1258 }
1259
1260 #[test]
1261 fn test_parse_search_empty() {
1262 let r = ok(b"* SEARCH\r\n");
1263 if let Response::Data(DataResponse::Search(ids)) = r {
1264 assert!(ids.is_empty());
1265 } else {
1266 panic!();
1267 }
1268 }
1269
1270 #[test]
1271 fn test_parse_search_results() {
1272 let r = ok(b"* SEARCH 1 2 3 42\r\n");
1273 if let Response::Data(DataResponse::Search(ids)) = r {
1274 assert_eq!(ids, vec![1, 2, 3, 42]);
1275 } else {
1276 panic!();
1277 }
1278 }
1279
1280 #[test]
1281 fn test_parse_flags() {
1282 let r = ok(b"* FLAGS (\\Seen \\Draft)\r\n");
1283 if let Response::Data(DataResponse::Flags(flags)) = r {
1284 assert_eq!(flags, vec!["\\Seen", "\\Draft"]);
1285 } else {
1286 panic!();
1287 }
1288 }
1289
1290 #[test]
1291 fn test_parse_exists_recent_expunge() {
1292 if let Response::Data(DataResponse::Exists(n)) = ok(b"* 5 EXISTS\r\n") {
1293 assert_eq!(n, 5);
1294 } else {
1295 panic!();
1296 }
1297 if let Response::Data(DataResponse::Recent(n)) = ok(b"* 1 RECENT\r\n") {
1298 assert_eq!(n, 1);
1299 } else {
1300 panic!();
1301 }
1302 if let Response::Data(DataResponse::Expunge(n)) = ok(b"* 10 EXPUNGE\r\n") {
1303 assert_eq!(n, 10);
1304 } else {
1305 panic!();
1306 }
1307 }
1308
1309 #[test]
1312 fn test_parse_fetch_flags() {
1313 let r = ok(b"* 1 FETCH (FLAGS (\\Seen))\r\n");
1314 if let Response::Data(DataResponse::Fetch { seq, attributes }) = r {
1315 assert_eq!(seq, 1);
1316 assert_eq!(attributes, vec![FetchAttribute::Flags(vec!["\\Seen"])]);
1317 } else {
1318 panic!();
1319 }
1320 }
1321
1322 #[test]
1323 fn test_parse_fetch_uid() {
1324 let r = ok(b"* 1 FETCH (UID 42)\r\n");
1325 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1326 assert_eq!(attributes, vec![FetchAttribute::Uid(42)]);
1327 } else {
1328 panic!();
1329 }
1330 }
1331
1332 #[test]
1333 fn test_parse_fetch_internaldate() {
1334 let r = ok(b"* 1 FETCH (INTERNALDATE \"17-Jul-1996 02:44:25 -0700\")\r\n");
1335 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1336 assert_eq!(
1337 attributes,
1338 vec![FetchAttribute::InternalDate("17-Jul-1996 02:44:25 -0700")]
1339 );
1340 } else {
1341 panic!();
1342 }
1343 }
1344
1345 #[test]
1346 fn test_parse_fetch_rfc822_size() {
1347 let r = ok(b"* 2 FETCH (RFC822.SIZE 4242)\r\n");
1348 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1349 assert_eq!(attributes, vec![FetchAttribute::Rfc822Size(4242)]);
1350 } else {
1351 panic!();
1352 }
1353 }
1354
1355 #[test]
1356 fn test_parse_fetch_body_section_with_literal() {
1357 let r = ok(b"* 1 FETCH (BODY[] {10}\r\n0123456789)\r\n");
1358 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1359 assert_eq!(
1360 attributes,
1361 vec![FetchAttribute::BodySection {
1362 section: None,
1363 origin: None,
1364 data: Some(b"0123456789".as_ref()),
1365 }]
1366 );
1367 } else {
1368 panic!();
1369 }
1370 }
1371
1372 #[test]
1373 fn test_parse_fetch_body_section_with_section_and_origin() {
1374 let r = ok(b"* 1 FETCH (BODY[HEADER.FIELDS (FROM TO)]<0> {7}\r\nFrom: a)\r\n");
1375 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1376 assert_eq!(
1377 attributes,
1378 vec![FetchAttribute::BodySection {
1379 section: Some("HEADER.FIELDS (FROM TO)"),
1380 origin: Some(0),
1381 data: Some(b"From: a".as_ref()),
1382 }]
1383 );
1384 } else {
1385 panic!();
1386 }
1387 }
1388
1389 #[test]
1390 fn test_parse_fetch_body_section_nil() {
1391 let r = ok(b"* 1 FETCH (BODY[] NIL)\r\n");
1392 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1393 assert_eq!(
1394 attributes,
1395 vec![FetchAttribute::BodySection {
1396 section: None,
1397 origin: None,
1398 data: None,
1399 }]
1400 );
1401 } else {
1402 panic!();
1403 }
1404 }
1405
1406 #[test]
1407 fn test_parse_fetch_multi_attrs_with_literal() {
1408 let r = ok(b"* 1 FETCH (UID 5 BODY[] {3}\r\nabc FLAGS (\\Seen))\r\n");
1409 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1410 assert_eq!(
1411 attributes,
1412 vec![
1413 FetchAttribute::Uid(5),
1414 FetchAttribute::BodySection {
1415 section: None,
1416 origin: None,
1417 data: Some(b"abc".as_ref()),
1418 },
1419 FetchAttribute::Flags(vec!["\\Seen"]),
1420 ]
1421 );
1422 } else {
1423 panic!();
1424 }
1425 }
1426
1427 #[test]
1428 fn test_parse_fetch_envelope_raw() {
1429 let r = ok(
1430 b"* 1 FETCH (ENVELOPE (\"Date\" \"Subject\" NIL NIL NIL NIL NIL NIL NIL \"<id>\"))\r\n",
1431 );
1432 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1433 if let FetchAttribute::Envelope(bytes) = &attributes[0] {
1434 assert!(bytes.starts_with(b"("));
1435 assert!(bytes.ends_with(b")"));
1436 } else {
1437 panic!("expected envelope");
1438 }
1439 } else {
1440 panic!();
1441 }
1442 }
1443
1444 #[test]
1445 fn test_parse_fetch_bodystructure_raw() {
1446 let r = ok(b"* 1 FETCH (BODYSTRUCTURE (\"text\" \"plain\" NIL NIL NIL \"7BIT\" 12))\r\n");
1447 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1448 assert!(matches!(attributes[0], FetchAttribute::BodyStructure(_)));
1449 } else {
1450 panic!();
1451 }
1452 }
1453
1454 #[test]
1457 fn test_parse_empty_input_incomplete() {
1458 let res = parse_response(b"");
1459 assert!(matches!(res, Err(ParseError::Incomplete)));
1460 }
1461
1462 #[test]
1463 fn test_parse_incomplete_no_crlf() {
1464 let res = parse_response(b"* OK ");
1465 assert!(matches!(res, Err(ParseError::Incomplete)));
1466 }
1467
1468 #[test]
1469 fn test_parse_incomplete_only_cr() {
1470 let res = parse_response(b"* OK text\r");
1471 assert!(matches!(res, Err(ParseError::Incomplete)));
1472 }
1473
1474 #[test]
1475 fn test_parse_literal_incomplete() {
1476 let res = parse_response(b"* 1 FETCH (BODY[] {10}\r\nabc");
1477 assert!(matches!(res, Err(ParseError::Incomplete)));
1478 }
1479
1480 #[test]
1481 fn test_parse_literal_too_large() {
1482 let s = format!("* 1 FETCH (BODY[] {{{}}}\r\n", MAX_LITERAL_SIZE + 1);
1484 let res = parse_response(s.as_bytes());
1485 assert!(matches!(res, Err(ParseError::LiteralTooLarge { .. })));
1486 }
1487
1488 #[test]
1489 fn test_parse_incomplete_tagged_no_space() {
1490 let res = parse_response(b"A1");
1491 assert!(matches!(res, Err(ParseError::Incomplete)));
1492 }
1493
1494 #[test]
1495 fn test_parse_incomplete_tagged_no_crlf() {
1496 let res = parse_response(b"A1 OK\r");
1497 assert!(matches!(res, Err(ParseError::Incomplete)));
1498 }
1499
1500 #[test]
1501 fn test_parse_missing_lf_after_cr() {
1502 let res = parse_response(b"* OK text\rX");
1503 assert!(matches!(res, Err(ParseError::Malformed(_))));
1504 }
1505
1506 #[test]
1507 fn test_parse_unknown_data_falls_back_to_other() {
1508 let r = ok(b"* WIBBLE foo bar\r\n");
1509 if let Response::Data(DataResponse::Other(line)) = r {
1510 assert_eq!(line, b"WIBBLE foo bar");
1511 } else {
1512 panic!();
1513 }
1514 }
1515
1516 #[test]
1519 fn test_parse_returns_remainder() {
1520 let input = b"* OK first\r\n* OK second\r\n";
1521 let (rem, _) = parse_response(input).unwrap();
1522 assert_eq!(rem, b"* OK second\r\n");
1523 }
1524
1525 #[test]
1528 fn test_parse_keyword_prefix_not_status() {
1529 let r = ok(b"* OKAY all good\r\n");
1532 if let Response::Data(DataResponse::Other(line)) = r {
1533 assert_eq!(line, b"OKAY all good");
1534 } else {
1535 panic!("expected Other data, got {r:?}");
1536 }
1537 }
1538
1539 #[test]
1542 fn test_parse_lf_without_cr_is_malformed() {
1543 let res = parse_response(b"* OK text\n");
1545 assert!(matches!(res, Err(ParseError::Malformed("expected CR"))));
1546 }
1547
1548 #[test]
1551 fn test_parse_number_incomplete_at_eof() {
1552 let res = parse_response(b"* OK [UIDNEXT ");
1553 assert!(matches!(res, Err(ParseError::Incomplete)));
1554 }
1555
1556 #[test]
1557 fn test_parse_number_invalid_non_digit() {
1558 let res = parse_response(b"* OK [UIDNEXT x]\r\n");
1559 assert!(matches!(res, Err(ParseError::InvalidNumber)));
1560 }
1561
1562 #[test]
1565 fn test_parse_atom_incomplete_at_eof() {
1566 let res = parse_response(b"* 5 ");
1568 assert!(matches!(res, Err(ParseError::Incomplete)));
1569 }
1570
1571 #[test]
1572 fn test_parse_atom_malformed_non_atom_char() {
1573 let res = parse_response(b"* 5 (\r\n");
1574 assert!(matches!(res, Err(ParseError::Malformed("expected atom"))));
1575 }
1576
1577 #[test]
1580 fn test_parse_tag_malformed_empty() {
1581 let res = parse_response(b"\r\n");
1584 assert!(matches!(res, Err(ParseError::Malformed("expected tag"))));
1585 }
1586
1587 #[test]
1590 fn test_parse_quoted_with_escape_sequence() {
1591 let r = ok(b"* 1 FETCH (INTERNALDATE \"a\\\"b\")\r\n");
1593 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1594 assert_eq!(attributes, vec![FetchAttribute::InternalDate("a\\\"b")]);
1595 } else {
1596 panic!("expected fetch, got {r:?}");
1597 }
1598 }
1599
1600 #[test]
1601 fn test_parse_quoted_with_cr_is_malformed() {
1602 let res = parse_response(b"* 1 FETCH (INTERNALDATE \"a\rb\")\r\n");
1603 assert!(matches!(
1604 res,
1605 Err(ParseError::Malformed("CR/LF inside quoted string"))
1606 ));
1607 }
1608
1609 #[test]
1612 fn test_parse_status_mailbox_as_atom() {
1613 let r = ok(b"* STATUS INBOX (MESSAGES 1)\r\n");
1615 if let Response::Data(DataResponse::Status { mailbox, .. }) = r {
1616 assert_eq!(mailbox, "INBOX");
1617 } else {
1618 panic!("expected status, got {r:?}");
1619 }
1620 }
1621
1622 #[test]
1623 fn test_parse_status_mailbox_as_literal() {
1624 let r = ok(b"* STATUS {5}\r\nINBOX (MESSAGES 1)\r\n");
1625 if let Response::Data(DataResponse::Status { mailbox, .. }) = r {
1626 assert_eq!(mailbox, "INBOX");
1627 } else {
1628 panic!("expected status, got {r:?}");
1629 }
1630 }
1631
1632 #[test]
1633 fn test_parse_list_delimiter_as_literal() {
1634 let r = ok(b"* LIST () {1}\r\n. \"INBOX\"\r\n");
1635 if let Response::Data(DataResponse::List {
1636 delimiter, name, ..
1637 }) = r
1638 {
1639 assert_eq!(delimiter, Some("."));
1640 assert_eq!(name, "INBOX");
1641 } else {
1642 panic!("expected list, got {r:?}");
1643 }
1644 }
1645
1646 #[test]
1647 fn test_parse_list_delimiter_malformed() {
1648 let res = parse_response(b"* LIST () X \"INBOX\"\r\n");
1649 assert!(matches!(
1650 res,
1651 Err(ParseError::Malformed("expected nstring"))
1652 ));
1653 }
1654
1655 #[test]
1658 fn test_parse_flag_list_malformed_no_separator() {
1659 let res = parse_response(b"* FLAGS (\\Seen\\Draft)\r\n");
1661 assert!(matches!(
1662 res,
1663 Err(ParseError::Malformed("expected SP or ) in list"))
1664 ));
1665 }
1666
1667 #[test]
1668 fn test_parse_flag_list_empty_item_malformed() {
1669 let res = parse_response(b"* FLAGS (()\r\n");
1670 assert!(matches!(
1671 res,
1672 Err(ParseError::Malformed("expected flag/atom"))
1673 ));
1674 }
1675
1676 #[test]
1679 fn test_parse_envelope_with_nested_parens_and_literal() {
1680 let r = ok(b"* 1 FETCH (ENVELOPE ((\"a\") {2}\r\nbc))\r\n");
1681 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1682 if let FetchAttribute::Envelope(bytes) = &attributes[0] {
1683 assert_eq!(*bytes, b"((\"a\") {2}\r\nbc)".as_ref());
1684 } else {
1685 panic!("expected envelope, got {attributes:?}");
1686 }
1687 } else {
1688 panic!("expected fetch, got {r:?}");
1689 }
1690 }
1691
1692 #[test]
1695 fn test_parse_tagged_partial_keyword_incomplete() {
1696 let res = parse_response(b"A1 O");
1697 assert!(matches!(res, Err(ParseError::Incomplete)));
1698 }
1699
1700 #[test]
1701 fn test_parse_tagged_unknown_keyword_malformed() {
1702 let res = parse_response(b"A1 ZZ done\r\n");
1703 assert!(matches!(
1704 res,
1705 Err(ParseError::Malformed("expected tagged status keyword"))
1706 ));
1707 }
1708
1709 #[test]
1712 fn test_parse_resp_code_empty_malformed() {
1713 let res = parse_response(b"* OK [] text\r\n");
1714 assert!(matches!(
1715 res,
1716 Err(ParseError::Malformed("empty response code"))
1717 ));
1718 }
1719
1720 #[test]
1721 fn test_parse_resp_code_badcharset_with_list() {
1722 let r = ok(b"* NO [BADCHARSET (UTF-8 KOI8-R)] bad charset\r\n");
1723 if let Response::Status(s) = r {
1724 assert_eq!(
1725 s.code,
1726 Some(ResponseCode::BadCharset(vec!["UTF-8", "KOI8-R"]))
1727 );
1728 } else {
1729 panic!("expected status, got {r:?}");
1730 }
1731 }
1732
1733 #[test]
1734 fn test_parse_resp_code_badcharset_without_list() {
1735 let r = ok(b"* NO [BADCHARSET] bad charset\r\n");
1736 if let Response::Status(s) = r {
1737 assert_eq!(s.code, Some(ResponseCode::BadCharset(vec![])));
1738 } else {
1739 panic!("expected status, got {r:?}");
1740 }
1741 }
1742
1743 #[test]
1744 fn test_parse_resp_code_other_without_extra() {
1745 let r = ok(b"* NO [NONEXISTENT] gone\r\n");
1746 if let Response::Status(s) = r {
1747 assert_eq!(s.code, Some(ResponseCode::Other("NONEXISTENT", None)));
1748 } else {
1749 panic!("expected status, got {r:?}");
1750 }
1751 }
1752
1753 #[test]
1756 fn test_parse_unknown_numeric_data_falls_back_to_other() {
1757 let r = ok(b"* 5 WIBBLE foo bar\r\n");
1758 if let Response::Data(DataResponse::Other(line)) = r {
1759 assert_eq!(line, b"* 5 WIBBLE foo bar");
1760 } else {
1761 panic!("expected Other data, got {r:?}");
1762 }
1763 }
1764
1765 #[test]
1768 fn test_parse_status_empty_items() {
1769 let r = ok(b"* STATUS \"INBOX\" ()\r\n");
1770 if let Response::Data(DataResponse::Status { items, .. }) = r {
1771 assert!(items.is_empty());
1772 } else {
1773 panic!("expected status, got {r:?}");
1774 }
1775 }
1776
1777 #[test]
1778 fn test_parse_status_items_uid_and_other() {
1779 let r = ok(b"* STATUS \"X\" (UIDNEXT 5 UIDVALIDITY 9 HIGHESTMODSEQ 100)\r\n");
1780 if let Response::Data(DataResponse::Status { items, .. }) = r {
1781 assert_eq!(
1782 items,
1783 vec![
1784 StatusItem::UidNext(5),
1785 StatusItem::UidValidity(9),
1786 StatusItem::Other(100),
1787 ]
1788 );
1789 } else {
1790 panic!("expected status, got {r:?}");
1791 }
1792 }
1793
1794 #[test]
1795 fn test_parse_status_items_malformed_separator() {
1796 let res = parse_response(b"* STATUS \"X\" (MESSAGES 1Z)\r\n");
1797 assert!(matches!(
1798 res,
1799 Err(ParseError::Malformed("expected SP or ) in STATUS items"))
1800 ));
1801 }
1802
1803 #[test]
1804 fn test_parse_status_items_missing_open_paren() {
1805 let res = parse_response(b"* STATUS \"X\" MESSAGES 1\r\n");
1807 assert!(matches!(res, Err(ParseError::InvalidChar(_))));
1808 }
1809
1810 #[test]
1813 fn test_parse_fetch_empty_attrs() {
1814 let r = ok(b"* 1 FETCH ()\r\n");
1815 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1816 assert!(attributes.is_empty());
1817 } else {
1818 panic!("expected fetch, got {r:?}");
1819 }
1820 }
1821
1822 #[test]
1823 fn test_parse_fetch_attrs_malformed_separator() {
1824 let res = parse_response(b"* 1 FETCH (UID 5Z)\r\n");
1825 assert!(matches!(
1826 res,
1827 Err(ParseError::Malformed("expected SP or ) in FETCH atts"))
1828 ));
1829 }
1830
1831 #[test]
1832 fn test_parse_fetch_unknown_attribute_malformed() {
1833 let res = parse_response(b"* 1 FETCH (FOOBAR 1)\r\n");
1834 assert!(matches!(
1835 res,
1836 Err(ParseError::Malformed("unknown FETCH attribute"))
1837 ));
1838 }
1839
1840 #[test]
1841 fn test_parse_fetch_empty_att_name_malformed() {
1842 let res = parse_response(b"* 1 FETCH ( )\r\n");
1843 assert!(matches!(
1844 res,
1845 Err(ParseError::Malformed("expected FETCH att name"))
1846 ));
1847 }
1848
1849 #[test]
1852 fn test_parse_fetch_rfc822_literal() {
1853 let r = ok(b"* 1 FETCH (RFC822 {3}\r\nabc)\r\n");
1854 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1855 assert_eq!(attributes, vec![FetchAttribute::Rfc822(b"abc")]);
1856 } else {
1857 panic!("expected fetch, got {r:?}");
1858 }
1859 }
1860
1861 #[test]
1862 fn test_parse_fetch_rfc822_header_and_text() {
1863 let r = ok(b"* 1 FETCH (RFC822.HEADER {2}\r\nhi RFC822.TEXT {2}\r\nyo)\r\n");
1864 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1865 assert_eq!(
1866 attributes,
1867 vec![
1868 FetchAttribute::Rfc822Header(b"hi"),
1869 FetchAttribute::Rfc822Text(b"yo"),
1870 ]
1871 );
1872 } else {
1873 panic!("expected fetch, got {r:?}");
1874 }
1875 }
1876
1877 #[test]
1878 fn test_parse_fetch_rfc822_quoted() {
1879 let r = ok(b"* 1 FETCH (RFC822 \"hi\")\r\n");
1880 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1881 assert_eq!(attributes, vec![FetchAttribute::Rfc822(b"hi")]);
1882 } else {
1883 panic!("expected fetch, got {r:?}");
1884 }
1885 }
1886
1887 #[test]
1888 fn test_parse_fetch_rfc822_nil() {
1889 let r = ok(b"* 1 FETCH (RFC822 NIL)\r\n");
1890 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1891 assert_eq!(attributes, vec![FetchAttribute::Rfc822(b"")]);
1892 } else {
1893 panic!("expected fetch, got {r:?}");
1894 }
1895 }
1896
1897 #[test]
1898 fn test_parse_fetch_rfc822_malformed() {
1899 let res = parse_response(b"* 1 FETCH (RFC822 Z)\r\n");
1900 assert!(matches!(
1901 res,
1902 Err(ParseError::Malformed("expected nstring"))
1903 ));
1904 }
1905
1906 #[test]
1909 fn test_parse_fetch_body_no_section() {
1910 let r = ok(b"* 1 FETCH (BODY (\"text\" \"plain\" NIL))\r\n");
1911 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1912 assert!(matches!(attributes[0], FetchAttribute::Body(_)));
1913 } else {
1914 panic!("expected fetch, got {r:?}");
1915 }
1916 }
1917
1918 #[test]
1919 fn test_parse_fetch_body_section_quoted_data() {
1920 let r = ok(b"* 1 FETCH (BODY[] \"hi\")\r\n");
1921 if let Response::Data(DataResponse::Fetch { attributes, .. }) = r {
1922 assert_eq!(
1923 attributes,
1924 vec![FetchAttribute::BodySection {
1925 section: None,
1926 origin: None,
1927 data: Some(b"hi".as_ref()),
1928 }]
1929 );
1930 } else {
1931 panic!("expected fetch, got {r:?}");
1932 }
1933 }
1934
1935 #[test]
1936 fn test_parse_fetch_body_section_malformed_data() {
1937 let res = parse_response(b"* 1 FETCH (BODY[] Z)\r\n");
1938 assert!(matches!(
1939 res,
1940 Err(ParseError::Malformed("expected nstring"))
1941 ));
1942 }
1943
1944 #[test]
1945 fn test_parse_fetch_body_section_crlf_in_section() {
1946 let res = parse_response(b"* 1 FETCH (BODY[HE\r\n");
1947 assert!(matches!(
1948 res,
1949 Err(ParseError::Malformed("CR/LF before terminator"))
1950 ));
1951 }
1952}