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"
|
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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
/// 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
|
||||||
|
|
|
||||||
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(
|
pub fn parse_noop(
|
||||||
lines: Vec<u8>,
|
lines: Vec<u8>,
|
||||||
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
|
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
|
||||||
|
|
@ -456,7 +424,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 +442,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 +456,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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -605,7 +573,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 {
|
||||||
|
|
|
||||||
|
|
@ -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::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,54 @@ 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 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.
|
/// 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 +102,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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue