From 3f2331423cf8071e32218d8f2f0deb8cec8bf4d6 Mon Sep 17 00:00:00 2001 From: Todd Mortimer Date: Sat, 17 Jul 2021 16:32:26 -0400 Subject: [PATCH] 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. --- Cargo.toml | 2 +- src/client.rs | 28 ++--- src/parse.rs | 123 ++++++------------- src/types/capabilities.rs | 31 ++--- src/types/fetch.rs | 81 +++++++++++-- src/types/flag.rs | 108 +++++++++++++++++ src/types/mod.rs | 242 +------------------------------------- src/types/name.rs | 66 +++++++++-- 8 files changed, 299 insertions(+), 382 deletions(-) create mode 100644 src/types/flag.rs diff --git a/Cargo.toml b/Cargo.toml index a30a60f..caa9fc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "imap" -version = "3.0.0-alpha.4" +version = "3.0.0-alpha.5" authors = ["Jon Gjengset ", "Matt McCoy "] documentation = "https://docs.rs/imap/" diff --git a/src/client.rs b/src/client.rs index bfb6c8b..d556fcb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -565,39 +565,39 @@ impl Session { /// - `RFC822.HEADER`: Functionally equivalent to `BODY.PEEK[HEADER]`. /// - `RFC822.SIZE`: The [RFC-2822](https://tools.ietf.org/html/rfc2822) size of the message. /// - `UID`: The unique identifier for the message. - pub fn fetch(&mut self, sequence_set: S1, query: S2) -> ZeroCopyResult> + pub fn fetch(&mut self, sequence_set: S1, query: S2) -> Result where S1: AsRef, S2: AsRef, { if sequence_set.as_ref().is_empty() { - parse_fetches(vec![], &mut self.unsolicited_responses_tx) + Fetches::parse(vec![], &mut self.unsolicited_responses_tx) } else { self.run_command_and_read_response(&format!( "FETCH {} {}", validate_sequence_set(sequence_set.as_ref())?, validate_str_noquote(query.as_ref())? )) - .and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) + .and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx)) } } /// Equivalent to [`Session::fetch`], except that all identifiers in `uid_set` are /// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8). - pub fn uid_fetch(&mut self, uid_set: S1, query: S2) -> ZeroCopyResult> + pub fn uid_fetch(&mut self, uid_set: S1, query: S2) -> Result where S1: AsRef, S2: AsRef, { if uid_set.as_ref().is_empty() { - parse_fetches(vec![], &mut self.unsolicited_responses_tx) + Fetches::parse(vec![], &mut self.unsolicited_responses_tx) } else { self.run_command_and_read_response(&format!( "UID FETCH {} {}", validate_sequence_set(uid_set.as_ref())?, validate_str_noquote(query.as_ref())? )) - .and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) + .and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx)) } } @@ -834,7 +834,7 @@ impl Session { /// Ok(()) /// } /// ``` - pub fn store(&mut self, sequence_set: S1, query: S2) -> ZeroCopyResult> + pub fn store(&mut self, sequence_set: S1, query: S2) -> Result where S1: AsRef, S2: AsRef, @@ -844,12 +844,12 @@ impl Session { sequence_set.as_ref(), query.as_ref() )) - .and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) + .and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx)) } /// Equivalent to [`Session::store`], except that all identifiers in `sequence_set` are /// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8). - pub fn uid_store(&mut self, uid_set: S1, query: S2) -> ZeroCopyResult> + pub fn uid_store(&mut self, uid_set: S1, query: S2) -> Result where S1: AsRef, S2: AsRef, @@ -859,7 +859,7 @@ impl Session { uid_set.as_ref(), query.as_ref() )) - .and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) + .and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx)) } /// The [`COPY` command](https://tools.ietf.org/html/rfc3501#section-6.4.7) copies the @@ -988,13 +988,13 @@ impl Session { &mut self, reference_name: Option<&str>, mailbox_pattern: Option<&str>, - ) -> ZeroCopyResult> { + ) -> Result { self.run_command_and_read_response(&format!( "LIST {} {}", quote!(reference_name.unwrap_or("")), mailbox_pattern.unwrap_or("\"\"") )) - .and_then(|lines| parse_names(lines, &mut self.unsolicited_responses_tx)) + .and_then(|lines| Names::parse(lines, &mut self.unsolicited_responses_tx)) } /// The [`LSUB` command](https://tools.ietf.org/html/rfc3501#section-6.3.9) returns a subset of @@ -1016,13 +1016,13 @@ impl Session { &mut self, reference_name: Option<&str>, mailbox_pattern: Option<&str>, - ) -> ZeroCopyResult> { + ) -> Result { self.run_command_and_read_response(&format!( "LSUB {} {}", quote!(reference_name.unwrap_or("")), mailbox_pattern.unwrap_or("") )) - .and_then(|lines| parse_names(lines, &mut self.unsolicited_responses_tx)) + .and_then(|lines| Names::parse(lines, &mut self.unsolicited_responses_tx)) } /// The [`STATUS` command](https://tools.ietf.org/html/rfc3501#section-6.3.10) requests the diff --git a/src/parse.rs b/src/parse.rs index dee40f2..75729b7 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -3,6 +3,7 @@ use lazy_static::lazy_static; use regex::Regex; use std::collections::HashSet; use std::convert::TryFrom; +use std::iter::Extend; use std::sync::mpsc; use super::error::{Error, ParseError, Result}; @@ -23,105 +24,51 @@ pub fn parse_authenticate_response(line: &str) -> Result<&str> { ))) } -enum MapOrNot { +pub(crate) enum MapOrNot<'a, T> { Map(T), - Not(Response<'static>), + MapVec(Vec), + Not(Response<'a>), #[allow(dead_code)] Ignore, } -unsafe fn parse_many( - lines: Vec, +/// Parse many `T` Responses with `F` and extend `into` with them. +/// Responses other than `T` go into the `unsolicited` channel. +pub(crate) fn parse_many_into<'input, T, F>( + input: &'input [u8], + into: &mut impl Extend, + unsolicited: &mut mpsc::Sender, mut map: F, - unsolicited: &mut mpsc::Sender, -) -> ZeroCopyResult> +) -> Result<()> where - F: FnMut(Response<'static>) -> Result>, + F: FnMut(Response<'input>) -> Result>, { - let f = |mut lines: &'static [u8]| { - let mut things = Vec::new(); - loop { - if lines.is_empty() { - break Ok(things); + let mut lines = input; + loop { + if lines.is_empty() { + break Ok(()); + } + + match imap_proto::parser::parse_response(lines) { + Ok((rest, resp)) => { + lines = rest; + + match map(resp)? { + MapOrNot::Map(t) => into.extend(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) { - Ok((rest, resp)) => { - lines = rest; - - match map(resp)? { - MapOrNot::Map(t) => things.push(t), - MapOrNot::Not(resp) => match try_handle_unilateral(resp, unsolicited) { - Some(Response::Fetch(..)) => continue, - Some(resp) => break Err(resp.into()), - None => {} - }, - MapOrNot::Ignore => continue, - } - } - _ => { - break Err(Error::Parse(ParseError::Invalid(lines.to_vec()))); - } + _ => { + break Err(Error::Parse(ParseError::Invalid(lines.to_vec()))); } } - }; - - ZeroCopy::make(lines, f) -} - -pub fn parse_names( - lines: Vec, - unsolicited: &mut mpsc::Sender, -) -> ZeroCopyResult> { - 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, - unsolicited: &mut mpsc::Sender, -) -> ZeroCopyResult> { - 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( diff --git a/src/types/capabilities.rs b/src/types/capabilities.rs index df723fb..563c75f 100644 --- a/src/types/capabilities.rs +++ b/src/types/capabilities.rs @@ -1,5 +1,5 @@ -use crate::error::{Error, ParseError}; -use crate::parse::try_handle_unilateral; +use crate::error::Error; +use crate::parse::{parse_many_into, MapOrNot}; use crate::types::UnsolicitedResponse; use imap_proto::{Capability, Response}; use ouroboros::self_referencing; @@ -52,29 +52,12 @@ impl Capabilities { CapabilitiesTryBuilder { data: owned, capabilities_builder: |input| { - let mut lines = input; 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(caps); - } - } + 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() diff --git a/src/types/fetch.rs b/src/types/fetch.rs index 809664f..d30d57e 100644 --- a/src/types/fetch.rs +++ b/src/types/fetch.rs @@ -1,17 +1,83 @@ use super::{Flag, Seq, Uid}; +use crate::error::Error; +use crate::parse::{parse_many_into, MapOrNot}; +use crate::types::UnsolicitedResponse; use chrono::{DateTime, FixedOffset}; -use imap_proto::types::{AttributeValue, BodyStructure, Envelope, MessageSection, SectionPath}; +use imap_proto::types::{ + AttributeValue, BodyStructure, Envelope, MessageSection, Response, SectionPath, +}; +use ouroboros::self_referencing; +use std::slice::Iter; +use std::sync::mpsc; /// Format of Date and Time as defined RFC3501. /// See `date-time` element in [Formal Syntax](https://tools.ietf.org/html/rfc3501#section-9) /// chapter of this RFC. const DATE_TIME_FORMAT: &str = "%d-%b-%Y %H:%M:%S %z"; +/// A wrapper for one or more [`Fetch`] responses. +#[self_referencing] +pub struct Fetches { + data: Vec, + #[borrows(data)] + #[covariant] + pub(crate) fetches: Vec>, +} + +impl Fetches { + /// Parse one or more [`Fetch`] responses from a response buffer. + pub fn parse( + owned: Vec, + unsolicited: &mut mpsc::Sender, + ) -> Result { + 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 /// data about a particular message. This response occurs as the result of a `FETCH` or `STORE` /// command, as well as by unilateral server decision (e.g., flag updates). #[derive(Debug, Eq, PartialEq)] -pub struct Fetch { +pub struct Fetch<'a> { /// The ordinal number of this message in its containing mailbox. pub message: Seq, @@ -24,16 +90,13 @@ pub struct Fetch { /// Only present if `RFC822.SIZE` was specified in the query argument to `FETCH`. pub size: Option, - // Note that none of these fields are *actually* 'static. Rather, they are tied to the lifetime - // of the `ZeroCopy` that contains this `Name`. That's also why they can't be public -- we can - // only return them with a lifetime tied to self. - pub(crate) fetch: Vec>, + pub(crate) fetch: Vec>, pub(crate) flags: Vec>, } -impl Fetch { +impl<'a> Fetch<'a> { /// A list of flags that are set for this message. - pub fn flags(&self) -> &[Flag<'_>] { + pub fn flags(&self) -> &[Flag<'a>] { &self.flags[..] } @@ -134,7 +197,7 @@ impl Fetch { /// /// See [section 2.3.6 of RFC 3501](https://tools.ietf.org/html/rfc3501#section-2.3.6) for /// details. - pub fn bodystructure<'a>(&self) -> Option<&BodyStructure<'a>> { + pub fn bodystructure(&self) -> Option<&BodyStructure<'a>> { self.fetch.iter().find_map(|av| match av { AttributeValue::BodyStructure(bs) => Some(bs), _ => None, diff --git a/src/types/flag.rs b/src/types/flag.rs new file mode 100644 index 0000000..fed41ec --- /dev/null +++ b/src/types/flag.rs @@ -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 { + 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( + v: impl IntoIterator, + ) -> impl Iterator> { + 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 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)) + } + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index d175f43..ba04668 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,7 +1,5 @@ //! This module contains types used throughout the IMAP protocol. -use std::borrow::Cow; - /// From section [2.3.1.1 of RFC 3501](https://tools.ietf.org/html/rfc3501#section-2.3.1.1). /// /// A 32-bit value assigned to each message, which when used with the unique identifier validity @@ -105,121 +103,17 @@ pub type Uid = u32; /// messages which have greater UIDs. pub type Seq = u32; -/// With the exception of [`Flag::Custom`], these flags are system flags that are pre-defined in -/// [RFC 3501 section 2.3.2](https://tools.ietf.org/html/rfc3501#section-2.3.2). All system flags -/// begin with `\` in the IMAP protocol. Certain system flags (`\Deleted` and `\Seen`) have -/// special semantics described elsewhere. -/// -/// A flag can be permanent or session-only on a per-flag basis. Permanent flags are those which -/// the client can add or remove from the message flags permanently; that is, concurrent and -/// subsequent sessions will see any change in permanent flags. Changes to session flags are valid -/// only in that session. -/// -/// > Note: The `\Recent` system flag is a special case of a session flag. `\Recent` can not be -/// > used as an argument in a `STORE` or `APPEND` command, and thus can not be changed at all. -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -#[non_exhaustive] -pub enum Flag<'a> { - /// Message has been read - Seen, +mod fetch; +pub use self::fetch::{Fetch, Fetches}; - /// Message has been answered - Answered, - - /// Message is "flagged" for urgent/special attention - Flagged, - - /// Message is "deleted" for removal by later EXPUNGE - Deleted, - - /// Message has not completed composition (marked as a draft). - Draft, - - /// Message is "recently" arrived in this mailbox. This session is the first session to have - /// been notified about this message; if the session is read-write, subsequent sessions will - /// not see `\Recent` set for this message. This flag can not be altered by the client. - /// - /// If it is not possible to determine whether or not this session is the first session to be - /// notified about a message, then that message will generally be considered recent. - /// - /// If multiple connections have the same mailbox selected simultaneously, it is undefined - /// which of these connections will see newly-arrived messages with `\Recent` set and which - /// will see it without `\Recent` set. - Recent, - - /// The [`Mailbox::permanent_flags`] can include this special flag (`\*`), which indicates that - /// it is possible to create new keywords by attempting to store those flags in the mailbox. - MayCreate, - - /// A non-standard user- or server-defined flag. - Custom(Cow<'a, str>), -} - -impl Flag<'static> { - fn system(s: &str) -> Option { - 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( - v: impl IntoIterator, - ) -> impl Iterator> { - 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 for Flag<'a> { - fn from(s: String) -> Self { - if let Some(f) = Flag::system(&s) { - f - } else { - Flag::Custom(Cow::Owned(s)) - } - } -} - -impl<'a> From<&'a str> for Flag<'a> { - fn from(s: &'a str) -> Self { - if let Some(f) = Flag::system(s) { - f - } else { - Flag::Custom(Cow::Borrowed(s)) - } - } -} +mod flag; +pub use self::flag::Flag; mod mailbox; pub use self::mailbox::Mailbox; -mod fetch; -pub use self::fetch::Fetch; - mod name; -pub use self::name::{Name, NameAttribute}; +pub use self::name::{Name, NameAttribute, Names}; mod capabilities; pub use self::capabilities::Capabilities; @@ -229,129 +123,3 @@ pub use self::deleted::Deleted; mod unsolicited_response; pub use self::unsolicited_response::{AttributeValue, UnsolicitedResponse}; - -/// This type wraps an input stream and a type that was constructed by parsing that input stream, -/// which allows the parsed type to refer to data in the underlying stream instead of copying it. -/// -/// Any references given out by a `ZeroCopy` should never be used after the `ZeroCopy` is dropped. -pub struct ZeroCopy { - _owned: Box<[u8]>, - derived: D, -} - -impl ZeroCopy { - /// 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(owned: Vec, derive: F) -> Result - where - F: FnOnce(&'static [u8]) -> Result, - { - 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 = Result, Error>; - -use std::ops::Deref; -impl Deref for ZeroCopy { - type Target = D; - fn deref(&self) -> &Self::Target { - &self.derived - } -} - -// re-implement standard traits -// basically copied from Rc - -impl PartialEq for ZeroCopy { - fn eq(&self, other: &ZeroCopy) -> bool { - **self == **other - } -} -impl Eq for ZeroCopy {} - -use std::cmp::Ordering; -impl PartialOrd for ZeroCopy { - fn partial_cmp(&self, other: &ZeroCopy) -> Option { - (**self).partial_cmp(&**other) - } - fn lt(&self, other: &ZeroCopy) -> bool { - **self < **other - } - fn le(&self, other: &ZeroCopy) -> bool { - **self <= **other - } - fn gt(&self, other: &ZeroCopy) -> bool { - **self > **other - } - fn ge(&self, other: &ZeroCopy) -> bool { - **self >= **other - } -} -impl Ord for ZeroCopy { - fn cmp(&self, other: &ZeroCopy) -> Ordering { - (**self).cmp(&**other) - } -} - -use std::hash::{Hash, Hasher}; -impl Hash for ZeroCopy { - fn hash(&self, state: &mut H) { - (**self).hash(state); - } -} - -use std::fmt; -impl fmt::Display for ZeroCopy { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} -impl fmt::Debug for ZeroCopy { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl<'a, D> IntoIterator for &'a ZeroCopy -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() - } -} diff --git a/src/types/name.rs b/src/types/name.rs index 9196738..825de59 100644 --- a/src/types/name.rs +++ b/src/types/name.rs @@ -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::slice::Iter; +use std::sync::mpsc; + +/// A wrapper for one or more [`Name`] responses. +#[self_referencing] +pub struct Names { + data: Vec, + #[borrows(data)] + #[covariant] + pub(crate) names: Vec>, +} + +impl Names { + /// Parse one or more [`Name`] from a response buffer + pub fn parse( + owned: Vec, + unsolicited: &mut mpsc::Sender, + ) -> Result { + 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. #[derive(Debug, Eq, PartialEq)] -pub struct Name { - // Note that none of these fields are *actually* 'static. - // Rather, they are tied to the lifetime of the `ZeroCopy` that contains this `Name`. - pub(crate) attributes: Vec>, - pub(crate) delimiter: Option>, - pub(crate) name: Cow<'static, str>, +pub struct Name<'a> { + pub(crate) attributes: Vec>, + pub(crate) delimiter: Option>, + pub(crate) name: Cow<'a, str>, } /// An attribute set for an IMAP name. @@ -34,7 +82,7 @@ pub enum NameAttribute<'a> { Custom(Cow<'a, str>), } -impl NameAttribute<'static> { +impl<'a> NameAttribute<'a> { fn system(s: &str) -> Option { match s { "\\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. - pub fn attributes(&self) -> &[NameAttribute<'_>] { + pub fn attributes(&self) -> &[NameAttribute<'a>] { &self.attributes[..] }