use regex::Regex; use nom::IResult; use imap_proto::{self, Response}; use super::types::*; use super::error::{Error, ParseError, Result}; pub fn parse_authenticate_response(line: String) -> Result { let authenticate_regex = Regex::new("^+(.*)\r\n").unwrap(); for cap in authenticate_regex.captures_iter(line.as_str()) { let data = cap.get(1).map(|x| x.as_str()).unwrap_or(""); return Ok(String::from(data)); } Err(Error::Parse(ParseError::Authentication(line))) } enum MapOrNot { Map(T), Not(Response<'static>), } unsafe fn parse_many(lines: Vec, mut map: F) -> ZeroCopyResult> where F: FnMut(Response<'static>) -> MapOrNot, { let f = |mut lines| { let mut things = Vec::new(); loop { match imap_proto::parse_response(lines) { IResult::Done(rest, resp) => { lines = rest; match map(resp) { MapOrNot::Map(t) => things.push(t), MapOrNot::Not(resp) => break Err(resp.into()), } if lines.is_empty() { break Ok(things); } } _ => { break Err(Error::Parse(ParseError::Invalid(lines.to_vec()))); } } } }; ZeroCopy::new(lines, f) } pub fn parse_names(lines: Vec) -> ZeroCopyResult> { use imap_proto::MailboxDatum; let f = |resp| match resp { // https://github.com/djc/imap-proto/issues/4 Response::MailboxData(MailboxDatum::List { flags, delimiter, name, }) | Response::MailboxData(MailboxDatum::SubList { flags, delimiter, name, }) => MapOrNot::Map(Name { attributes: flags, delimiter, name, }), resp => MapOrNot::Not(resp), }; unsafe { parse_many(lines, f) } } pub fn parse_fetches(lines: Vec) -> ZeroCopyResult> { let f = |resp| match resp { Response::Fetch(num, attrs) => { let mut fetch = Fetch { message: num, flags: vec![], uid: None, rfc822: None, }; for attr in attrs { use imap_proto::AttributeValue; match attr { AttributeValue::Flags(flags) => { fetch.flags.extend(flags); } AttributeValue::Uid(uid) => fetch.uid = Some(uid), AttributeValue::Rfc822(rfc) => fetch.rfc822 = rfc, _ => {} } } MapOrNot::Map(fetch) } resp => MapOrNot::Not(resp), }; unsafe { parse_many(lines, f) } } pub fn parse_capabilities(lines: Vec) -> ZeroCopyResult { let f = |mut lines| { use std::collections::HashSet; let mut caps = HashSet::new(); loop { match imap_proto::parse_response(lines) { IResult::Done(rest, Response::Capabilities(c)) => { lines = rest; caps.extend(c); if lines.is_empty() { break Ok(Capabilities(caps)); } } IResult::Done(_, resp) => { break Err(resp.into()); } _ => { break Err(Error::Parse(ParseError::Invalid(lines.to_vec()))); } } } }; unsafe { ZeroCopy::new(lines, f) } } pub fn parse_mailbox(mut lines: &[u8]) -> Result { let mut mailbox = Mailbox::default(); loop { match imap_proto::parse_response(lines) { IResult::Done(rest, Response::Data { status, code, .. }) => { lines = rest; if let imap_proto::Status::Ok = status { } else { // how can this happen for a Response::Data? unreachable!(); } use imap_proto::ResponseCode; match code { Some(ResponseCode::UidValidity(uid)) => { mailbox.uid_validity = Some(uid); } Some(ResponseCode::UidNext(unext)) => { mailbox.uid_next = Some(unext); } Some(ResponseCode::Unseen(n)) => { mailbox.unseen = Some(n); } Some(ResponseCode::PermanentFlags(flags)) => { mailbox .permanent_flags .extend(flags.into_iter().map(|s| s.to_string())); } _ => {} } } IResult::Done(rest, Response::MailboxData(m)) => { lines = rest; use imap_proto::MailboxDatum; match m { MailboxDatum::Exists(e) => { mailbox.exists = e; } MailboxDatum::Recent(r) => { mailbox.recent = r; } MailboxDatum::Flags(flags) => { mailbox .flags .extend(flags.into_iter().map(|s| s.to_string())); } MailboxDatum::SubList { .. } | MailboxDatum::List { .. } => {} } } IResult::Done(_, resp) => { break Err(resp.into()); } _ => { break Err(Error::Parse(ParseError::Invalid(lines.to_vec()))); } } if lines.is_empty() { break Ok(mailbox); } } } #[cfg(test)] mod tests { use super::*; #[test] fn parse_capability_test() { let expected_capabilities = vec!["IMAP4rev1", "STARTTLS", "AUTH=GSSAPI", "LOGINDISABLED"]; let lines = b"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n"; let capabilities = parse_capabilities(lines.to_vec()).unwrap(); assert_eq!(capabilities.len(), 4); for e in expected_capabilities { assert!(capabilities.has(e)); } } #[test] #[should_panic] fn parse_capability_invalid_test() { let lines = b"* JUNK IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n"; parse_capabilities(lines.to_vec()).unwrap(); } #[test] fn parse_names_test() { let lines = b"* LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n"; let names = parse_names(lines.to_vec()).unwrap(); assert_eq!(names.len(), 1); assert_eq!(names[0].attributes(), &["\\HasNoChildren"]); assert_eq!(names[0].delimiter(), "."); assert_eq!(names[0].name(), "INBOX"); } #[test] fn parse_fetches_test() { let lines = b"\ * 24 FETCH (FLAGS (\\Seen) UID 4827943)\r\n\ * 25 FETCH (FLAGS (\\Seen))\r\n"; let fetches = parse_fetches(lines.to_vec()).unwrap(); assert_eq!(fetches.len(), 2); assert_eq!(fetches[0].message, 24); assert_eq!(fetches[0].flags(), &["\\Seen"]); assert_eq!(fetches[0].uid, Some(4827943)); assert_eq!(fetches[0].rfc822(), None); assert_eq!(fetches[1].message, 25); assert_eq!(fetches[1].flags(), &["\\Seen"]); assert_eq!(fetches[1].uid, None); assert_eq!(fetches[1].rfc822(), None); } }