Rework Name and Fetch to use ouroboros.

Use a helper function in `parse_many_into` to support parsing into
any container that implements Extend. Refactor Capabilities to use it.

Delete ZeroCopy and associated bits.

Move Flag into it's own module in types.
This commit is contained in:
Todd Mortimer 2021-07-17 16:32:26 -04:00
parent c2d3aed978
commit 3f2331423c
8 changed files with 299 additions and 382 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "imap" name = "imap"
version = "3.0.0-alpha.4" version = "3.0.0-alpha.5"
authors = ["Jon Gjengset <jon@thesquareplanet.com>", authors = ["Jon Gjengset <jon@thesquareplanet.com>",
"Matt McCoy <mattnenterprise@yahoo.com>"] "Matt McCoy <mattnenterprise@yahoo.com>"]
documentation = "https://docs.rs/imap/" documentation = "https://docs.rs/imap/"

View file

@ -565,39 +565,39 @@ impl<T: Read + Write> Session<T> {
/// - `RFC822.HEADER`: Functionally equivalent to `BODY.PEEK[HEADER]`. /// - `RFC822.HEADER`: Functionally equivalent to `BODY.PEEK[HEADER]`.
/// - `RFC822.SIZE`: The [RFC-2822](https://tools.ietf.org/html/rfc2822) size of the message. /// - `RFC822.SIZE`: The [RFC-2822](https://tools.ietf.org/html/rfc2822) size of the message.
/// - `UID`: The unique identifier for the message. /// - `UID`: The unique identifier for the message.
pub fn fetch<S1, S2>(&mut self, sequence_set: S1, query: S2) -> ZeroCopyResult<Vec<Fetch>> pub fn fetch<S1, S2>(&mut self, sequence_set: S1, query: S2) -> Result<Fetches>
where where
S1: AsRef<str>, S1: AsRef<str>,
S2: AsRef<str>, S2: AsRef<str>,
{ {
if sequence_set.as_ref().is_empty() { if sequence_set.as_ref().is_empty() {
parse_fetches(vec![], &mut self.unsolicited_responses_tx) Fetches::parse(vec![], &mut self.unsolicited_responses_tx)
} else { } else {
self.run_command_and_read_response(&format!( self.run_command_and_read_response(&format!(
"FETCH {} {}", "FETCH {} {}",
validate_sequence_set(sequence_set.as_ref())?, validate_sequence_set(sequence_set.as_ref())?,
validate_str_noquote(query.as_ref())? validate_str_noquote(query.as_ref())?
)) ))
.and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) .and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx))
} }
} }
/// Equivalent to [`Session::fetch`], except that all identifiers in `uid_set` are /// Equivalent to [`Session::fetch`], except that all identifiers in `uid_set` are
/// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8). /// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8).
pub fn uid_fetch<S1, S2>(&mut self, uid_set: S1, query: S2) -> ZeroCopyResult<Vec<Fetch>> pub fn uid_fetch<S1, S2>(&mut self, uid_set: S1, query: S2) -> Result<Fetches>
where where
S1: AsRef<str>, S1: AsRef<str>,
S2: AsRef<str>, S2: AsRef<str>,
{ {
if uid_set.as_ref().is_empty() { if uid_set.as_ref().is_empty() {
parse_fetches(vec![], &mut self.unsolicited_responses_tx) Fetches::parse(vec![], &mut self.unsolicited_responses_tx)
} else { } else {
self.run_command_and_read_response(&format!( self.run_command_and_read_response(&format!(
"UID FETCH {} {}", "UID FETCH {} {}",
validate_sequence_set(uid_set.as_ref())?, validate_sequence_set(uid_set.as_ref())?,
validate_str_noquote(query.as_ref())? validate_str_noquote(query.as_ref())?
)) ))
.and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) .and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx))
} }
} }
@ -834,7 +834,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>,
@ -844,12 +844,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>,
@ -859,7 +859,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
@ -988,13 +988,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
@ -1016,13 +1016,13 @@ impl<T: Read + Write> Session<T> {
&mut self, &mut self,
reference_name: Option<&str>, reference_name: Option<&str>,
mailbox_pattern: Option<&str>, mailbox_pattern: Option<&str>,
) -> ZeroCopyResult<Vec<Name>> { ) -> Result<Names> {
self.run_command_and_read_response(&format!( self.run_command_and_read_response(&format!(
"LSUB {} {}", "LSUB {} {}",
quote!(reference_name.unwrap_or("")), quote!(reference_name.unwrap_or("")),
mailbox_pattern.unwrap_or("") mailbox_pattern.unwrap_or("")
)) ))
.and_then(|lines| parse_names(lines, &mut self.unsolicited_responses_tx)) .and_then(|lines| Names::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`STATUS` command](https://tools.ietf.org/html/rfc3501#section-6.3.10) requests the /// The [`STATUS` command](https://tools.ietf.org/html/rfc3501#section-6.3.10) requests the

View file

@ -3,6 +3,7 @@ use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use std::collections::HashSet; use std::collections::HashSet;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::iter::Extend;
use std::sync::mpsc; use std::sync::mpsc;
use super::error::{Error, ParseError, Result}; use super::error::{Error, ParseError, Result};
@ -23,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(Some(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(

View file

@ -1,5 +1,5 @@
use crate::error::{Error, ParseError}; use crate::error::Error;
use crate::parse::try_handle_unilateral; use crate::parse::{parse_many_into, MapOrNot};
use crate::types::UnsolicitedResponse; use crate::types::UnsolicitedResponse;
use imap_proto::{Capability, Response}; use imap_proto::{Capability, Response};
use ouroboros::self_referencing; use ouroboros::self_referencing;
@ -52,29 +52,12 @@ impl Capabilities {
CapabilitiesTryBuilder { CapabilitiesTryBuilder {
data: owned, data: owned,
capabilities_builder: |input| { capabilities_builder: |input| {
let mut lines = input;
let mut caps = HashSet::new(); let mut caps = HashSet::new();
loop { parse_many_into(input, &mut caps, unsolicited, |response| match response {
match imap_proto::parser::parse_response(lines) { Response::Capabilities(c) => Ok(MapOrNot::MapVec(c)),
Ok((rest, Response::Capabilities(c))) => { resp => Ok(MapOrNot::Not(resp)),
lines = rest; })?;
caps.extend(c); Ok(caps)
}
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(caps);
}
}
}, },
} }
.try_build() .try_build()

View file

@ -1,17 +1,83 @@
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()
}
}
/// 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 +90,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,7 +197,7 @@ 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,

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

@ -0,0 +1,108 @@
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> std::fmt::Display for Flag<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Flag::Seen => write!(f, "\\Seen"),
Flag::Answered => write!(f, "\\Answered"),
Flag::Flagged => write!(f, "\\Flagged"),
Flag::Deleted => write!(f, "\\Deleted"),
Flag::Draft => write!(f, "\\Draft"),
Flag::Recent => write!(f, "\\Recent"),
Flag::MayCreate => write!(f, "\\*"),
Flag::Custom(ref s) => write!(f, "{}", s),
}
}
}
impl<'a> From<String> for Flag<'a> {
fn from(s: String) -> Self {
if let Some(f) = Flag::system(&s) {
f
} else {
Flag::Custom(Cow::Owned(s))
}
}
}
impl<'a> From<&'a str> for Flag<'a> {
fn from(s: &'a str) -> Self {
if let Some(f) = Flag::system(s) {
f
} else {
Flag::Custom(Cow::Borrowed(s))
}
}
}

View file

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

View file

@ -1,13 +1,61 @@
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()
}
}
/// 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.
@ -34,7 +82,7 @@ pub enum NameAttribute<'a> {
Custom(Cow<'a, str>), Custom(Cow<'a, str>),
} }
impl NameAttribute<'static> { impl<'a> NameAttribute<'a> {
fn system(s: &str) -> Option<Self> { fn system(s: &str) -> Option<Self> {
match s { match s {
"\\Noinferiors" => Some(NameAttribute::NoInferiors), "\\Noinferiors" => Some(NameAttribute::NoInferiors),
@ -76,9 +124,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[..]
} }