Convert Capabilities to ouroboros.
This commit is contained in:
parent
d86d1e228b
commit
c2d3aed978
4 changed files with 60 additions and 48 deletions
|
|
@ -27,6 +27,7 @@ nom = { version = "6.0", default-features = false }
|
|||
base64 = "0.13"
|
||||
chrono = { version = "0.4", default-features = false, features = ["std"]}
|
||||
lazy_static = "1.4"
|
||||
ouroboros = "0.9.5"
|
||||
|
||||
[dev-dependencies]
|
||||
lettre = "0.9"
|
||||
|
|
|
|||
|
|
@ -717,9 +717,9 @@ impl<T: Read + Write> Session<T> {
|
|||
/// The [`CAPABILITY` command](https://tools.ietf.org/html/rfc3501#section-6.1.1) requests a
|
||||
/// listing of capabilities that the server supports. The server will include "IMAP4rev1" as
|
||||
/// one of the listed capabilities. See [`Capabilities`] for further details.
|
||||
pub fn capabilities(&mut self) -> ZeroCopyResult<Capabilities> {
|
||||
pub fn capabilities(&mut self) -> Result<Capabilities> {
|
||||
self.run_command_and_read_response("CAPABILITY")
|
||||
.and_then(|lines| parse_capabilities(lines, &mut self.unsolicited_responses_tx))
|
||||
.and_then(|lines| Capabilities::parse(lines, &mut self.unsolicited_responses_tx))
|
||||
}
|
||||
|
||||
/// The [`EXPUNGE` command](https://tools.ietf.org/html/rfc3501#section-6.4.3) permanently
|
||||
|
|
|
|||
40
src/parse.rs
40
src/parse.rs
|
|
@ -169,38 +169,6 @@ pub fn parse_expunge(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn parse_capabilities(
|
||||
lines: Vec<u8>,
|
||||
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
|
||||
) -> ZeroCopyResult<Capabilities> {
|
||||
let f = |mut lines| {
|
||||
let mut caps = HashSet::new();
|
||||
loop {
|
||||
match imap_proto::parser::parse_response(lines) {
|
||||
Ok((rest, Response::Capabilities(c))) => {
|
||||
lines = rest;
|
||||
caps.extend(c);
|
||||
}
|
||||
Ok((rest, data)) => {
|
||||
lines = rest;
|
||||
if let Some(resp) = try_handle_unilateral(data, unsolicited) {
|
||||
break Err(resp.into());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
|
||||
}
|
||||
}
|
||||
|
||||
if lines.is_empty() {
|
||||
break Ok(Capabilities(caps));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
unsafe { ZeroCopy::make(lines, f) }
|
||||
}
|
||||
|
||||
pub fn parse_noop(
|
||||
lines: Vec<u8>,
|
||||
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
|
||||
|
|
@ -456,7 +424,7 @@ mod tests {
|
|||
];
|
||||
let lines = b"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n";
|
||||
let (mut send, recv) = mpsc::channel();
|
||||
let capabilities = parse_capabilities(lines.to_vec(), &mut send).unwrap();
|
||||
let capabilities = Capabilities::parse(lines.to_vec(), &mut send).unwrap();
|
||||
// shouldn't be any unexpected responses parsed
|
||||
assert!(recv.try_recv().is_err());
|
||||
assert_eq!(capabilities.len(), 4);
|
||||
|
|
@ -474,7 +442,7 @@ mod tests {
|
|||
];
|
||||
let lines = b"* CAPABILITY IMAP4REV1 STARTTLS\r\n";
|
||||
let (mut send, recv) = mpsc::channel();
|
||||
let capabilities = parse_capabilities(lines.to_vec(), &mut send).unwrap();
|
||||
let capabilities = Capabilities::parse(lines.to_vec(), &mut send).unwrap();
|
||||
// shouldn't be any unexpected responses parsed
|
||||
assert!(recv.try_recv().is_err());
|
||||
assert_eq!(capabilities.len(), 2);
|
||||
|
|
@ -488,7 +456,7 @@ mod tests {
|
|||
fn parse_capability_invalid_test() {
|
||||
let (mut send, recv) = mpsc::channel();
|
||||
let lines = b"* JUNK IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n";
|
||||
parse_capabilities(lines.to_vec(), &mut send).unwrap();
|
||||
Capabilities::parse(lines.to_vec(), &mut send).unwrap();
|
||||
assert!(recv.try_recv().is_err());
|
||||
}
|
||||
|
||||
|
|
@ -605,7 +573,7 @@ mod tests {
|
|||
* STATUS dev.github (MESSAGES 10 UIDNEXT 11 UIDVALIDITY 1408806928 UNSEEN 0)\r\n\
|
||||
* 4 EXISTS\r\n";
|
||||
let (mut send, recv) = mpsc::channel();
|
||||
let capabilities = parse_capabilities(lines.to_vec(), &mut send).unwrap();
|
||||
let capabilities = Capabilities::parse(lines.to_vec(), &mut send).unwrap();
|
||||
|
||||
assert_eq!(capabilities.len(), 4);
|
||||
for e in expected_capabilities {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
use imap_proto::types::Capability;
|
||||
use crate::error::{Error, ParseError};
|
||||
use crate::parse::try_handle_unilateral;
|
||||
use crate::types::UnsolicitedResponse;
|
||||
use imap_proto::{Capability, Response};
|
||||
use ouroboros::self_referencing;
|
||||
use std::collections::hash_set::Iter;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::mpsc;
|
||||
|
||||
const IMAP4REV1_CAPABILITY: &str = "IMAP4rev1";
|
||||
const AUTH_CAPABILITY_PREFIX: &str = "AUTH=";
|
||||
|
|
@ -30,16 +35,54 @@ const AUTH_CAPABILITY_PREFIX: &str = "AUTH=";
|
|||
///
|
||||
/// Client implementations SHOULD NOT require any capability name other than `IMAP4rev1`, and MUST
|
||||
/// ignore any unknown capability names.
|
||||
pub struct Capabilities(
|
||||
// Note that this field isn't *actually* 'static.
|
||||
// Rather, it is tied to the lifetime of the `ZeroCopy` that contains this `Name`.
|
||||
pub(crate) HashSet<Capability<'static>>,
|
||||
);
|
||||
#[self_referencing]
|
||||
pub struct Capabilities {
|
||||
data: Vec<u8>,
|
||||
#[borrows(data)]
|
||||
#[covariant]
|
||||
pub(crate) capabilities: HashSet<Capability<'this>>,
|
||||
}
|
||||
|
||||
impl Capabilities {
|
||||
/// Parse the given input into one or more [`Capabilitity`] responses.
|
||||
pub fn parse(
|
||||
owned: Vec<u8>,
|
||||
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
|
||||
) -> Result<Self, Error> {
|
||||
CapabilitiesTryBuilder {
|
||||
data: owned,
|
||||
capabilities_builder: |input| {
|
||||
let mut 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);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
.try_build()
|
||||
}
|
||||
|
||||
/// Check if the server has the given capability.
|
||||
pub fn has<'a>(&self, cap: &Capability<'a>) -> bool {
|
||||
self.0.contains(cap)
|
||||
self.borrow_capabilities().contains(cap)
|
||||
}
|
||||
|
||||
/// Check if the server has the given capability via str.
|
||||
|
|
@ -59,16 +102,16 @@ impl Capabilities {
|
|||
|
||||
/// Iterate over all the server's capabilities
|
||||
pub fn iter(&self) -> Iter<'_, Capability<'_>> {
|
||||
self.0.iter()
|
||||
self.borrow_capabilities().iter()
|
||||
}
|
||||
|
||||
/// Returns how many capabilities the server has.
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
self.borrow_capabilities().len()
|
||||
}
|
||||
|
||||
/// Returns true if the server purports to have no capabilities.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
self.borrow_capabilities().is_empty()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue