Skip to content

Commit c2d3aed

Browse files
committed
Convert Capabilities to ouroboros.
1 parent d86d1e2 commit c2d3aed

File tree

4 files changed

+60
-48
lines changed

4 files changed

+60
-48
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ nom = { version = "6.0", default-features = false }
2727
base64 = "0.13"
2828
chrono = { version = "0.4", default-features = false, features = ["std"]}
2929
lazy_static = "1.4"
30+
ouroboros = "0.9.5"
3031

3132
[dev-dependencies]
3233
lettre = "0.9"

src/client.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -717,9 +717,9 @@ impl<T: Read + Write> Session<T> {
717717
/// The [`CAPABILITY` command](https://tools.ietf.org/html/rfc3501#section-6.1.1) requests a
718718
/// listing of capabilities that the server supports. The server will include "IMAP4rev1" as
719719
/// one of the listed capabilities. See [`Capabilities`] for further details.
720-
pub fn capabilities(&mut self) -> ZeroCopyResult<Capabilities> {
720+
pub fn capabilities(&mut self) -> Result<Capabilities> {
721721
self.run_command_and_read_response("CAPABILITY")
722-
.and_then(|lines| parse_capabilities(lines, &mut self.unsolicited_responses_tx))
722+
.and_then(|lines| Capabilities::parse(lines, &mut self.unsolicited_responses_tx))
723723
}
724724

725725
/// The [`EXPUNGE` command](https://tools.ietf.org/html/rfc3501#section-6.4.3) permanently

src/parse.rs

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -169,38 +169,6 @@ pub fn parse_expunge(
169169
}
170170
}
171171

172-
pub fn parse_capabilities(
173-
lines: Vec<u8>,
174-
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
175-
) -> ZeroCopyResult<Capabilities> {
176-
let f = |mut lines| {
177-
let mut caps = HashSet::new();
178-
loop {
179-
match imap_proto::parser::parse_response(lines) {
180-
Ok((rest, Response::Capabilities(c))) => {
181-
lines = rest;
182-
caps.extend(c);
183-
}
184-
Ok((rest, data)) => {
185-
lines = rest;
186-
if let Some(resp) = try_handle_unilateral(data, unsolicited) {
187-
break Err(resp.into());
188-
}
189-
}
190-
_ => {
191-
break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
192-
}
193-
}
194-
195-
if lines.is_empty() {
196-
break Ok(Capabilities(caps));
197-
}
198-
}
199-
};
200-
201-
unsafe { ZeroCopy::make(lines, f) }
202-
}
203-
204172
pub fn parse_noop(
205173
lines: Vec<u8>,
206174
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
@@ -456,7 +424,7 @@ mod tests {
456424
];
457425
let lines = b"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n";
458426
let (mut send, recv) = mpsc::channel();
459-
let capabilities = parse_capabilities(lines.to_vec(), &mut send).unwrap();
427+
let capabilities = Capabilities::parse(lines.to_vec(), &mut send).unwrap();
460428
// shouldn't be any unexpected responses parsed
461429
assert!(recv.try_recv().is_err());
462430
assert_eq!(capabilities.len(), 4);
@@ -474,7 +442,7 @@ mod tests {
474442
];
475443
let lines = b"* CAPABILITY IMAP4REV1 STARTTLS\r\n";
476444
let (mut send, recv) = mpsc::channel();
477-
let capabilities = parse_capabilities(lines.to_vec(), &mut send).unwrap();
445+
let capabilities = Capabilities::parse(lines.to_vec(), &mut send).unwrap();
478446
// shouldn't be any unexpected responses parsed
479447
assert!(recv.try_recv().is_err());
480448
assert_eq!(capabilities.len(), 2);
@@ -488,7 +456,7 @@ mod tests {
488456
fn parse_capability_invalid_test() {
489457
let (mut send, recv) = mpsc::channel();
490458
let lines = b"* JUNK IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n";
491-
parse_capabilities(lines.to_vec(), &mut send).unwrap();
459+
Capabilities::parse(lines.to_vec(), &mut send).unwrap();
492460
assert!(recv.try_recv().is_err());
493461
}
494462

@@ -605,7 +573,7 @@ mod tests {
605573
* STATUS dev.github (MESSAGES 10 UIDNEXT 11 UIDVALIDITY 1408806928 UNSEEN 0)\r\n\
606574
* 4 EXISTS\r\n";
607575
let (mut send, recv) = mpsc::channel();
608-
let capabilities = parse_capabilities(lines.to_vec(), &mut send).unwrap();
576+
let capabilities = Capabilities::parse(lines.to_vec(), &mut send).unwrap();
609577

610578
assert_eq!(capabilities.len(), 4);
611579
for e in expected_capabilities {

src/types/capabilities.rs

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
use imap_proto::types::Capability;
1+
use crate::error::{Error, ParseError};
2+
use crate::parse::try_handle_unilateral;
3+
use crate::types::UnsolicitedResponse;
4+
use imap_proto::{Capability, Response};
5+
use ouroboros::self_referencing;
26
use std::collections::hash_set::Iter;
37
use std::collections::HashSet;
8+
use std::sync::mpsc;
49

510
const IMAP4REV1_CAPABILITY: &str = "IMAP4rev1";
611
const AUTH_CAPABILITY_PREFIX: &str = "AUTH=";
@@ -30,16 +35,54 @@ const AUTH_CAPABILITY_PREFIX: &str = "AUTH=";
3035
///
3136
/// Client implementations SHOULD NOT require any capability name other than `IMAP4rev1`, and MUST
3237
/// ignore any unknown capability names.
33-
pub struct Capabilities(
34-
// Note that this field isn't *actually* 'static.
35-
// Rather, it is tied to the lifetime of the `ZeroCopy` that contains this `Name`.
36-
pub(crate) HashSet<Capability<'static>>,
37-
);
38+
#[self_referencing]
39+
pub struct Capabilities {
40+
data: Vec<u8>,
41+
#[borrows(data)]
42+
#[covariant]
43+
pub(crate) capabilities: HashSet<Capability<'this>>,
44+
}
3845

3946
impl Capabilities {
47+
/// Parse the given input into one or more [`Capabilitity`] responses.
48+
pub fn parse(
49+
owned: Vec<u8>,
50+
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
51+
) -> Result<Self, Error> {
52+
CapabilitiesTryBuilder {
53+
data: owned,
54+
capabilities_builder: |input| {
55+
let mut lines = input;
56+
let mut caps = HashSet::new();
57+
loop {
58+
match imap_proto::parser::parse_response(lines) {
59+
Ok((rest, Response::Capabilities(c))) => {
60+
lines = rest;
61+
caps.extend(c);
62+
}
63+
Ok((rest, data)) => {
64+
lines = rest;
65+
if let Some(resp) = try_handle_unilateral(data, unsolicited) {
66+
break Err(resp.into());
67+
}
68+
}
69+
_ => {
70+
break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
71+
}
72+
}
73+
74+
if lines.is_empty() {
75+
break Ok(caps);
76+
}
77+
}
78+
},
79+
}
80+
.try_build()
81+
}
82+
4083
/// Check if the server has the given capability.
4184
pub fn has<'a>(&self, cap: &Capability<'a>) -> bool {
42-
self.0.contains(cap)
85+
self.borrow_capabilities().contains(cap)
4386
}
4487

4588
/// Check if the server has the given capability via str.
@@ -59,16 +102,16 @@ impl Capabilities {
59102

60103
/// Iterate over all the server's capabilities
61104
pub fn iter(&self) -> Iter<'_, Capability<'_>> {
62-
self.0.iter()
105+
self.borrow_capabilities().iter()
63106
}
64107

65108
/// Returns how many capabilities the server has.
66109
pub fn len(&self) -> usize {
67-
self.0.len()
110+
self.borrow_capabilities().len()
68111
}
69112

70113
/// Returns true if the server purports to have no capabilities.
71114
pub fn is_empty(&self) -> bool {
72-
self.0.is_empty()
115+
self.borrow_capabilities().is_empty()
73116
}
74117
}

0 commit comments

Comments
 (0)