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]
|
[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"
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
234
src/parse.rs
234
src/parse.rs
|
|
@ -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,105 +24,51 @@ 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.
|
||||||
|
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,
|
mut map: F,
|
||||||
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
|
) -> Result<()>
|
||||||
) -> ZeroCopyResult<Vec<T>>
|
|
||||||
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(());
|
||||||
break Ok(things);
|
}
|
||||||
|
|
||||||
|
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) {
|
break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
|
||||||
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())));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
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.
|
//! 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue