Merge pull request #208 from mordak/zero-copy
Convert ZeroCopy to ouroboros.
This commit is contained in:
commit
88417339f9
10 changed files with 461 additions and 443 deletions
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "imap"
|
||||
version = "3.0.0-alpha.4"
|
||||
version = "3.0.0-alpha.5"
|
||||
authors = ["Jon Gjengset <jon@thesquareplanet.com>",
|
||||
"Matt McCoy <mattnenterprise@yahoo.com>"]
|
||||
documentation = "https://docs.rs/imap/"
|
||||
|
|
@ -27,6 +27,7 @@ nom = { version = "6.0", default-features = false }
|
|||
base64 = "0.13"
|
||||
chrono = { version = "0.4", default-features = false, features = ["std"]}
|
||||
lazy_static = "1.4"
|
||||
ouroboros = "0.9.5"
|
||||
|
||||
[dev-dependencies]
|
||||
lettre = "0.9"
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ fn main() {
|
|||
|
||||
match imap_session.fetch("2", "body[text]") {
|
||||
Ok(msgs) => {
|
||||
for msg in &msgs {
|
||||
for msg in msgs.iter() {
|
||||
print!("{:?}", msg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -565,39 +565,39 @@ impl<T: Read + Write> Session<T> {
|
|||
/// - `RFC822.HEADER`: Functionally equivalent to `BODY.PEEK[HEADER]`.
|
||||
/// - `RFC822.SIZE`: The [RFC-2822](https://tools.ietf.org/html/rfc2822) size of 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
|
||||
S1: AsRef<str>,
|
||||
S2: AsRef<str>,
|
||||
{
|
||||
if sequence_set.as_ref().is_empty() {
|
||||
parse_fetches(vec![], &mut self.unsolicited_responses_tx)
|
||||
Fetches::parse(vec![], &mut self.unsolicited_responses_tx)
|
||||
} else {
|
||||
self.run_command_and_read_response(&format!(
|
||||
"FETCH {} {}",
|
||||
validate_sequence_set(sequence_set.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
|
||||
/// [`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
|
||||
S1: AsRef<str>,
|
||||
S2: AsRef<str>,
|
||||
{
|
||||
if uid_set.as_ref().is_empty() {
|
||||
parse_fetches(vec![], &mut self.unsolicited_responses_tx)
|
||||
Fetches::parse(vec![], &mut self.unsolicited_responses_tx)
|
||||
} else {
|
||||
self.run_command_and_read_response(&format!(
|
||||
"UID FETCH {} {}",
|
||||
validate_sequence_set(uid_set.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
|
||||
/// listing of capabilities that the server supports. The server will include "IMAP4rev1" as
|
||||
/// 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")
|
||||
.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
|
||||
|
|
@ -845,7 +845,7 @@ impl<T: Read + Write> Session<T> {
|
|||
/// 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
|
||||
S1: AsRef<str>,
|
||||
S2: AsRef<str>,
|
||||
|
|
@ -855,12 +855,12 @@ impl<T: Read + Write> Session<T> {
|
|||
sequence_set.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
|
||||
/// [`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
|
||||
S1: AsRef<str>,
|
||||
S2: AsRef<str>,
|
||||
|
|
@ -870,7 +870,7 @@ impl<T: Read + Write> Session<T> {
|
|||
uid_set.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
|
||||
|
|
@ -999,13 +999,13 @@ impl<T: Read + Write> Session<T> {
|
|||
&mut self,
|
||||
reference_name: Option<&str>,
|
||||
mailbox_pattern: Option<&str>,
|
||||
) -> ZeroCopyResult<Vec<Name>> {
|
||||
) -> Result<Names> {
|
||||
self.run_command_and_read_response(&format!(
|
||||
"LIST {} {}",
|
||||
quote!(reference_name.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
|
||||
|
|
@ -1027,13 +1027,13 @@ impl<T: Read + Write> Session<T> {
|
|||
&mut self,
|
||||
reference_name: Option<&str>,
|
||||
mailbox_pattern: Option<&str>,
|
||||
) -> ZeroCopyResult<Vec<Name>> {
|
||||
) -> Result<Names> {
|
||||
self.run_command_and_read_response(&format!(
|
||||
"LSUB {} {}",
|
||||
quote!(reference_name.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
|
||||
|
|
|
|||
234
src/parse.rs
234
src/parse.rs
|
|
@ -3,6 +3,7 @@ use lazy_static::lazy_static;
|
|||
use regex::Regex;
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryFrom;
|
||||
use std::iter::Extend;
|
||||
use std::sync::mpsc;
|
||||
|
||||
use super::error::{Error, ParseError, Result};
|
||||
|
|
@ -23,105 +24,51 @@ pub fn parse_authenticate_response(line: &str) -> Result<&str> {
|
|||
)))
|
||||
}
|
||||
|
||||
enum MapOrNot<T> {
|
||||
pub(crate) enum MapOrNot<'a, T> {
|
||||
Map(T),
|
||||
Not(Response<'static>),
|
||||
MapVec(Vec<T>),
|
||||
Not(Response<'a>),
|
||||
#[allow(dead_code)]
|
||||
Ignore,
|
||||
}
|
||||
|
||||
unsafe fn parse_many<T, F>(
|
||||
lines: Vec<u8>,
|
||||
/// Parse many `T` Responses with `F` and extend `into` with them.
|
||||
/// Responses other than `T` go into the `unsolicited` channel.
|
||||
pub(crate) fn parse_many_into<'input, T, F>(
|
||||
input: &'input [u8],
|
||||
into: &mut impl Extend<T>,
|
||||
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
|
||||
mut map: F,
|
||||
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
|
||||
) -> ZeroCopyResult<Vec<T>>
|
||||
) -> Result<()>
|
||||
where
|
||||
F: FnMut(Response<'static>) -> Result<MapOrNot<T>>,
|
||||
F: FnMut(Response<'input>) -> Result<MapOrNot<'input, T>>,
|
||||
{
|
||||
let f = |mut lines: &'static [u8]| {
|
||||
let mut things = Vec::new();
|
||||
loop {
|
||||
if lines.is_empty() {
|
||||
break Ok(things);
|
||||
let mut lines = input;
|
||||
loop {
|
||||
if lines.is_empty() {
|
||||
break Ok(());
|
||||
}
|
||||
|
||||
match imap_proto::parser::parse_response(lines) {
|
||||
Ok((rest, resp)) => {
|
||||
lines = rest;
|
||||
|
||||
match map(resp)? {
|
||||
MapOrNot::Map(t) => into.extend(std::iter::once(t)),
|
||||
MapOrNot::MapVec(t) => into.extend(t),
|
||||
MapOrNot::Not(resp) => match try_handle_unilateral(resp, unsolicited) {
|
||||
Some(Response::Fetch(..)) => continue,
|
||||
Some(resp) => break Err(resp.into()),
|
||||
None => {}
|
||||
},
|
||||
MapOrNot::Ignore => continue,
|
||||
}
|
||||
}
|
||||
|
||||
match imap_proto::parser::parse_response(lines) {
|
||||
Ok((rest, resp)) => {
|
||||
lines = rest;
|
||||
|
||||
match map(resp)? {
|
||||
MapOrNot::Map(t) => things.push(t),
|
||||
MapOrNot::Not(resp) => match try_handle_unilateral(resp, unsolicited) {
|
||||
Some(Response::Fetch(..)) => continue,
|
||||
Some(resp) => break Err(resp.into()),
|
||||
None => {}
|
||||
},
|
||||
MapOrNot::Ignore => continue,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
|
||||
}
|
||||
_ => {
|
||||
break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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(
|
||||
|
|
@ -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(
|
||||
lines: Vec<u8>,
|
||||
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
|
||||
|
|
@ -456,7 +371,7 @@ mod tests {
|
|||
];
|
||||
let lines = b"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n";
|
||||
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
|
||||
assert!(recv.try_recv().is_err());
|
||||
assert_eq!(capabilities.len(), 4);
|
||||
|
|
@ -474,7 +389,7 @@ mod tests {
|
|||
];
|
||||
let lines = b"* CAPABILITY IMAP4REV1 STARTTLS\r\n";
|
||||
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
|
||||
assert!(recv.try_recv().is_err());
|
||||
assert_eq!(capabilities.len(), 2);
|
||||
|
|
@ -488,7 +403,7 @@ mod tests {
|
|||
fn parse_capability_invalid_test() {
|
||||
let (mut send, recv) = mpsc::channel();
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
@ -496,22 +411,23 @@ mod tests {
|
|||
fn parse_names_test() {
|
||||
let lines = b"* LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n";
|
||||
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_eq!(names.len(), 1);
|
||||
let first = names.get(0).unwrap();
|
||||
assert_eq!(
|
||||
names[0].attributes(),
|
||||
first.attributes(),
|
||||
&[NameAttribute::from("\\HasNoChildren")]
|
||||
);
|
||||
assert_eq!(names[0].delimiter(), Some("."));
|
||||
assert_eq!(names[0].name(), "INBOX");
|
||||
assert_eq!(first.delimiter(), Some("."));
|
||||
assert_eq!(first.name(), "INBOX");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_fetches_empty() {
|
||||
let lines = b"";
|
||||
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!(fetches.is_empty());
|
||||
}
|
||||
|
|
@ -522,19 +438,21 @@ mod tests {
|
|||
* 24 FETCH (FLAGS (\\Seen) UID 4827943)\r\n\
|
||||
* 25 FETCH (FLAGS (\\Seen))\r\n";
|
||||
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_eq!(fetches.len(), 2);
|
||||
assert_eq!(fetches[0].message, 24);
|
||||
assert_eq!(fetches[0].flags(), &[Flag::Seen]);
|
||||
assert_eq!(fetches[0].uid, Some(4827943));
|
||||
assert_eq!(fetches[0].body(), None);
|
||||
assert_eq!(fetches[0].header(), None);
|
||||
assert_eq!(fetches[1].message, 25);
|
||||
assert_eq!(fetches[1].flags(), &[Flag::Seen]);
|
||||
assert_eq!(fetches[1].uid, None);
|
||||
assert_eq!(fetches[1].body(), None);
|
||||
assert_eq!(fetches[1].header(), None);
|
||||
let first = fetches.get(0).unwrap();
|
||||
assert_eq!(first.message, 24);
|
||||
assert_eq!(first.flags(), &[Flag::Seen]);
|
||||
assert_eq!(first.uid, Some(4827943));
|
||||
assert_eq!(first.body(), None);
|
||||
assert_eq!(first.header(), None);
|
||||
let second = fetches.get(1).unwrap();
|
||||
assert_eq!(second.message, 25);
|
||||
assert_eq!(second.flags(), &[Flag::Seen]);
|
||||
assert_eq!(second.uid, None);
|
||||
assert_eq!(second.body(), None);
|
||||
assert_eq!(second.header(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -544,11 +462,12 @@ mod tests {
|
|||
* 37 FETCH (UID 74)\r\n\
|
||||
* 1 RECENT\r\n";
|
||||
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!(fetches.len(), 1);
|
||||
assert_eq!(fetches[0].message, 37);
|
||||
assert_eq!(fetches[0].uid, Some(74));
|
||||
let first = fetches.get(0).unwrap();
|
||||
assert_eq!(first.message, 37);
|
||||
assert_eq!(first.uid, Some(74));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -560,7 +479,7 @@ mod tests {
|
|||
* OK Searched 91% of the mailbox, ETA 0:01\r\n\
|
||||
* 37 FETCH (UID 74)\r\n";
|
||||
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::Ok {
|
||||
|
|
@ -569,8 +488,9 @@ mod tests {
|
|||
})
|
||||
);
|
||||
assert_eq!(fetches.len(), 1);
|
||||
assert_eq!(fetches[0].message, 37);
|
||||
assert_eq!(fetches[0].uid, Some(74));
|
||||
let first = fetches.get(0).unwrap();
|
||||
assert_eq!(first.message, 37);
|
||||
assert_eq!(first.uid, Some(74));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -579,17 +499,18 @@ mod tests {
|
|||
* LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n\
|
||||
* 4 EXPUNGE\r\n";
|
||||
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!(names.len(), 1);
|
||||
let first = names.get(0).unwrap();
|
||||
assert_eq!(
|
||||
names[0].attributes(),
|
||||
first.attributes(),
|
||||
&[NameAttribute::from("\\HasNoChildren")]
|
||||
);
|
||||
assert_eq!(names[0].delimiter(), Some("."));
|
||||
assert_eq!(names[0].name(), "INBOX");
|
||||
assert_eq!(first.delimiter(), Some("."));
|
||||
assert_eq!(first.name(), "INBOX");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -605,7 +526,7 @@ mod tests {
|
|||
* STATUS dev.github (MESSAGES 10 UIDNEXT 11 UIDVALIDITY 1408806928 UNSEEN 0)\r\n\
|
||||
* 4 EXISTS\r\n";
|
||||
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);
|
||||
for e in expected_capabilities {
|
||||
|
|
@ -720,7 +641,7 @@ mod tests {
|
|||
let lines = b"* VANISHED (EARLIER) 3:8,12,50:60\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() {
|
||||
UnsolicitedResponse::Vanished { earlier, uids } => {
|
||||
assert!(earlier);
|
||||
|
|
@ -736,10 +657,11 @@ mod tests {
|
|||
}
|
||||
assert!(recv.try_recv().is_err());
|
||||
assert_eq!(fetches.len(), 1);
|
||||
assert_eq!(fetches[0].message, 49);
|
||||
assert_eq!(fetches[0].flags(), &[Flag::Seen, Flag::Answered]);
|
||||
assert_eq!(fetches[0].uid, Some(117));
|
||||
assert_eq!(fetches[0].body(), None);
|
||||
assert_eq!(fetches[0].header(), None);
|
||||
let first = fetches.get(0).unwrap();
|
||||
assert_eq!(first.message, 49);
|
||||
assert_eq!(first.flags(), &[Flag::Seen, Flag::Answered]);
|
||||
assert_eq!(first.uid, Some(117));
|
||||
assert_eq!(first.body(), None);
|
||||
assert_eq!(first.header(), None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::HashSet;
|
||||
use std::sync::mpsc;
|
||||
|
||||
const IMAP4REV1_CAPABILITY: &str = "IMAP4rev1";
|
||||
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
|
||||
/// ignore any unknown capability names.
|
||||
pub struct Capabilities(
|
||||
// Note that this field isn't *actually* 'static.
|
||||
// Rather, it is tied to the lifetime of the `ZeroCopy` that contains this `Name`.
|
||||
pub(crate) HashSet<Capability<'static>>,
|
||||
);
|
||||
#[self_referencing]
|
||||
pub struct Capabilities {
|
||||
data: Vec<u8>,
|
||||
#[borrows(data)]
|
||||
#[covariant]
|
||||
pub(crate) capabilities: HashSet<Capability<'this>>,
|
||||
}
|
||||
|
||||
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.
|
||||
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.
|
||||
|
|
@ -59,16 +85,16 @@ impl Capabilities {
|
|||
|
||||
/// Iterate over all the server's capabilities
|
||||
pub fn iter(&self) -> Iter<'_, Capability<'_>> {
|
||||
self.0.iter()
|
||||
self.borrow_capabilities().iter()
|
||||
}
|
||||
|
||||
/// Returns how many capabilities the server has.
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
self.borrow_capabilities().len()
|
||||
}
|
||||
|
||||
/// Returns true if the server purports to have no capabilities.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
self.borrow_capabilities().is_empty()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,98 @@
|
|||
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 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.
|
||||
/// See `date-time` element in [Formal Syntax](https://tools.ietf.org/html/rfc3501#section-9)
|
||||
/// chapter of this RFC.
|
||||
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
|
||||
/// 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).
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Fetch {
|
||||
pub struct Fetch<'a> {
|
||||
/// The ordinal number of this message in its containing mailbox.
|
||||
pub message: Seq,
|
||||
|
||||
|
|
@ -24,16 +105,13 @@ pub struct Fetch {
|
|||
/// Only present if `RFC822.SIZE` was specified in the query argument to `FETCH`.
|
||||
pub size: Option<u32>,
|
||||
|
||||
// Note that none of these fields are *actually* 'static. Rather, they are tied to the lifetime
|
||||
// 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) fetch: Vec<AttributeValue<'a>>,
|
||||
pub(crate) flags: Vec<Flag<'static>>,
|
||||
}
|
||||
|
||||
impl Fetch {
|
||||
impl<'a> Fetch<'a> {
|
||||
/// A list of flags that are set for this message.
|
||||
pub fn flags(&self) -> &[Flag<'_>] {
|
||||
pub fn flags(&self) -> &[Flag<'a>] {
|
||||
&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
|
||||
/// details.
|
||||
pub fn bodystructure<'a>(&self) -> Option<&BodyStructure<'a>> {
|
||||
pub fn bodystructure(&self) -> Option<&BodyStructure<'a>> {
|
||||
self.fetch.iter().find_map(|av| match av {
|
||||
AttributeValue::BodyStructure(bs) => Some(bs),
|
||||
_ => 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
124
src/types/flag.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
242
src/types/mod.rs
242
src/types/mod.rs
|
|
@ -1,7 +1,5 @@
|
|||
//! 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).
|
||||
///
|
||||
/// 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.
|
||||
pub type Seq = u32;
|
||||
|
||||
/// 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,
|
||||
mod fetch;
|
||||
pub use self::fetch::{Fetch, Fetches};
|
||||
|
||||
/// 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> 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 flag;
|
||||
pub use self::flag::Flag;
|
||||
|
||||
mod mailbox;
|
||||
pub use self::mailbox::Mailbox;
|
||||
|
||||
mod fetch;
|
||||
pub use self::fetch::Fetch;
|
||||
|
||||
mod name;
|
||||
pub use self::name::{Name, NameAttribute};
|
||||
pub use self::name::{Name, NameAttribute, Names};
|
||||
|
||||
mod capabilities;
|
||||
pub use self::capabilities::Capabilities;
|
||||
|
|
@ -229,129 +123,3 @@ pub use self::deleted::Deleted;
|
|||
|
||||
mod unsolicited_response;
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::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.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Name {
|
||||
// Note that none of these fields are *actually* 'static.
|
||||
// Rather, they are tied to the lifetime of the `ZeroCopy` that contains this `Name`.
|
||||
pub(crate) attributes: Vec<NameAttribute<'static>>,
|
||||
pub(crate) delimiter: Option<Cow<'static, str>>,
|
||||
pub(crate) name: Cow<'static, str>,
|
||||
pub struct Name<'a> {
|
||||
pub(crate) attributes: Vec<NameAttribute<'a>>,
|
||||
pub(crate) delimiter: Option<Cow<'a, str>>,
|
||||
pub(crate) name: Cow<'a, str>,
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
fn from(s: String) -> Self {
|
||||
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.
|
||||
pub fn attributes(&self) -> &[NameAttribute<'_>] {
|
||||
pub fn attributes(&self) -> &[NameAttribute<'a>] {
|
||||
&self.attributes[..]
|
||||
}
|
||||
|
||||
|
|
@ -97,4 +172,17 @@ impl Name {
|
|||
pub fn name(&self) -> &str {
|
||||
&*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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ fn inbox() {
|
|||
// let's see that we can also fetch the e-mails
|
||||
let fetch = c.fetch("1", "(ALL UID)").unwrap();
|
||||
assert_eq!(fetch.len(), 1);
|
||||
let fetch = &fetch[0];
|
||||
let fetch = fetch.iter().next().unwrap();
|
||||
assert_eq!(fetch.message, 1);
|
||||
assert_ne!(fetch.uid, None);
|
||||
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 fetch = c.uid_fetch(format!("{}", uid), "(ALL UID)").unwrap();
|
||||
assert_eq!(fetch.len(), 1);
|
||||
let fetch = &fetch[0];
|
||||
let fetch = fetch.iter().next().unwrap();
|
||||
assert_eq!(fetch.uid, Some(uid));
|
||||
let e = fetch.envelope().unwrap();
|
||||
assert_eq!(e.subject, Some(b"My first e-mail"[..].into()));
|
||||
|
|
@ -325,7 +325,7 @@ fn append() {
|
|||
// fetch the e-mail
|
||||
let fetch = c.uid_fetch(format!("{}", uid), "(ALL UID)").unwrap();
|
||||
assert_eq!(fetch.len(), 1);
|
||||
let fetch = &fetch[0];
|
||||
let fetch = fetch.iter().next().unwrap();
|
||||
assert_eq!(fetch.uid, Some(uid));
|
||||
let e = fetch.envelope().unwrap();
|
||||
assert_eq!(e.subject, Some(b"My second e-mail"[..].into()));
|
||||
|
|
@ -376,7 +376,7 @@ fn append_with_flags() {
|
|||
// fetch the e-mail
|
||||
let fetch = c.uid_fetch(format!("{}", uid), "(ALL UID)").unwrap();
|
||||
assert_eq!(fetch.len(), 1);
|
||||
let fetch = &fetch[0];
|
||||
let fetch = fetch.iter().next().unwrap();
|
||||
assert_eq!(fetch.uid, Some(uid));
|
||||
let e = fetch.envelope().unwrap();
|
||||
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
|
||||
let fetch = c.uid_fetch(format!("{}", uid), "(ALL UID)").unwrap();
|
||||
assert_eq!(fetch.len(), 1);
|
||||
let fetch = &fetch[0];
|
||||
let fetch = fetch.iter().next().unwrap();
|
||||
assert_eq!(fetch.uid, Some(uid));
|
||||
assert_eq!(fetch.internal_date(), Some(date));
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue