You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

605 lines
21KB

  1. use super::*;
  2. use nom::{digit, is_digit, rest, IResult};
  3. use std::collections::hash_map::DefaultHasher;
  4. use std::hash::{Hash, Hasher};
  5. use std::str::FromStr;
  6. macro_rules! to_str (
  7. ($v:expr) => (unsafe{ std::str::from_utf8_unchecked($v) })
  8. );
  9. macro_rules! dbg_dmp (
  10. ($i: expr, $submac:ident!( $($args:tt)* )) => (
  11. {
  12. let l = line!();
  13. match $submac!($i, $($args)*) {
  14. nom::IResult::Error(a) => {
  15. debug!("Error({:?}) at l.{} by ' {} '\n{}", a, l, stringify!($submac!($($args)*)), unsafe{ std::str::from_utf8_unchecked($i) });
  16. nom::IResult::Error(a)
  17. },
  18. nom::IResult::Incomplete(a) => {
  19. debug!("Incomplete({:?}) at {} by ' {} '\n{}", a, l, stringify!($submac!($($args)*)), unsafe{ std::str::from_utf8_unchecked($i) });
  20. nom::IResult::Incomplete(a)
  21. },
  22. a => a
  23. }
  24. }
  25. );
  26. ($i:expr, $f:ident) => (
  27. dbg_dmp!($i, call!($f));
  28. );
  29. );
  30. macro_rules! get_path_hash {
  31. ($path:expr) => {{
  32. let mut hasher = DefaultHasher::new();
  33. $path.hash(&mut hasher);
  34. hasher.finish()
  35. }};
  36. }
  37. /*
  38. * LIST (\HasNoChildren) "." INBOX.Sent
  39. * LIST (\HasChildren) "." INBOX
  40. */
  41. named!(
  42. pub list_folder_result<ImapFolder>,
  43. do_parse!(
  44. ws!(tag!("* LIST ("))
  45. >> properties: take_until!(&b")"[0..])
  46. >> tag!(b") ")
  47. >> separator: delimited!(tag!(b"\""), take!(1), tag!(b"\""))
  48. >> take!(1)
  49. >> path: alt_complete!(delimited!(tag!("\""), is_not!("\""), tag!("\"")) | call!(rest))
  50. >> ({
  51. let separator: u8 = separator[0];
  52. let mut f = ImapFolder::default();
  53. f.hash = get_path_hash!(path);
  54. f.path = String::from_utf8_lossy(path).into();
  55. f.name = if let Some(pos) = path.iter().rposition(|&c| c == separator) {
  56. f.parent = Some(get_path_hash!(&path[..pos]));
  57. String::from_utf8_lossy(&path[pos + 1..]).into()
  58. } else {
  59. f.path.clone()
  60. };
  61. debug!(f)
  62. })
  63. )
  64. );
  65. named!(
  66. my_flags<Flag>,
  67. do_parse!(
  68. flags: separated_list!(tag!(" "), preceded!(tag!("\\"), is_not!(")")))
  69. >> ({
  70. let mut ret = Flag::default();
  71. for f in flags {
  72. match f {
  73. b"Answered" => {
  74. ret.set(Flag::REPLIED, true);
  75. }
  76. b"Flagged" => {
  77. ret.set(Flag::FLAGGED, true);
  78. }
  79. b"Deleted" => {
  80. ret.set(Flag::TRASHED, true);
  81. }
  82. b"Seen" => {
  83. ret.set(Flag::SEEN, true);
  84. }
  85. b"Draft" => {
  86. ret.set(Flag::DRAFT, true);
  87. }
  88. f => {
  89. debug!("unknown Flag token value: {}", unsafe {
  90. std::str::from_utf8_unchecked(f)
  91. });
  92. }
  93. }
  94. }
  95. ret
  96. })
  97. )
  98. );
  99. /*
  100. *
  101. * "* 1 FETCH (FLAGS (\Seen) UID 1 RFC822.HEADER {5224} "
  102. */
  103. named!(
  104. pub uid_fetch_response<Vec<(usize, Option<Flag>, &[u8])>>,
  105. many0!(
  106. do_parse!(
  107. tag!("* ")
  108. >> take_while!(call!(is_digit))
  109. >> tag!(" FETCH (")
  110. >> uid_flags: permutation!(preceded!(ws!(tag!("UID ")), map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })), opt!(preceded!(ws!(tag!("FLAGS ")), delimited!(tag!("("), byte_flags, tag!(")")))))
  111. >> is_not!("{")
  112. >> body: length_bytes!(delimited!(tag!("{"), map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) }), tag!("}\r\n")))
  113. >> tag!(")\r\n")
  114. >> ((uid_flags.0, uid_flags.1, body))
  115. )
  116. )
  117. );
  118. named!(
  119. pub uid_fetch_flags_response<Vec<(usize, Flag)>>,
  120. many0!(
  121. do_parse!(
  122. tag!("* ")
  123. >> take_while!(call!(is_digit))
  124. >> tag!(" FETCH (")
  125. >> uid_flags: permutation!(preceded!(ws!(tag!("UID ")), map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })), preceded!(ws!(tag!("FLAGS ")), delimited!(tag!("("), byte_flags, tag!(")"))))
  126. >> tag!(")\r\n")
  127. >> ((uid_flags.0, uid_flags.1))
  128. )
  129. )
  130. );
  131. macro_rules! flags_to_imap_list {
  132. ($flags:ident) => {{
  133. let mut ret = String::new();
  134. if !($flags & Flag::REPLIED).is_empty() {
  135. ret.push_str("\\Answered");
  136. }
  137. if !($flags & Flag::FLAGGED).is_empty() {
  138. if !ret.is_empty() {
  139. ret.push(' ');
  140. }
  141. ret.push_str("\\Flagged");
  142. }
  143. if !($flags & Flag::TRASHED).is_empty() {
  144. if !ret.is_empty() {
  145. ret.push(' ');
  146. }
  147. ret.push_str("\\Deleted");
  148. }
  149. if !($flags & Flag::SEEN).is_empty() {
  150. if !ret.is_empty() {
  151. ret.push(' ');
  152. }
  153. ret.push_str("\\Seen");
  154. }
  155. if !($flags & Flag::DRAFT).is_empty() {
  156. if !ret.is_empty() {
  157. ret.push(' ');
  158. }
  159. ret.push_str("\\Draft");
  160. }
  161. ret
  162. }};
  163. }
  164. /* Input Example:
  165. * ==============
  166. *
  167. * "M0 OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS BINARY MOVE SPECIAL-USE] Logged in\r\n"
  168. */
  169. named!(
  170. pub capabilities<Vec<&[u8]>>,
  171. do_parse!(
  172. take_until!("[CAPABILITY ")
  173. >> tag!("[CAPABILITY ")
  174. >> ret: terminated!(separated_nonempty_list_complete!(tag!(" "), is_not!(" ]")), tag!("]"))
  175. >> take_until!("\r\n")
  176. >> tag!("\r\n")
  177. >> ({ ret })
  178. )
  179. );
  180. /// This enum represents the server's untagged responses detailed in `7. Server Responses` of RFC 3501 INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1
  181. pub enum UntaggedResponse {
  182. /// ```text
  183. /// 7.4.1. EXPUNGE Response
  184. ///
  185. /// The EXPUNGE response reports that the specified message sequence
  186. /// number has been permanently removed from the mailbox. The message
  187. /// sequence number for each successive message in the mailbox is
  188. /// immediately decremented by 1, and this decrement is reflected in
  189. /// message sequence numbers in subsequent responses (including other
  190. /// untagged EXPUNGE responses).
  191. ///
  192. /// The EXPUNGE response also decrements the number of messages in the
  193. /// mailbox; it is not necessary to send an EXISTS response with the
  194. /// new value.
  195. ///
  196. /// As a result of the immediate decrement rule, message sequence
  197. /// numbers that appear in a set of successive EXPUNGE responses
  198. /// depend upon whether the messages are removed starting from lower
  199. /// numbers to higher numbers, or from higher numbers to lower
  200. /// numbers. For example, if the last 5 messages in a 9-message
  201. /// mailbox are expunged, a "lower to higher" server will send five
  202. /// untagged EXPUNGE responses for message sequence number 5, whereas
  203. /// a "higher to lower server" will send successive untagged EXPUNGE
  204. /// responses for message sequence numbers 9, 8, 7, 6, and 5.
  205. ///
  206. /// An EXPUNGE response MUST NOT be sent when no command is in
  207. /// progress, nor while responding to a FETCH, STORE, or SEARCH
  208. /// command. This rule is necessary to prevent a loss of
  209. /// synchronization of message sequence numbers between client and
  210. /// server. A command is not "in progress" until the complete command
  211. /// has been received; in particular, a command is not "in progress"
  212. /// during the negotiation of command continuation.
  213. ///
  214. /// Note: UID FETCH, UID STORE, and UID SEARCH are different
  215. /// commands from FETCH, STORE, and SEARCH. An EXPUNGE
  216. /// response MAY be sent during a UID command.
  217. ///
  218. /// The update from the EXPUNGE response MUST be recorded by the
  219. /// client.
  220. /// ```
  221. Expunge(usize),
  222. /// ```text
  223. /// 7.3.1. EXISTS Response
  224. ///
  225. /// The EXISTS response reports the number of messages in the mailbox.
  226. /// This response occurs as a result of a SELECT or EXAMINE command,
  227. /// and if the size of the mailbox changes (e.g., new messages).
  228. ///
  229. /// The update from the EXISTS response MUST be recorded by the
  230. /// client.
  231. /// ```
  232. Exists(usize),
  233. /// ```text
  234. /// 7.3.2. RECENT Response
  235. /// The RECENT response reports the number of messages with the
  236. /// \Recent flag set. This response occurs as a result of a SELECT or
  237. /// EXAMINE command, and if the size of the mailbox changes (e.g., new
  238. /// messages).
  239. /// ```
  240. Recent(usize),
  241. }
  242. named!(
  243. pub untagged_responses<Option<UntaggedResponse>>,
  244. do_parse!(
  245. tag!("* ")
  246. >> num: map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })
  247. >> tag!(" ")
  248. >> tag: take_until!("\r\n")
  249. >> tag!("\r\n")
  250. >> ({
  251. use UntaggedResponse::*;
  252. match tag {
  253. b"EXPUNGE" => Some(Expunge(num)),
  254. b"EXISTS" => Some(Exists(num)),
  255. b"RECENT" => Some(Recent(num)),
  256. _ => {
  257. debug!("unknown untagged_response: {}", unsafe { std::str::from_utf8_unchecked(tag) });
  258. None
  259. }
  260. }
  261. })
  262. )
  263. );
  264. named!(
  265. pub search_results<Vec<usize>>,
  266. alt_complete!(do_parse!( tag!("* SEARCH")
  267. >> list: separated_nonempty_list_complete!(tag!(" "), map_res!(is_not!("\r\n"), |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) }))
  268. >> tag!("\r\n")
  269. >> ({ list })) |
  270. do_parse!(tag!("* SEARCH\r\n") >> ({ Vec::new() })))
  271. );
  272. named!(
  273. pub search_results_raw<&[u8]>,
  274. alt_complete!(do_parse!( tag!("* SEARCH ")
  275. >> list: take_until!("\r\n")
  276. >> tag!("\r\n")
  277. >> ({ list })) |
  278. do_parse!(tag!("* SEARCH\r\n") >> ({ &b""[0..] })))
  279. );
  280. #[derive(Debug, Default, Clone)]
  281. pub struct SelectResponse {
  282. pub exists: usize,
  283. pub recent: usize,
  284. pub flags: Flag,
  285. pub unseen: usize,
  286. pub uidvalidity: usize,
  287. pub uidnext: usize,
  288. pub permanentflags: Flag,
  289. pub read_only: bool,
  290. }
  291. /*
  292. * Example: C: A142 SELECT INBOX
  293. * S: * 172 EXISTS
  294. * S: * 1 RECENT
  295. * S: * OK [UNSEEN 12] Message 12 is first unseen
  296. * S: * OK [UIDVALIDITY 3857529045] UIDs valid
  297. * S: * OK [UIDNEXT 4392] Predicted next UID
  298. * S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
  299. * S: * OK [PERMANENTFLAGS (\Deleted \Seen \*)] Limited
  300. * S: A142 OK [READ-WRITE] SELECT completed
  301. */
  302. /*
  303. *
  304. * * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
  305. * * OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft \*)] Flags permitted.
  306. * * 45 EXISTS
  307. * * 0 RECENT
  308. * * OK [UNSEEN 16] First unseen.
  309. * * OK [UIDVALIDITY 1554422056] UIDs valid
  310. * * OK [UIDNEXT 50] Predicted next UID
  311. */
  312. pub fn select_response(input: &str) -> Result<SelectResponse> {
  313. if input.contains("* OK") {
  314. let mut ret = SelectResponse::default();
  315. for l in input.split("\r\n") {
  316. if l.starts_with("* ") && l.ends_with(" EXISTS") {
  317. ret.exists = usize::from_str(&l["* ".len()..l.len() - " EXISTS".len()]).unwrap();
  318. } else if l.starts_with("* ") && l.ends_with(" RECENT") {
  319. ret.recent = usize::from_str(&l["* ".len()..l.len() - " RECENT".len()]).unwrap();
  320. } else if l.starts_with("* FLAGS (") {
  321. ret.flags = flags(&l["* FLAGS (".len()..l.len() - ")".len()])
  322. .to_full_result()
  323. .unwrap();
  324. } else if l.starts_with("* OK [UNSEEN ") {
  325. ret.unseen =
  326. usize::from_str(&l["* OK [UNSEEN ".len()..l.find(']').unwrap()]).unwrap();
  327. } else if l.starts_with("* OK [UIDVALIDITY ") {
  328. ret.uidvalidity =
  329. usize::from_str(&l["* OK [UIDVALIDITY ".len()..l.find(']').unwrap()]).unwrap();
  330. } else if l.starts_with("* OK [UIDNEXT ") {
  331. ret.uidnext =
  332. usize::from_str(&l["* OK [UIDNEXT ".len()..l.find(']').unwrap()]).unwrap();
  333. } else if l.starts_with("* OK [PERMANENTFLAGS (") {
  334. ret.permanentflags =
  335. flags(&l["* OK [PERMANENTFLAGS (".len()..l.find(')').unwrap()])
  336. .to_full_result()
  337. .unwrap();
  338. } else if l.contains("OK [READ-WRITE]") {
  339. ret.read_only = false;
  340. } else if l.contains("OK [READ-ONLY]") {
  341. ret.read_only = true;
  342. } else if !l.is_empty() {
  343. debug!("select response: {}", l);
  344. }
  345. }
  346. Ok(ret)
  347. } else {
  348. debug!("BAD/NO response in select: {}", input);
  349. Err(MeliError::new(input))
  350. }
  351. }
  352. pub fn flags(input: &str) -> IResult<&str, Flag> {
  353. let mut ret = Flag::default();
  354. let mut input = input;
  355. while !input.starts_with(")") && !input.is_empty() {
  356. if input.starts_with("\\") {
  357. input = &input[1..];
  358. }
  359. let mut match_end = 0;
  360. while match_end < input.len() {
  361. if input[match_end..].starts_with(" ") || input[match_end..].starts_with(")") {
  362. break;
  363. }
  364. match_end += 1;
  365. }
  366. match &input[..match_end] {
  367. "Answered" => {
  368. ret.set(Flag::REPLIED, true);
  369. }
  370. "Flagged" => {
  371. ret.set(Flag::FLAGGED, true);
  372. }
  373. "Deleted" => {
  374. ret.set(Flag::TRASHED, true);
  375. }
  376. "Seen" => {
  377. ret.set(Flag::SEEN, true);
  378. }
  379. "Draft" => {
  380. ret.set(Flag::DRAFT, true);
  381. }
  382. f => {
  383. debug!("unknown Flag token value: {}", f);
  384. }
  385. }
  386. input = &input[match_end..];
  387. input = input.trim_start();
  388. }
  389. IResult::Done(input, ret)
  390. }
  391. pub fn byte_flags(input: &[u8]) -> IResult<&[u8], Flag> {
  392. let i = unsafe { std::str::from_utf8_unchecked(input) };
  393. match flags(i) {
  394. IResult::Done(rest, ret) => IResult::Done(rest.as_bytes(), ret),
  395. IResult::Error(e) => IResult::Error(e),
  396. IResult::Incomplete(e) => IResult::Incomplete(e),
  397. }
  398. }
  399. /*
  400. * The fields of the envelope structure are in the following
  401. * order: date, subject, from, sender, reply-to, to, cc, bcc,
  402. * in-reply-to, and message-id. The date, subject, in-reply-to,
  403. * and message-id fields are strings. The from, sender, reply-to,
  404. * to, cc, and bcc fields are parenthesized lists of address
  405. * structures.
  406. * An address structure is a parenthesized list that describes an
  407. * electronic mail address. The fields of an address structure
  408. * are in the following order: personal name, [SMTP]
  409. * at-domain-list (source route), mailbox name, and host name.
  410. */
  411. /*
  412. * * 12 FETCH (FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700"
  413. * RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)"
  414. * "IMAP4rev1 WG mtg summary and minutes"
  415. * (("Terry Gray" NIL "gray" "cac.washington.edu"))
  416. * (("Terry Gray" NIL "gray" "cac.washington.edu"))
  417. * (("Terry Gray" NIL "gray" "cac.washington.edu"))
  418. * ((NIL NIL "imap" "cac.washington.edu"))
  419. * ((NIL NIL "minutes" "CNRI.Reston.VA.US")
  420. * ("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL
  421. * "<B27397-0100000@cac.washington.edu>")
  422. */
  423. named!(
  424. pub envelope<Envelope>,
  425. do_parse!(
  426. tag!("(")
  427. >> opt!(is_a!("\r\n\t "))
  428. >> date: quoted_or_nil
  429. >> opt!(is_a!("\r\n\t "))
  430. >> subject: quoted_or_nil
  431. >> opt!(is_a!("\r\n\t "))
  432. >> from: envelope_addresses
  433. >> opt!(is_a!("\r\n\t "))
  434. >> sender: envelope_addresses
  435. >> opt!(is_a!("\r\n\t "))
  436. >> reply_to: envelope_addresses
  437. >> opt!(is_a!("\r\n\t "))
  438. >> to: envelope_addresses
  439. >> opt!(is_a!("\r\n\t "))
  440. >> cc: envelope_addresses
  441. >> opt!(is_a!("\r\n\t "))
  442. >> bcc: envelope_addresses
  443. >> opt!(is_a!("\r\n\t "))
  444. >> in_reply_to: quoted_or_nil
  445. >> opt!(is_a!("\r\n\t "))
  446. >> message_id: quoted_or_nil
  447. >> opt!(is_a!("\r\n\t "))
  448. >> tag!(")")
  449. >> ({
  450. let mut env = Envelope::new(0);
  451. if let Some(date) = date {
  452. env.set_date(&date);
  453. if let Some(d) = crate::email::parser::date(env.date_as_str().as_bytes()) {
  454. env.set_datetime(d);
  455. }
  456. }
  457. if let Some(subject) = subject {
  458. env.set_subject(subject.to_vec());
  459. }
  460. if let Some(from) = from {
  461. env.set_from(from);
  462. }
  463. if let Some(to) = to {
  464. env.set_to(to);
  465. }
  466. if let Some(cc) = cc {
  467. env.set_cc(cc);
  468. }
  469. if let Some(bcc) = bcc {
  470. env.set_bcc(bcc);
  471. }
  472. if let Some(in_reply_to) = in_reply_to {
  473. env.set_in_reply_to(&in_reply_to);
  474. env.push_references(&in_reply_to);
  475. }
  476. if let Some(message_id) = message_id {
  477. env.set_message_id(&message_id);
  478. }
  479. env
  480. })
  481. ));
  482. /* Helper to build StrBuilder for Address structs */
  483. macro_rules! str_builder {
  484. ($offset:expr, $length:expr) => {
  485. StrBuilder {
  486. offset: $offset,
  487. length: $length,
  488. }
  489. };
  490. }
  491. // Parse a list of addresses in the format of the ENVELOPE structure
  492. named!(pub envelope_addresses<Option<Vec<Address>>>,
  493. alt_complete!(map!(tag!("NIL"), |_| None) |
  494. do_parse!(
  495. tag!("(")
  496. >> envelopes: many1!(delimited!(ws!(tag!("(")), envelope_address, tag!(")")))
  497. >> tag!(")")
  498. >> ({
  499. Some(envelopes)
  500. })
  501. )
  502. ));
  503. // Parse an address in the format of the ENVELOPE structure eg
  504. // ("Terry Gray" NIL "gray" "cac.washington.edu")
  505. named!(
  506. pub envelope_address<Address>,
  507. do_parse!(
  508. name: alt_complete!(quoted | map!(tag!("NIL"), |_| Vec::new()))
  509. >> is_a!("\r\n\t ")
  510. >> alt_complete!(quoted| map!(tag!("NIL"), |_| Vec::new()))
  511. >> is_a!("\r\n\t ")
  512. >> mailbox_name: dbg_dmp!(alt_complete!(quoted | map!(tag!("NIL"), |_| Vec::new())))
  513. >> is_a!("\r\n\t ")
  514. >> host_name: alt_complete!(quoted | map!(tag!("NIL"), |_| Vec::new()))
  515. >> ({
  516. Address::Mailbox(MailboxAddress {
  517. raw: format!("{}{}<{}@{}>", to_str!(&name), if name.is_empty() { "" } else { " " }, to_str!(&mailbox_name), to_str!(&host_name)).into_bytes(),
  518. display_name: str_builder!(0, name.len()),
  519. address_spec: str_builder!(if name.is_empty() { 1 } else { name.len() + 2 }, mailbox_name.len() + host_name.len() + 1),
  520. })
  521. })
  522. ));
  523. // Read a literal ie a byte sequence prefixed with a tag containing its length delimited in {}s
  524. named!(pub literal<&[u8]>,length_bytes!(delimited!(tag!("{"), map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) }), tag!("}\r\n"))));
  525. // Return a byte sequence surrounded by "s and decoded if necessary
  526. pub fn quoted(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
  527. if let IResult::Done(r, o) = literal(input) {
  528. return match crate::email::parser::phrase(o) {
  529. IResult::Done(_, out) => IResult::Done(r, out),
  530. e => e,
  531. };
  532. }
  533. if input.is_empty() || input[0] != b'"' {
  534. return IResult::Error(nom::ErrorKind::Custom(0));
  535. }
  536. let mut i = 1;
  537. while i < input.len() {
  538. if input[i] == b'\"' {
  539. //&& input[i - 1] != b'\\' {
  540. return match crate::email::parser::phrase(&input[1..i]) {
  541. IResult::Done(_, out) => IResult::Done(&input[i + 1..], out),
  542. e => e,
  543. };
  544. }
  545. i += 1;
  546. }
  547. return IResult::Error(nom::ErrorKind::Custom(0));
  548. }
  549. named!(
  550. pub quoted_or_nil<Option<Vec<u8>>>,
  551. alt_complete!(map!(ws!(tag!("NIL")), |_| None) | map!(quoted, |v| Some(v))));
  552. named!(
  553. pub uid_fetch_envelopes_response<Vec<(usize, Option<Flag>, Envelope)>>,
  554. many0!(
  555. do_parse!(
  556. tag!("* ")
  557. >> take_while!(call!(is_digit))
  558. >> tag!(" FETCH (")
  559. >> uid_flags: permutation!(preceded!(ws!(tag!("UID ")), map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })), opt!(preceded!(ws!(tag!("FLAGS ")), delimited!(tag!("("), byte_flags, tag!(")")))))
  560. >> tag!(" ENVELOPE ")
  561. >> env: ws!(envelope)
  562. >> tag!(")\r\n")
  563. >> ((uid_flags.0, uid_flags.1, env))
  564. )
  565. )
  566. );