Merge pull request #208 from mordak/zero-copy

Convert ZeroCopy to ouroboros.
This commit is contained in:
Jon Gjengset 2021-08-15 20:41:09 -04:00 committed by GitHub
commit 88417339f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 461 additions and 443 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "imap" name = "imap"
version = "3.0.0-alpha.4" version = "3.0.0-alpha.5"
authors = ["Jon Gjengset <jon@thesquareplanet.com>", authors = ["Jon Gjengset <jon@thesquareplanet.com>",
"Matt McCoy <mattnenterprise@yahoo.com>"] "Matt McCoy <mattnenterprise@yahoo.com>"]
documentation = "https://docs.rs/imap/" documentation = "https://docs.rs/imap/"
@ -27,6 +27,7 @@ nom = { version = "6.0", default-features = false }
base64 = "0.13" base64 = "0.13"
chrono = { version = "0.4", default-features = false, features = ["std"]} chrono = { version = "0.4", default-features = false, features = ["std"]}
lazy_static = "1.4" lazy_static = "1.4"
ouroboros = "0.9.5"
[dev-dependencies] [dev-dependencies]
lettre = "0.9" lettre = "0.9"

View file

@ -42,7 +42,7 @@ fn main() {
match imap_session.fetch("2", "body[text]") { match imap_session.fetch("2", "body[text]") {
Ok(msgs) => { Ok(msgs) => {
for msg in &msgs { for msg in msgs.iter() {
print!("{:?}", msg); print!("{:?}", msg);
} }
} }

View file

@ -565,39 +565,39 @@ impl<T: Read + Write> Session<T> {
/// - `RFC822.HEADER`: Functionally equivalent to `BODY.PEEK[HEADER]`. /// - `RFC822.HEADER`: Functionally equivalent to `BODY.PEEK[HEADER]`.
/// - `RFC822.SIZE`: The [RFC-2822](https://tools.ietf.org/html/rfc2822) size of the message. /// - `RFC822.SIZE`: The [RFC-2822](https://tools.ietf.org/html/rfc2822) size of the message.
/// - `UID`: The unique identifier for the message. /// - `UID`: The unique identifier for the message.
pub fn fetch<S1, S2>(&mut self, sequence_set: S1, query: S2) -> ZeroCopyResult<Vec<Fetch>> pub fn fetch<S1, S2>(&mut self, sequence_set: S1, query: S2) -> Result<Fetches>
where where
S1: AsRef<str>, S1: AsRef<str>,
S2: AsRef<str>, S2: AsRef<str>,
{ {
if sequence_set.as_ref().is_empty() { if sequence_set.as_ref().is_empty() {
parse_fetches(vec![], &mut self.unsolicited_responses_tx) Fetches::parse(vec![], &mut self.unsolicited_responses_tx)
} else { } else {
self.run_command_and_read_response(&format!( self.run_command_and_read_response(&format!(
"FETCH {} {}", "FETCH {} {}",
validate_sequence_set(sequence_set.as_ref())?, validate_sequence_set(sequence_set.as_ref())?,
validate_str_noquote(query.as_ref())? validate_str_noquote(query.as_ref())?
)) ))
.and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) .and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx))
} }
} }
/// Equivalent to [`Session::fetch`], except that all identifiers in `uid_set` are /// Equivalent to [`Session::fetch`], except that all identifiers in `uid_set` are
/// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8). /// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8).
pub fn uid_fetch<S1, S2>(&mut self, uid_set: S1, query: S2) -> ZeroCopyResult<Vec<Fetch>> pub fn uid_fetch<S1, S2>(&mut self, uid_set: S1, query: S2) -> Result<Fetches>
where where
S1: AsRef<str>, S1: AsRef<str>,
S2: AsRef<str>, S2: AsRef<str>,
{ {
if uid_set.as_ref().is_empty() { if uid_set.as_ref().is_empty() {
parse_fetches(vec![], &mut self.unsolicited_responses_tx) Fetches::parse(vec![], &mut self.unsolicited_responses_tx)
} else { } else {
self.run_command_and_read_response(&format!( self.run_command_and_read_response(&format!(
"UID FETCH {} {}", "UID FETCH {} {}",
validate_sequence_set(uid_set.as_ref())?, validate_sequence_set(uid_set.as_ref())?,
validate_str_noquote(query.as_ref())? validate_str_noquote(query.as_ref())?
)) ))
.and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) .and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx))
} }
} }
@ -728,9 +728,9 @@ impl<T: Read + Write> Session<T> {
/// The [`CAPABILITY` command](https://tools.ietf.org/html/rfc3501#section-6.1.1) requests a /// The [`CAPABILITY` command](https://tools.ietf.org/html/rfc3501#section-6.1.1) requests a
/// listing of capabilities that the server supports. The server will include "IMAP4rev1" as /// listing of capabilities that the server supports. The server will include "IMAP4rev1" as
/// one of the listed capabilities. See [`Capabilities`] for further details. /// one of the listed capabilities. See [`Capabilities`] for further details.
pub fn capabilities(&mut self) -> ZeroCopyResult<Capabilities> { pub fn capabilities(&mut self) -> Result<Capabilities> {
self.run_command_and_read_response("CAPABILITY") self.run_command_and_read_response("CAPABILITY")
.and_then(|lines| parse_capabilities(lines, &mut self.unsolicited_responses_tx)) .and_then(|lines| Capabilities::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`EXPUNGE` command](https://tools.ietf.org/html/rfc3501#section-6.4.3) permanently /// The [`EXPUNGE` command](https://tools.ietf.org/html/rfc3501#section-6.4.3) permanently
@ -845,7 +845,7 @@ impl<T: Read + Write> Session<T> {
/// Ok(()) /// Ok(())
/// } /// }
/// ``` /// ```
pub fn store<S1, S2>(&mut self, sequence_set: S1, query: S2) -> ZeroCopyResult<Vec<Fetch>> pub fn store<S1, S2>(&mut self, sequence_set: S1, query: S2) -> Result<Fetches>
where where
S1: AsRef<str>, S1: AsRef<str>,
S2: AsRef<str>, S2: AsRef<str>,
@ -855,12 +855,12 @@ impl<T: Read + Write> Session<T> {
sequence_set.as_ref(), sequence_set.as_ref(),
query.as_ref() query.as_ref()
)) ))
.and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) .and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// Equivalent to [`Session::store`], except that all identifiers in `sequence_set` are /// Equivalent to [`Session::store`], except that all identifiers in `sequence_set` are
/// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8). /// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8).
pub fn uid_store<S1, S2>(&mut self, uid_set: S1, query: S2) -> ZeroCopyResult<Vec<Fetch>> pub fn uid_store<S1, S2>(&mut self, uid_set: S1, query: S2) -> Result<Fetches>
where where
S1: AsRef<str>, S1: AsRef<str>,
S2: AsRef<str>, S2: AsRef<str>,
@ -870,7 +870,7 @@ impl<T: Read + Write> Session<T> {
uid_set.as_ref(), uid_set.as_ref(),
query.as_ref() query.as_ref()
)) ))
.and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) .and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`COPY` command](https://tools.ietf.org/html/rfc3501#section-6.4.7) copies the /// The [`COPY` command](https://tools.ietf.org/html/rfc3501#section-6.4.7) copies the
@ -999,13 +999,13 @@ impl<T: Read + Write> Session<T> {
&mut self, &mut self,
reference_name: Option<&str>, reference_name: Option<&str>,
mailbox_pattern: Option<&str>, mailbox_pattern: Option<&str>,
) -> ZeroCopyResult<Vec<Name>> { ) -> Result<Names> {
self.run_command_and_read_response(&format!( self.run_command_and_read_response(&format!(
"LIST {} {}", "LIST {} {}",
quote!(reference_name.unwrap_or("")), quote!(reference_name.unwrap_or("")),
mailbox_pattern.unwrap_or("\"\"") mailbox_pattern.unwrap_or("\"\"")
)) ))
.and_then(|lines| parse_names(lines, &mut self.unsolicited_responses_tx)) .and_then(|lines| Names::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`LSUB` command](https://tools.ietf.org/html/rfc3501#section-6.3.9) returns a subset of /// The [`LSUB` command](https://tools.ietf.org/html/rfc3501#section-6.3.9) returns a subset of
@ -1027,13 +1027,13 @@ impl<T: Read + Write> Session<T> {
&mut self, &mut self,
reference_name: Option<&str>, reference_name: Option<&str>,
mailbox_pattern: Option<&str>, mailbox_pattern: Option<&str>,
) -> ZeroCopyResult<Vec<Name>> { ) -> Result<Names> {
self.run_command_and_read_response(&format!( self.run_command_and_read_response(&format!(
"LSUB {} {}", "LSUB {} {}",
quote!(reference_name.unwrap_or("")), quote!(reference_name.unwrap_or("")),
mailbox_pattern.unwrap_or("") mailbox_pattern.unwrap_or("")
)) ))
.and_then(|lines| parse_names(lines, &mut self.unsolicited_responses_tx)) .and_then(|lines| Names::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`STATUS` command](https://tools.ietf.org/html/rfc3501#section-6.3.10) requests the /// The [`STATUS` command](https://tools.ietf.org/html/rfc3501#section-6.3.10) requests the

View file

@ -3,6 +3,7 @@ use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use std::collections::HashSet; use std::collections::HashSet;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::iter::Extend;
use std::sync::mpsc; use std::sync::mpsc;
use super::error::{Error, ParseError, Result}; use super::error::{Error, ParseError, Result};
@ -23,26 +24,29 @@ pub fn parse_authenticate_response(line: &str) -> Result<&str> {
))) )))
} }
enum MapOrNot<T> { pub(crate) enum MapOrNot<'a, T> {
Map(T), Map(T),
Not(Response<'static>), MapVec(Vec<T>),
Not(Response<'a>),
#[allow(dead_code)] #[allow(dead_code)]
Ignore, Ignore,
} }
unsafe fn parse_many<T, F>( /// Parse many `T` Responses with `F` and extend `into` with them.
lines: Vec<u8>, /// Responses other than `T` go into the `unsolicited` channel.
mut map: F, pub(crate) fn parse_many_into<'input, T, F>(
input: &'input [u8],
into: &mut impl Extend<T>,
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> ZeroCopyResult<Vec<T>> mut map: F,
) -> Result<()>
where where
F: FnMut(Response<'static>) -> Result<MapOrNot<T>>, F: FnMut(Response<'input>) -> Result<MapOrNot<'input, T>>,
{ {
let f = |mut lines: &'static [u8]| { let mut lines = input;
let mut things = Vec::new();
loop { loop {
if lines.is_empty() { if lines.is_empty() {
break Ok(things); break Ok(());
} }
match imap_proto::parser::parse_response(lines) { match imap_proto::parser::parse_response(lines) {
@ -50,7 +54,8 @@ where
lines = rest; lines = rest;
match map(resp)? { match map(resp)? {
MapOrNot::Map(t) => things.push(t), MapOrNot::Map(t) => into.extend(std::iter::once(t)),
MapOrNot::MapVec(t) => into.extend(t),
MapOrNot::Not(resp) => match try_handle_unilateral(resp, unsolicited) { MapOrNot::Not(resp) => match try_handle_unilateral(resp, unsolicited) {
Some(Response::Fetch(..)) => continue, Some(Response::Fetch(..)) => continue,
Some(resp) => break Err(resp.into()), Some(resp) => break Err(resp.into()),
@ -64,64 +69,6 @@ where
} }
} }
} }
};
ZeroCopy::make(lines, f)
}
pub fn parse_names(
lines: Vec<u8>,
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> ZeroCopyResult<Vec<Name>> {
let f = |resp| match resp {
// https://github.com/djc/imap-proto/issues/4
Response::MailboxData(MailboxDatum::List {
flags,
delimiter,
name,
}) => Ok(MapOrNot::Map(Name {
attributes: flags.into_iter().map(NameAttribute::from).collect(),
delimiter,
name,
})),
resp => Ok(MapOrNot::Not(resp)),
};
unsafe { parse_many(lines, f, unsolicited) }
}
pub fn parse_fetches(
lines: Vec<u8>,
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> ZeroCopyResult<Vec<Fetch>> {
let f = |resp| match resp {
Response::Fetch(num, attrs) => {
let mut fetch = Fetch {
message: num,
flags: vec![],
uid: None,
size: None,
fetch: attrs,
};
// set some common fields eaglery
for attr in &fetch.fetch {
match attr {
AttributeValue::Flags(flags) => {
fetch.flags.extend(Flag::from_strs(flags));
}
AttributeValue::Uid(uid) => fetch.uid = Some(*uid),
AttributeValue::Rfc822Size(sz) => fetch.size = Some(*sz),
_ => {}
}
}
Ok(MapOrNot::Map(fetch))
}
resp => Ok(MapOrNot::Not(resp)),
};
unsafe { parse_many(lines, f, unsolicited) }
} }
pub fn parse_expunge( pub fn parse_expunge(
@ -169,38 +116,6 @@ pub fn parse_expunge(
} }
} }
pub fn parse_capabilities(
lines: Vec<u8>,
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> ZeroCopyResult<Capabilities> {
let f = |mut lines| {
let mut caps = HashSet::new();
loop {
match imap_proto::parser::parse_response(lines) {
Ok((rest, Response::Capabilities(c))) => {
lines = rest;
caps.extend(c);
}
Ok((rest, data)) => {
lines = rest;
if let Some(resp) = try_handle_unilateral(data, unsolicited) {
break Err(resp.into());
}
}
_ => {
break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
}
}
if lines.is_empty() {
break Ok(Capabilities(caps));
}
}
};
unsafe { ZeroCopy::make(lines, f) }
}
pub fn parse_noop( pub fn parse_noop(
lines: Vec<u8>, lines: Vec<u8>,
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
@ -456,7 +371,7 @@ mod tests {
]; ];
let lines = b"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n"; let lines = b"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n";
let (mut send, recv) = mpsc::channel(); let (mut send, recv) = mpsc::channel();
let capabilities = parse_capabilities(lines.to_vec(), &mut send).unwrap(); let capabilities = Capabilities::parse(lines.to_vec(), &mut send).unwrap();
// shouldn't be any unexpected responses parsed // shouldn't be any unexpected responses parsed
assert!(recv.try_recv().is_err()); assert!(recv.try_recv().is_err());
assert_eq!(capabilities.len(), 4); assert_eq!(capabilities.len(), 4);
@ -474,7 +389,7 @@ mod tests {
]; ];
let lines = b"* CAPABILITY IMAP4REV1 STARTTLS\r\n"; let lines = b"* CAPABILITY IMAP4REV1 STARTTLS\r\n";
let (mut send, recv) = mpsc::channel(); let (mut send, recv) = mpsc::channel();
let capabilities = parse_capabilities(lines.to_vec(), &mut send).unwrap(); let capabilities = Capabilities::parse(lines.to_vec(), &mut send).unwrap();
// shouldn't be any unexpected responses parsed // shouldn't be any unexpected responses parsed
assert!(recv.try_recv().is_err()); assert!(recv.try_recv().is_err());
assert_eq!(capabilities.len(), 2); assert_eq!(capabilities.len(), 2);
@ -488,7 +403,7 @@ mod tests {
fn parse_capability_invalid_test() { fn parse_capability_invalid_test() {
let (mut send, recv) = mpsc::channel(); let (mut send, recv) = mpsc::channel();
let lines = b"* JUNK IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n"; let lines = b"* JUNK IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n";
parse_capabilities(lines.to_vec(), &mut send).unwrap(); Capabilities::parse(lines.to_vec(), &mut send).unwrap();
assert!(recv.try_recv().is_err()); assert!(recv.try_recv().is_err());
} }
@ -496,22 +411,23 @@ mod tests {
fn parse_names_test() { fn parse_names_test() {
let lines = b"* LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n"; let lines = b"* LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n";
let (mut send, recv) = mpsc::channel(); let (mut send, recv) = mpsc::channel();
let names = parse_names(lines.to_vec(), &mut send).unwrap(); let names = Names::parse(lines.to_vec(), &mut send).unwrap();
assert!(recv.try_recv().is_err()); assert!(recv.try_recv().is_err());
assert_eq!(names.len(), 1); assert_eq!(names.len(), 1);
let first = names.get(0).unwrap();
assert_eq!( assert_eq!(
names[0].attributes(), first.attributes(),
&[NameAttribute::from("\\HasNoChildren")] &[NameAttribute::from("\\HasNoChildren")]
); );
assert_eq!(names[0].delimiter(), Some(".")); assert_eq!(first.delimiter(), Some("."));
assert_eq!(names[0].name(), "INBOX"); assert_eq!(first.name(), "INBOX");
} }
#[test] #[test]
fn parse_fetches_empty() { fn parse_fetches_empty() {
let lines = b""; let lines = b"";
let (mut send, recv) = mpsc::channel(); let (mut send, recv) = mpsc::channel();
let fetches = parse_fetches(lines.to_vec(), &mut send).unwrap(); let fetches = Fetches::parse(lines.to_vec(), &mut send).unwrap();
assert!(recv.try_recv().is_err()); assert!(recv.try_recv().is_err());
assert!(fetches.is_empty()); assert!(fetches.is_empty());
} }
@ -522,19 +438,21 @@ mod tests {
* 24 FETCH (FLAGS (\\Seen) UID 4827943)\r\n\ * 24 FETCH (FLAGS (\\Seen) UID 4827943)\r\n\
* 25 FETCH (FLAGS (\\Seen))\r\n"; * 25 FETCH (FLAGS (\\Seen))\r\n";
let (mut send, recv) = mpsc::channel(); let (mut send, recv) = mpsc::channel();
let fetches = parse_fetches(lines.to_vec(), &mut send).unwrap(); let fetches = Fetches::parse(lines.to_vec(), &mut send).unwrap();
assert!(recv.try_recv().is_err()); assert!(recv.try_recv().is_err());
assert_eq!(fetches.len(), 2); assert_eq!(fetches.len(), 2);
assert_eq!(fetches[0].message, 24); let first = fetches.get(0).unwrap();
assert_eq!(fetches[0].flags(), &[Flag::Seen]); assert_eq!(first.message, 24);
assert_eq!(fetches[0].uid, Some(4827943)); assert_eq!(first.flags(), &[Flag::Seen]);
assert_eq!(fetches[0].body(), None); assert_eq!(first.uid, Some(4827943));
assert_eq!(fetches[0].header(), None); assert_eq!(first.body(), None);
assert_eq!(fetches[1].message, 25); assert_eq!(first.header(), None);
assert_eq!(fetches[1].flags(), &[Flag::Seen]); let second = fetches.get(1).unwrap();
assert_eq!(fetches[1].uid, None); assert_eq!(second.message, 25);
assert_eq!(fetches[1].body(), None); assert_eq!(second.flags(), &[Flag::Seen]);
assert_eq!(fetches[1].header(), None); assert_eq!(second.uid, None);
assert_eq!(second.body(), None);
assert_eq!(second.header(), None);
} }
#[test] #[test]
@ -544,11 +462,12 @@ mod tests {
* 37 FETCH (UID 74)\r\n\ * 37 FETCH (UID 74)\r\n\
* 1 RECENT\r\n"; * 1 RECENT\r\n";
let (mut send, recv) = mpsc::channel(); let (mut send, recv) = mpsc::channel();
let fetches = parse_fetches(lines.to_vec(), &mut send).unwrap(); let fetches = Fetches::parse(lines.to_vec(), &mut send).unwrap();
assert_eq!(recv.try_recv(), Ok(UnsolicitedResponse::Recent(1))); assert_eq!(recv.try_recv(), Ok(UnsolicitedResponse::Recent(1)));
assert_eq!(fetches.len(), 1); assert_eq!(fetches.len(), 1);
assert_eq!(fetches[0].message, 37); let first = fetches.get(0).unwrap();
assert_eq!(fetches[0].uid, Some(74)); assert_eq!(first.message, 37);
assert_eq!(first.uid, Some(74));
} }
#[test] #[test]
@ -560,7 +479,7 @@ mod tests {
* OK Searched 91% of the mailbox, ETA 0:01\r\n\ * OK Searched 91% of the mailbox, ETA 0:01\r\n\
* 37 FETCH (UID 74)\r\n"; * 37 FETCH (UID 74)\r\n";
let (mut send, recv) = mpsc::channel(); let (mut send, recv) = mpsc::channel();
let fetches = parse_fetches(lines.to_vec(), &mut send).unwrap(); let fetches = Fetches::parse(lines.to_vec(), &mut send).unwrap();
assert_eq!( assert_eq!(
recv.try_recv(), recv.try_recv(),
Ok(UnsolicitedResponse::Ok { Ok(UnsolicitedResponse::Ok {
@ -569,8 +488,9 @@ mod tests {
}) })
); );
assert_eq!(fetches.len(), 1); assert_eq!(fetches.len(), 1);
assert_eq!(fetches[0].message, 37); let first = fetches.get(0).unwrap();
assert_eq!(fetches[0].uid, Some(74)); assert_eq!(first.message, 37);
assert_eq!(first.uid, Some(74));
} }
#[test] #[test]
@ -579,17 +499,18 @@ mod tests {
* LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n\ * LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n\
* 4 EXPUNGE\r\n"; * 4 EXPUNGE\r\n";
let (mut send, recv) = mpsc::channel(); let (mut send, recv) = mpsc::channel();
let names = parse_names(lines.to_vec(), &mut send).unwrap(); let names = Names::parse(lines.to_vec(), &mut send).unwrap();
assert_eq!(recv.try_recv().unwrap(), UnsolicitedResponse::Expunge(4)); assert_eq!(recv.try_recv().unwrap(), UnsolicitedResponse::Expunge(4));
assert_eq!(names.len(), 1); assert_eq!(names.len(), 1);
let first = names.get(0).unwrap();
assert_eq!( assert_eq!(
names[0].attributes(), first.attributes(),
&[NameAttribute::from("\\HasNoChildren")] &[NameAttribute::from("\\HasNoChildren")]
); );
assert_eq!(names[0].delimiter(), Some(".")); assert_eq!(first.delimiter(), Some("."));
assert_eq!(names[0].name(), "INBOX"); assert_eq!(first.name(), "INBOX");
} }
#[test] #[test]
@ -605,7 +526,7 @@ mod tests {
* STATUS dev.github (MESSAGES 10 UIDNEXT 11 UIDVALIDITY 1408806928 UNSEEN 0)\r\n\ * STATUS dev.github (MESSAGES 10 UIDNEXT 11 UIDVALIDITY 1408806928 UNSEEN 0)\r\n\
* 4 EXISTS\r\n"; * 4 EXISTS\r\n";
let (mut send, recv) = mpsc::channel(); let (mut send, recv) = mpsc::channel();
let capabilities = parse_capabilities(lines.to_vec(), &mut send).unwrap(); let capabilities = Capabilities::parse(lines.to_vec(), &mut send).unwrap();
assert_eq!(capabilities.len(), 4); assert_eq!(capabilities.len(), 4);
for e in expected_capabilities { for e in expected_capabilities {
@ -720,7 +641,7 @@ mod tests {
let lines = b"* VANISHED (EARLIER) 3:8,12,50:60\r\n\ let lines = b"* VANISHED (EARLIER) 3:8,12,50:60\r\n\
* 49 FETCH (UID 117 FLAGS (\\Seen \\Answered) MODSEQ (90060115194045001))\r\n"; * 49 FETCH (UID 117 FLAGS (\\Seen \\Answered) MODSEQ (90060115194045001))\r\n";
let fetches = parse_fetches(lines.to_vec(), &mut send).unwrap(); let fetches = Fetches::parse(lines.to_vec(), &mut send).unwrap();
match recv.try_recv().unwrap() { match recv.try_recv().unwrap() {
UnsolicitedResponse::Vanished { earlier, uids } => { UnsolicitedResponse::Vanished { earlier, uids } => {
assert!(earlier); assert!(earlier);
@ -736,10 +657,11 @@ mod tests {
} }
assert!(recv.try_recv().is_err()); assert!(recv.try_recv().is_err());
assert_eq!(fetches.len(), 1); assert_eq!(fetches.len(), 1);
assert_eq!(fetches[0].message, 49); let first = fetches.get(0).unwrap();
assert_eq!(fetches[0].flags(), &[Flag::Seen, Flag::Answered]); assert_eq!(first.message, 49);
assert_eq!(fetches[0].uid, Some(117)); assert_eq!(first.flags(), &[Flag::Seen, Flag::Answered]);
assert_eq!(fetches[0].body(), None); assert_eq!(first.uid, Some(117));
assert_eq!(fetches[0].header(), None); assert_eq!(first.body(), None);
assert_eq!(first.header(), None);
} }
} }

View file

@ -1,6 +1,11 @@
use imap_proto::types::Capability; use crate::error::Error;
use crate::parse::{parse_many_into, MapOrNot};
use crate::types::UnsolicitedResponse;
use imap_proto::{Capability, Response};
use ouroboros::self_referencing;
use std::collections::hash_set::Iter; use std::collections::hash_set::Iter;
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::mpsc;
const IMAP4REV1_CAPABILITY: &str = "IMAP4rev1"; const IMAP4REV1_CAPABILITY: &str = "IMAP4rev1";
const AUTH_CAPABILITY_PREFIX: &str = "AUTH="; const AUTH_CAPABILITY_PREFIX: &str = "AUTH=";
@ -30,16 +35,37 @@ const AUTH_CAPABILITY_PREFIX: &str = "AUTH=";
/// ///
/// Client implementations SHOULD NOT require any capability name other than `IMAP4rev1`, and MUST /// Client implementations SHOULD NOT require any capability name other than `IMAP4rev1`, and MUST
/// ignore any unknown capability names. /// ignore any unknown capability names.
pub struct Capabilities( #[self_referencing]
// Note that this field isn't *actually* 'static. pub struct Capabilities {
// Rather, it is tied to the lifetime of the `ZeroCopy` that contains this `Name`. data: Vec<u8>,
pub(crate) HashSet<Capability<'static>>, #[borrows(data)]
); #[covariant]
pub(crate) capabilities: HashSet<Capability<'this>>,
}
impl Capabilities { impl Capabilities {
/// Parse the given input into one or more [`Capabilitity`] responses.
pub fn parse(
owned: Vec<u8>,
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Self, Error> {
CapabilitiesTryBuilder {
data: owned,
capabilities_builder: |input| {
let mut caps = HashSet::new();
parse_many_into(input, &mut caps, unsolicited, |response| match response {
Response::Capabilities(c) => Ok(MapOrNot::MapVec(c)),
resp => Ok(MapOrNot::Not(resp)),
})?;
Ok(caps)
},
}
.try_build()
}
/// Check if the server has the given capability. /// Check if the server has the given capability.
pub fn has<'a>(&self, cap: &Capability<'a>) -> bool { pub fn has<'a>(&self, cap: &Capability<'a>) -> bool {
self.0.contains(cap) self.borrow_capabilities().contains(cap)
} }
/// Check if the server has the given capability via str. /// Check if the server has the given capability via str.
@ -59,16 +85,16 @@ impl Capabilities {
/// Iterate over all the server's capabilities /// Iterate over all the server's capabilities
pub fn iter(&self) -> Iter<'_, Capability<'_>> { pub fn iter(&self) -> Iter<'_, Capability<'_>> {
self.0.iter() self.borrow_capabilities().iter()
} }
/// Returns how many capabilities the server has. /// Returns how many capabilities the server has.
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.0.len() self.borrow_capabilities().len()
} }
/// Returns true if the server purports to have no capabilities. /// Returns true if the server purports to have no capabilities.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.0.is_empty() self.borrow_capabilities().is_empty()
} }
} }

View file

@ -1,17 +1,98 @@
use super::{Flag, Seq, Uid}; use super::{Flag, Seq, Uid};
use crate::error::Error;
use crate::parse::{parse_many_into, MapOrNot};
use crate::types::UnsolicitedResponse;
use chrono::{DateTime, FixedOffset}; use chrono::{DateTime, FixedOffset};
use imap_proto::types::{AttributeValue, BodyStructure, Envelope, MessageSection, SectionPath}; use imap_proto::types::{
AttributeValue, BodyStructure, Envelope, MessageSection, Response, SectionPath,
};
use ouroboros::self_referencing;
use std::slice::Iter;
use std::sync::mpsc;
/// Format of Date and Time as defined RFC3501. /// Format of Date and Time as defined RFC3501.
/// See `date-time` element in [Formal Syntax](https://tools.ietf.org/html/rfc3501#section-9) /// See `date-time` element in [Formal Syntax](https://tools.ietf.org/html/rfc3501#section-9)
/// chapter of this RFC. /// chapter of this RFC.
const DATE_TIME_FORMAT: &str = "%d-%b-%Y %H:%M:%S %z"; const DATE_TIME_FORMAT: &str = "%d-%b-%Y %H:%M:%S %z";
/// A wrapper for one or more [`Fetch`] responses.
#[self_referencing]
pub struct Fetches {
data: Vec<u8>,
#[borrows(data)]
#[covariant]
pub(crate) fetches: Vec<Fetch<'this>>,
}
impl Fetches {
/// Parse one or more [`Fetch`] responses from a response buffer.
pub fn parse(
owned: Vec<u8>,
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Self, Error> {
FetchesTryBuilder {
data: owned,
fetches_builder: |input| {
let mut fetches = Vec::new();
parse_many_into(input, &mut fetches, unsolicited, |response| {
match response {
Response::Fetch(num, attrs) => {
let mut fetch = Fetch {
message: num,
flags: vec![],
uid: None,
size: None,
fetch: attrs,
};
// set some common fields eagerly
for attr in &fetch.fetch {
match attr {
AttributeValue::Flags(flags) => {
fetch.flags.extend(Flag::from_strs(flags));
}
AttributeValue::Uid(uid) => fetch.uid = Some(*uid),
AttributeValue::Rfc822Size(sz) => fetch.size = Some(*sz),
_ => {}
}
}
Ok(MapOrNot::Map(fetch))
}
resp => Ok(MapOrNot::Not(resp)),
}
})?;
Ok(fetches)
},
}
.try_build()
}
/// Iterate over the contained [`Fetch`]es.
pub fn iter(&self) -> Iter<'_, Fetch<'_>> {
self.borrow_fetches().iter()
}
/// Get the number of [`Fetch`]es in this container.
pub fn len(&self) -> usize {
self.borrow_fetches().len()
}
/// Return true if there are no [`Fetch`]es in the container.
pub fn is_empty(&self) -> bool {
self.borrow_fetches().is_empty()
}
/// Get the element at the given index
pub fn get(&self, index: usize) -> Option<&Fetch<'_>> {
self.borrow_fetches().get(index)
}
}
/// An IMAP [`FETCH` response](https://tools.ietf.org/html/rfc3501#section-7.4.2) that contains /// An IMAP [`FETCH` response](https://tools.ietf.org/html/rfc3501#section-7.4.2) that contains
/// data about a particular message. This response occurs as the result of a `FETCH` or `STORE` /// data about a particular message. This response occurs as the result of a `FETCH` or `STORE`
/// command, as well as by unilateral server decision (e.g., flag updates). /// command, as well as by unilateral server decision (e.g., flag updates).
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub struct Fetch { pub struct Fetch<'a> {
/// The ordinal number of this message in its containing mailbox. /// The ordinal number of this message in its containing mailbox.
pub message: Seq, pub message: Seq,
@ -24,16 +105,13 @@ pub struct Fetch {
/// Only present if `RFC822.SIZE` was specified in the query argument to `FETCH`. /// Only present if `RFC822.SIZE` was specified in the query argument to `FETCH`.
pub size: Option<u32>, pub size: Option<u32>,
// Note that none of these fields are *actually* 'static. Rather, they are tied to the lifetime pub(crate) fetch: Vec<AttributeValue<'a>>,
// of the `ZeroCopy` that contains this `Name`. That's also why they can't be public -- we can
// only return them with a lifetime tied to self.
pub(crate) fetch: Vec<AttributeValue<'static>>,
pub(crate) flags: Vec<Flag<'static>>, pub(crate) flags: Vec<Flag<'static>>,
} }
impl Fetch { impl<'a> Fetch<'a> {
/// A list of flags that are set for this message. /// A list of flags that are set for this message.
pub fn flags(&self) -> &[Flag<'_>] { pub fn flags(&self) -> &[Flag<'a>] {
&self.flags[..] &self.flags[..]
} }
@ -134,10 +212,21 @@ impl Fetch {
/// ///
/// See [section 2.3.6 of RFC 3501](https://tools.ietf.org/html/rfc3501#section-2.3.6) for /// See [section 2.3.6 of RFC 3501](https://tools.ietf.org/html/rfc3501#section-2.3.6) for
/// details. /// details.
pub fn bodystructure<'a>(&self) -> Option<&BodyStructure<'a>> { pub fn bodystructure(&self) -> Option<&BodyStructure<'a>> {
self.fetch.iter().find_map(|av| match av { self.fetch.iter().find_map(|av| match av {
AttributeValue::BodyStructure(bs) => Some(bs), AttributeValue::BodyStructure(bs) => Some(bs),
_ => None, _ => None,
}) })
} }
/// Get an owned copy of the [`Fetch`].
pub fn into_owned(self) -> Fetch<'static> {
Fetch {
message: self.message,
uid: self.uid,
size: self.size,
fetch: self.fetch.into_iter().map(|av| av.into_owned()).collect(),
flags: self.flags.clone(),
}
}
} }

124
src/types/flag.rs Normal file
View file

@ -0,0 +1,124 @@
use std::borrow::Cow;
/// With the exception of [`Flag::Custom`], these flags are system flags that are pre-defined in
/// [RFC 3501 section 2.3.2](https://tools.ietf.org/html/rfc3501#section-2.3.2). All system flags
/// begin with `\` in the IMAP protocol. Certain system flags (`\Deleted` and `\Seen`) have
/// special semantics described elsewhere.
///
/// A flag can be permanent or session-only on a per-flag basis. Permanent flags are those which
/// the client can add or remove from the message flags permanently; that is, concurrent and
/// subsequent sessions will see any change in permanent flags. Changes to session flags are valid
/// only in that session.
///
/// > Note: The `\Recent` system flag is a special case of a session flag. `\Recent` can not be
/// > used as an argument in a `STORE` or `APPEND` command, and thus can not be changed at all.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[non_exhaustive]
pub enum Flag<'a> {
/// Message has been read
Seen,
/// Message has been answered
Answered,
/// Message is "flagged" for urgent/special attention
Flagged,
/// Message is "deleted" for removal by later EXPUNGE
Deleted,
/// Message has not completed composition (marked as a draft).
Draft,
/// Message is "recently" arrived in this mailbox. This session is the first session to have
/// been notified about this message; if the session is read-write, subsequent sessions will
/// not see `\Recent` set for this message. This flag can not be altered by the client.
///
/// If it is not possible to determine whether or not this session is the first session to be
/// notified about a message, then that message will generally be considered recent.
///
/// If multiple connections have the same mailbox selected simultaneously, it is undefined
/// which of these connections will see newly-arrived messages with `\Recent` set and which
/// will see it without `\Recent` set.
Recent,
/// The [`Mailbox::permanent_flags`] can include this special flag (`\*`), which indicates that
/// it is possible to create new keywords by attempting to store those flags in the mailbox.
MayCreate,
/// A non-standard user- or server-defined flag.
Custom(Cow<'a, str>),
}
impl Flag<'static> {
fn system(s: &str) -> Option<Self> {
match s {
"\\Seen" => Some(Flag::Seen),
"\\Answered" => Some(Flag::Answered),
"\\Flagged" => Some(Flag::Flagged),
"\\Deleted" => Some(Flag::Deleted),
"\\Draft" => Some(Flag::Draft),
"\\Recent" => Some(Flag::Recent),
"\\*" => Some(Flag::MayCreate),
_ => None,
}
}
/// Helper function to transform Strings into owned Flags
pub fn from_strs<S: ToString>(
v: impl IntoIterator<Item = S>,
) -> impl Iterator<Item = Flag<'static>> {
v.into_iter().map(|s| Flag::from(s.to_string()))
}
}
impl<'a> Flag<'a> {
/// Get an owned version of the [`Flag`].
pub fn into_owned(self) -> Flag<'static> {
match self {
Flag::Custom(cow) => Flag::Custom(Cow::Owned(cow.into_owned())),
Flag::Seen => Flag::Seen,
Flag::Answered => Flag::Answered,
Flag::Flagged => Flag::Flagged,
Flag::Deleted => Flag::Deleted,
Flag::Draft => Flag::Draft,
Flag::Recent => Flag::Recent,
Flag::MayCreate => Flag::MayCreate,
}
}
}
impl<'a> std::fmt::Display for Flag<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Flag::Seen => write!(f, "\\Seen"),
Flag::Answered => write!(f, "\\Answered"),
Flag::Flagged => write!(f, "\\Flagged"),
Flag::Deleted => write!(f, "\\Deleted"),
Flag::Draft => write!(f, "\\Draft"),
Flag::Recent => write!(f, "\\Recent"),
Flag::MayCreate => write!(f, "\\*"),
Flag::Custom(ref s) => write!(f, "{}", s),
}
}
}
impl<'a> From<String> for Flag<'a> {
fn from(s: String) -> Self {
if let Some(f) = Flag::system(&s) {
f
} else {
Flag::Custom(Cow::Owned(s))
}
}
}
impl<'a> From<&'a str> for Flag<'a> {
fn from(s: &'a str) -> Self {
if let Some(f) = Flag::system(s) {
f
} else {
Flag::Custom(Cow::Borrowed(s))
}
}
}

View file

@ -1,7 +1,5 @@
//! This module contains types used throughout the IMAP protocol. //! This module contains types used throughout the IMAP protocol.
use std::borrow::Cow;
/// From section [2.3.1.1 of RFC 3501](https://tools.ietf.org/html/rfc3501#section-2.3.1.1). /// From section [2.3.1.1 of RFC 3501](https://tools.ietf.org/html/rfc3501#section-2.3.1.1).
/// ///
/// A 32-bit value assigned to each message, which when used with the unique identifier validity /// A 32-bit value assigned to each message, which when used with the unique identifier validity
@ -105,121 +103,17 @@ pub type Uid = u32;
/// messages which have greater UIDs. /// messages which have greater UIDs.
pub type Seq = u32; pub type Seq = u32;
/// With the exception of [`Flag::Custom`], these flags are system flags that are pre-defined in mod fetch;
/// [RFC 3501 section 2.3.2](https://tools.ietf.org/html/rfc3501#section-2.3.2). All system flags pub use self::fetch::{Fetch, Fetches};
/// begin with `\` in the IMAP protocol. Certain system flags (`\Deleted` and `\Seen`) have
/// special semantics described elsewhere.
///
/// A flag can be permanent or session-only on a per-flag basis. Permanent flags are those which
/// the client can add or remove from the message flags permanently; that is, concurrent and
/// subsequent sessions will see any change in permanent flags. Changes to session flags are valid
/// only in that session.
///
/// > Note: The `\Recent` system flag is a special case of a session flag. `\Recent` can not be
/// > used as an argument in a `STORE` or `APPEND` command, and thus can not be changed at all.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[non_exhaustive]
pub enum Flag<'a> {
/// Message has been read
Seen,
/// Message has been answered mod flag;
Answered, pub use self::flag::Flag;
/// Message is "flagged" for urgent/special attention
Flagged,
/// Message is "deleted" for removal by later EXPUNGE
Deleted,
/// Message has not completed composition (marked as a draft).
Draft,
/// Message is "recently" arrived in this mailbox. This session is the first session to have
/// been notified about this message; if the session is read-write, subsequent sessions will
/// not see `\Recent` set for this message. This flag can not be altered by the client.
///
/// If it is not possible to determine whether or not this session is the first session to be
/// notified about a message, then that message will generally be considered recent.
///
/// If multiple connections have the same mailbox selected simultaneously, it is undefined
/// which of these connections will see newly-arrived messages with `\Recent` set and which
/// will see it without `\Recent` set.
Recent,
/// The [`Mailbox::permanent_flags`] can include this special flag (`\*`), which indicates that
/// it is possible to create new keywords by attempting to store those flags in the mailbox.
MayCreate,
/// A non-standard user- or server-defined flag.
Custom(Cow<'a, str>),
}
impl Flag<'static> {
fn system(s: &str) -> Option<Self> {
match s {
"\\Seen" => Some(Flag::Seen),
"\\Answered" => Some(Flag::Answered),
"\\Flagged" => Some(Flag::Flagged),
"\\Deleted" => Some(Flag::Deleted),
"\\Draft" => Some(Flag::Draft),
"\\Recent" => Some(Flag::Recent),
"\\*" => Some(Flag::MayCreate),
_ => None,
}
}
/// Helper function to transform Strings into owned Flags
pub fn from_strs<S: ToString>(
v: impl IntoIterator<Item = S>,
) -> impl Iterator<Item = Flag<'static>> {
v.into_iter().map(|s| Flag::from(s.to_string()))
}
}
impl<'a> fmt::Display for Flag<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Flag::Seen => write!(f, "\\Seen"),
Flag::Answered => write!(f, "\\Answered"),
Flag::Flagged => write!(f, "\\Flagged"),
Flag::Deleted => write!(f, "\\Deleted"),
Flag::Draft => write!(f, "\\Draft"),
Flag::Recent => write!(f, "\\Recent"),
Flag::MayCreate => write!(f, "\\*"),
Flag::Custom(ref s) => write!(f, "{}", s),
}
}
}
impl<'a> From<String> for Flag<'a> {
fn from(s: String) -> Self {
if let Some(f) = Flag::system(&s) {
f
} else {
Flag::Custom(Cow::Owned(s))
}
}
}
impl<'a> From<&'a str> for Flag<'a> {
fn from(s: &'a str) -> Self {
if let Some(f) = Flag::system(s) {
f
} else {
Flag::Custom(Cow::Borrowed(s))
}
}
}
mod mailbox; mod mailbox;
pub use self::mailbox::Mailbox; pub use self::mailbox::Mailbox;
mod fetch;
pub use self::fetch::Fetch;
mod name; mod name;
pub use self::name::{Name, NameAttribute}; pub use self::name::{Name, NameAttribute, Names};
mod capabilities; mod capabilities;
pub use self::capabilities::Capabilities; pub use self::capabilities::Capabilities;
@ -229,129 +123,3 @@ pub use self::deleted::Deleted;
mod unsolicited_response; mod unsolicited_response;
pub use self::unsolicited_response::{AttributeValue, UnsolicitedResponse}; pub use self::unsolicited_response::{AttributeValue, UnsolicitedResponse};
/// This type wraps an input stream and a type that was constructed by parsing that input stream,
/// which allows the parsed type to refer to data in the underlying stream instead of copying it.
///
/// Any references given out by a `ZeroCopy` should never be used after the `ZeroCopy` is dropped.
pub struct ZeroCopy<D> {
_owned: Box<[u8]>,
derived: D,
}
impl<D> ZeroCopy<D> {
/// Derive a new `ZeroCopy` view of the byte data stored in `owned`.
///
/// # Safety
///
/// The `derive` callback will be passed a `&'static [u8]`. However, this reference is not, in
/// fact `'static`. Instead, it is only valid for as long as the `ZeroCopy` lives. Therefore,
/// it is *only* safe to call this function if *every* accessor on `D` returns either a type
/// that does not contain any borrows, *or* where the return type is bound to the lifetime of
/// `&self`.
///
/// It is *not* safe for the error type `E` to borrow from the passed reference.
pub(crate) unsafe fn make<F, E>(owned: Vec<u8>, derive: F) -> Result<Self, E>
where
F: FnOnce(&'static [u8]) -> Result<D, E>,
{
use std::mem;
// the memory pointed to by `owned` now has a stable address (on the heap).
// even if we move the `Box` (i.e., into `ZeroCopy`), a slice to it will remain valid.
let _owned = owned.into_boxed_slice();
// this is the unsafe part -- the implementor of `derive` must be aware that the reference
// they are passed is not *really* 'static, but rather the lifetime of `&self`.
let static_owned_ref: &'static [u8] = mem::transmute(&*_owned);
Ok(ZeroCopy {
_owned,
derived: derive(static_owned_ref)?,
})
}
/// Take out the derived value of this `ZeroCopy`.
///
/// Only safe if `D` contains no references into the underlying input stream (i.e., the `owned`
/// passed to `ZeroCopy::new`).
#[allow(dead_code)]
pub(crate) unsafe fn take(self) -> D {
self.derived
}
}
use super::error::Error;
pub(crate) type ZeroCopyResult<T> = Result<ZeroCopy<T>, Error>;
use std::ops::Deref;
impl<D> Deref for ZeroCopy<D> {
type Target = D;
fn deref(&self) -> &Self::Target {
&self.derived
}
}
// re-implement standard traits
// basically copied from Rc
impl<D: PartialEq> PartialEq for ZeroCopy<D> {
fn eq(&self, other: &ZeroCopy<D>) -> bool {
**self == **other
}
}
impl<D: Eq> Eq for ZeroCopy<D> {}
use std::cmp::Ordering;
impl<D: PartialOrd> PartialOrd for ZeroCopy<D> {
fn partial_cmp(&self, other: &ZeroCopy<D>) -> Option<Ordering> {
(**self).partial_cmp(&**other)
}
fn lt(&self, other: &ZeroCopy<D>) -> bool {
**self < **other
}
fn le(&self, other: &ZeroCopy<D>) -> bool {
**self <= **other
}
fn gt(&self, other: &ZeroCopy<D>) -> bool {
**self > **other
}
fn ge(&self, other: &ZeroCopy<D>) -> bool {
**self >= **other
}
}
impl<D: Ord> Ord for ZeroCopy<D> {
fn cmp(&self, other: &ZeroCopy<D>) -> Ordering {
(**self).cmp(&**other)
}
}
use std::hash::{Hash, Hasher};
impl<D: Hash> Hash for ZeroCopy<D> {
fn hash<H: Hasher>(&self, state: &mut H) {
(**self).hash(state);
}
}
use std::fmt;
impl<D: fmt::Display> fmt::Display for ZeroCopy<D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&**self, f)
}
}
impl<D: fmt::Debug> fmt::Debug for ZeroCopy<D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
impl<'a, D> IntoIterator for &'a ZeroCopy<D>
where
&'a D: IntoIterator,
{
type Item = <&'a D as IntoIterator>::Item;
type IntoIter = <&'a D as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
(**self).into_iter()
}
}

View file

@ -1,13 +1,76 @@
use crate::error::Error;
use crate::parse::{parse_many_into, MapOrNot};
use crate::types::UnsolicitedResponse;
use imap_proto::{MailboxDatum, Response};
use ouroboros::self_referencing;
use std::borrow::Cow; use std::borrow::Cow;
use std::slice::Iter;
use std::sync::mpsc;
/// A wrapper for one or more [`Name`] responses.
#[self_referencing]
pub struct Names {
data: Vec<u8>,
#[borrows(data)]
#[covariant]
pub(crate) names: Vec<Name<'this>>,
}
impl Names {
/// Parse one or more [`Name`] from a response buffer
pub fn parse(
owned: Vec<u8>,
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Self, Error> {
NamesTryBuilder {
data: owned,
names_builder: |input| {
let mut names = Vec::new();
parse_many_into(input, &mut names, unsolicited, |response| match response {
Response::MailboxData(MailboxDatum::List {
flags,
delimiter,
name,
}) => Ok(MapOrNot::Map(Name {
attributes: flags.into_iter().map(NameAttribute::from).collect(),
delimiter,
name,
})),
resp => Ok(MapOrNot::Not(resp)),
})?;
Ok(names)
},
}
.try_build()
}
/// Iterate over the contained [`Name`]s
pub fn iter(&self) -> Iter<'_, Name<'_>> {
self.borrow_names().iter()
}
/// Get the number of [`Name`]s in this container.
pub fn len(&self) -> usize {
self.borrow_names().len()
}
/// Return true of there are no [`Name`]s in the container.
pub fn is_empty(&self) -> bool {
self.borrow_names().is_empty()
}
/// Get the element at the given index
pub fn get(&self, index: usize) -> Option<&Name<'_>> {
self.borrow_names().get(index)
}
}
/// A name that matches a `LIST` or `LSUB` command. /// A name that matches a `LIST` or `LSUB` command.
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub struct Name { pub struct Name<'a> {
// Note that none of these fields are *actually* 'static. pub(crate) attributes: Vec<NameAttribute<'a>>,
// Rather, they are tied to the lifetime of the `ZeroCopy` that contains this `Name`. pub(crate) delimiter: Option<Cow<'a, str>>,
pub(crate) attributes: Vec<NameAttribute<'static>>, pub(crate) name: Cow<'a, str>,
pub(crate) delimiter: Option<Cow<'static, str>>,
pub(crate) name: Cow<'static, str>,
} }
/// An attribute set for an IMAP name. /// An attribute set for an IMAP name.
@ -46,6 +109,18 @@ impl NameAttribute<'static> {
} }
} }
impl<'a> NameAttribute<'a> {
fn into_owned(self) -> NameAttribute<'static> {
match self {
NameAttribute::NoInferiors => NameAttribute::NoInferiors,
NameAttribute::NoSelect => NameAttribute::NoSelect,
NameAttribute::Marked => NameAttribute::Marked,
NameAttribute::Unmarked => NameAttribute::Unmarked,
NameAttribute::Custom(cow) => NameAttribute::Custom(Cow::Owned(cow.into_owned())),
}
}
}
impl<'a> From<String> for NameAttribute<'a> { impl<'a> From<String> for NameAttribute<'a> {
fn from(s: String) -> Self { fn from(s: String) -> Self {
if let Some(f) = NameAttribute::system(&s) { if let Some(f) = NameAttribute::system(&s) {
@ -76,9 +151,9 @@ impl<'a> From<&'a str> for NameAttribute<'a> {
} }
} }
impl Name { impl<'a> Name<'a> {
/// Attributes of this name. /// Attributes of this name.
pub fn attributes(&self) -> &[NameAttribute<'_>] { pub fn attributes(&self) -> &[NameAttribute<'a>] {
&self.attributes[..] &self.attributes[..]
} }
@ -97,4 +172,17 @@ impl Name {
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
&*self.name &*self.name
} }
/// Get an owned version of this [`Name`].
pub fn into_owned(self) -> Name<'static> {
Name {
attributes: self
.attributes
.into_iter()
.map(|av| av.into_owned())
.collect(),
delimiter: self.delimiter.map(|cow| Cow::Owned(cow.into_owned())),
name: Cow::Owned(self.name.into_owned()),
}
}
} }

View file

@ -147,7 +147,7 @@ fn inbox() {
// let's see that we can also fetch the e-mails // let's see that we can also fetch the e-mails
let fetch = c.fetch("1", "(ALL UID)").unwrap(); let fetch = c.fetch("1", "(ALL UID)").unwrap();
assert_eq!(fetch.len(), 1); assert_eq!(fetch.len(), 1);
let fetch = &fetch[0]; let fetch = fetch.iter().next().unwrap();
assert_eq!(fetch.message, 1); assert_eq!(fetch.message, 1);
assert_ne!(fetch.uid, None); assert_ne!(fetch.uid, None);
assert_eq!(fetch.size, Some(138)); assert_eq!(fetch.size, Some(138));
@ -265,7 +265,7 @@ fn inbox_uid() {
// let's see that we can also fetch the e-mail // let's see that we can also fetch the e-mail
let fetch = c.uid_fetch(format!("{}", uid), "(ALL UID)").unwrap(); let fetch = c.uid_fetch(format!("{}", uid), "(ALL UID)").unwrap();
assert_eq!(fetch.len(), 1); assert_eq!(fetch.len(), 1);
let fetch = &fetch[0]; let fetch = fetch.iter().next().unwrap();
assert_eq!(fetch.uid, Some(uid)); assert_eq!(fetch.uid, Some(uid));
let e = fetch.envelope().unwrap(); let e = fetch.envelope().unwrap();
assert_eq!(e.subject, Some(b"My first e-mail"[..].into())); assert_eq!(e.subject, Some(b"My first e-mail"[..].into()));
@ -325,7 +325,7 @@ fn append() {
// fetch the e-mail // fetch the e-mail
let fetch = c.uid_fetch(format!("{}", uid), "(ALL UID)").unwrap(); let fetch = c.uid_fetch(format!("{}", uid), "(ALL UID)").unwrap();
assert_eq!(fetch.len(), 1); assert_eq!(fetch.len(), 1);
let fetch = &fetch[0]; let fetch = fetch.iter().next().unwrap();
assert_eq!(fetch.uid, Some(uid)); assert_eq!(fetch.uid, Some(uid));
let e = fetch.envelope().unwrap(); let e = fetch.envelope().unwrap();
assert_eq!(e.subject, Some(b"My second e-mail"[..].into())); assert_eq!(e.subject, Some(b"My second e-mail"[..].into()));
@ -376,7 +376,7 @@ fn append_with_flags() {
// fetch the e-mail // fetch the e-mail
let fetch = c.uid_fetch(format!("{}", uid), "(ALL UID)").unwrap(); let fetch = c.uid_fetch(format!("{}", uid), "(ALL UID)").unwrap();
assert_eq!(fetch.len(), 1); assert_eq!(fetch.len(), 1);
let fetch = &fetch[0]; let fetch = fetch.iter().next().unwrap();
assert_eq!(fetch.uid, Some(uid)); assert_eq!(fetch.uid, Some(uid));
let e = fetch.envelope().unwrap(); let e = fetch.envelope().unwrap();
assert_eq!(e.subject, Some(b"My third e-mail"[..].into())); assert_eq!(e.subject, Some(b"My third e-mail"[..].into()));
@ -436,7 +436,7 @@ fn append_with_flags_and_date() {
// fetch the e-mail // fetch the e-mail
let fetch = c.uid_fetch(format!("{}", uid), "(ALL UID)").unwrap(); let fetch = c.uid_fetch(format!("{}", uid), "(ALL UID)").unwrap();
assert_eq!(fetch.len(), 1); assert_eq!(fetch.len(), 1);
let fetch = &fetch[0]; let fetch = fetch.iter().next().unwrap();
assert_eq!(fetch.uid, Some(uid)); assert_eq!(fetch.uid, Some(uid));
assert_eq!(fetch.internal_date(), Some(date)); assert_eq!(fetch.internal_date(), Some(date));