Add structured results for all values using imap-proto (#58)
* First stab at structured types (#28) Tests currently fail due to djc/imap-proto#2. LSUB is also broken due to djc/imap-proto#4 (but we don't have tests for that atm). * Also parse out RFC822 fetch responses * Make all the things zero-copy * Also delegate IntoIterator for ZeroCopy ref * Fix UNSEEN and LSUB All tests now pass * Address @sanmai-NL comments * Correctly handle incomplete parser responses * No need for git dep anymore
This commit is contained in:
parent
dc21eae428
commit
590b80e3a6
12 changed files with 619 additions and 249 deletions
|
|
@ -28,6 +28,8 @@ path = "src/lib.rs"
|
|||
native-tls = "0.1"
|
||||
regex = "0.2"
|
||||
bufstream = "0.1"
|
||||
imap-proto = "0.3"
|
||||
nom = "3.2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
base64 = "0.7"
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ fn main() {
|
|||
|
||||
imap_socket.login("username", "password").unwrap();
|
||||
|
||||
match imap_socket.capability() {
|
||||
match imap_socket.capabilities() {
|
||||
Ok(capabilities) => for capability in capabilities.iter() {
|
||||
println!("{}", capability);
|
||||
},
|
||||
|
|
@ -31,8 +31,8 @@ fn main() {
|
|||
};
|
||||
|
||||
match imap_socket.fetch("2", "body[text]") {
|
||||
Ok(lines) => for line in lines.iter() {
|
||||
print!("{}", line);
|
||||
Ok(msgs) => for msg in &msgs {
|
||||
print!("{:?}", msg);
|
||||
},
|
||||
Err(e) => println!("Error Fetching email 2: {}", e),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ fn main() {
|
|||
};
|
||||
|
||||
match imap_socket.fetch("2", "body[text]") {
|
||||
Ok(lines) => for line in lines.iter() {
|
||||
print!("{}", line);
|
||||
Ok(msgs) => for msg in &msgs {
|
||||
print!("{:?}", msg);
|
||||
},
|
||||
Err(e) => println!("Error Fetching email 2: {}", e),
|
||||
};
|
||||
|
|
|
|||
290
src/client.rs
290
src/client.rs
|
|
@ -3,11 +3,12 @@ use native_tls::{TlsConnector, TlsStream};
|
|||
use std::io::{self, Read, Write};
|
||||
use std::time::Duration;
|
||||
use bufstream::BufStream;
|
||||
use nom::IResult;
|
||||
|
||||
use super::mailbox::Mailbox;
|
||||
use super::types::*;
|
||||
use super::authenticator::Authenticator;
|
||||
use super::parse::{parse_authenticate_response, parse_capability, parse_response,
|
||||
parse_response_ok, parse_select_or_examine};
|
||||
use super::parse::{parse_authenticate_response, parse_capabilities, parse_fetches, parse_mailbox,
|
||||
parse_names};
|
||||
use super::error::{Error, ParseError, Result, ValidateError};
|
||||
|
||||
static TAG_PREFIX: &'static str = "a";
|
||||
|
|
@ -88,27 +89,23 @@ impl<'a, T: Read + Write + 'a> IdleHandle<'a, T> {
|
|||
//
|
||||
// a) if there's an error, or
|
||||
// b) *after* we send DONE
|
||||
let tag = format!("{}{} ", TAG_PREFIX, self.client.tag);
|
||||
let raw_data = try!(self.client.readline());
|
||||
let line = String::from_utf8(raw_data).unwrap();
|
||||
if line.starts_with(&tag) {
|
||||
try!(parse_response(vec![line]));
|
||||
// We should *only* get a continuation on an error (i.e., it gives BAD or NO).
|
||||
unreachable!();
|
||||
} else if !line.starts_with("+") {
|
||||
return Err(Error::BadResponse(vec![line]));
|
||||
let mut v = Vec::new();
|
||||
try!(self.client.readline(&mut v));
|
||||
if v.starts_with(b"+") {
|
||||
self.done = false;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.done = false;
|
||||
Ok(())
|
||||
self.client.read_response_onto(&mut v)?;
|
||||
// We should *only* get a continuation on an error (i.e., it gives BAD or NO).
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn terminate(&mut self) -> Result<()> {
|
||||
if !self.done {
|
||||
self.done = true;
|
||||
try!(self.client.write_line(b"DONE"));
|
||||
let lines = try!(self.client.read_response());
|
||||
parse_response_ok(lines)
|
||||
self.client.read_response().map(|_| ())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -118,7 +115,8 @@ impl<'a, T: Read + Write + 'a> IdleHandle<'a, T> {
|
|||
///
|
||||
/// This is necessary so that we can keep using the inner `Client` in `wait_keepalive`.
|
||||
fn wait_inner(&mut self) -> Result<()> {
|
||||
match self.client.readline().map(|_| ()) {
|
||||
let mut v = Vec::new();
|
||||
match self.client.readline(&mut v).map(|_| ()) {
|
||||
Err(Error::Io(ref e))
|
||||
if e.kind() == io::ErrorKind::TimedOut || e.kind() == io::ErrorKind::WouldBlock =>
|
||||
{
|
||||
|
|
@ -272,7 +270,8 @@ impl<T: Read + Write> Client<T> {
|
|||
fn do_auth_handshake<A: Authenticator>(&mut self, authenticator: A) -> Result<()> {
|
||||
// TODO Clean up this code
|
||||
loop {
|
||||
let line = try!(self.readline());
|
||||
let mut line = Vec::new();
|
||||
try!(self.readline(&mut line));
|
||||
|
||||
if line.starts_with(b"+") {
|
||||
let data = try!(parse_authenticate_response(
|
||||
|
|
@ -281,14 +280,8 @@ impl<T: Read + Write> Client<T> {
|
|||
let auth_response = authenticator.process(data);
|
||||
|
||||
try!(self.write_line(auth_response.into_bytes().as_slice()))
|
||||
} else if line.starts_with(format!("{}{} ", TAG_PREFIX, self.tag).as_bytes()) {
|
||||
try!(parse_response(vec![String::from_utf8(line).unwrap()]));
|
||||
return Ok(());
|
||||
} else {
|
||||
let mut lines = try!(self.read_response());
|
||||
lines.insert(0, String::from_utf8(line).unwrap());
|
||||
try!(parse_response(lines.clone()));
|
||||
return Ok(());
|
||||
return self.read_response_onto(&mut line).map(|_| ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -304,27 +297,25 @@ impl<T: Read + Write> Client<T> {
|
|||
|
||||
/// Selects a mailbox
|
||||
pub fn select(&mut self, mailbox_name: &str) -> Result<Mailbox> {
|
||||
let lines = try!(
|
||||
self.run_command_and_read_response(&format!("SELECT {}", validate_str(mailbox_name)?))
|
||||
);
|
||||
parse_select_or_examine(lines)
|
||||
self.run_command_and_read_response(&format!("SELECT {}", validate_str(mailbox_name)?))
|
||||
.and_then(|lines| parse_mailbox(&lines[..]))
|
||||
}
|
||||
|
||||
/// Examine is identical to Select, but the selected mailbox is identified as read-only
|
||||
pub fn examine(&mut self, mailbox_name: &str) -> Result<Mailbox> {
|
||||
let lines = try!(
|
||||
self.run_command_and_read_response(&format!("EXAMINE {}", validate_str(mailbox_name)?))
|
||||
);
|
||||
parse_select_or_examine(lines)
|
||||
self.run_command_and_read_response(&format!("EXAMINE {}", validate_str(mailbox_name)?))
|
||||
.and_then(|lines| parse_mailbox(&lines[..]))
|
||||
}
|
||||
|
||||
/// Fetch retreives data associated with a message in the mailbox.
|
||||
pub fn fetch(&mut self, sequence_set: &str, query: &str) -> Result<Vec<String>> {
|
||||
pub fn fetch(&mut self, sequence_set: &str, query: &str) -> ZeroCopyResult<Vec<Fetch>> {
|
||||
self.run_command_and_read_response(&format!("FETCH {} {}", sequence_set, query))
|
||||
.and_then(|lines| parse_fetches(lines))
|
||||
}
|
||||
|
||||
pub fn uid_fetch(&mut self, uid_set: &str, query: &str) -> Result<Vec<String>> {
|
||||
pub fn uid_fetch(&mut self, uid_set: &str, query: &str) -> ZeroCopyResult<Vec<Fetch>> {
|
||||
self.run_command_and_read_response(&format!("UID FETCH {} {}", uid_set, query))
|
||||
.and_then(|lines| parse_fetches(lines))
|
||||
}
|
||||
|
||||
/// Noop always succeeds, and it does nothing.
|
||||
|
|
@ -369,9 +360,9 @@ impl<T: Read + Write> Client<T> {
|
|||
}
|
||||
|
||||
/// Capability requests a listing of capabilities that the server supports.
|
||||
pub fn capability(&mut self) -> Result<Vec<String>> {
|
||||
let lines = try!(self.run_command_and_read_response(&format!("CAPABILITY")));
|
||||
parse_capability(lines)
|
||||
pub fn capabilities(&mut self) -> ZeroCopyResult<Capabilities> {
|
||||
self.run_command_and_read_response(&format!("CAPABILITY"))
|
||||
.and_then(|lines| parse_capabilities(lines))
|
||||
}
|
||||
|
||||
/// Expunge permanently removes all messages that have the \Deleted flag set from the currently
|
||||
|
|
@ -392,12 +383,14 @@ impl<T: Read + Write> Client<T> {
|
|||
}
|
||||
|
||||
/// Store alters data associated with a message in the mailbox.
|
||||
pub fn store(&mut self, sequence_set: &str, query: &str) -> Result<Vec<String>> {
|
||||
pub fn store(&mut self, sequence_set: &str, query: &str) -> ZeroCopyResult<Vec<Fetch>> {
|
||||
self.run_command_and_read_response(&format!("STORE {} {}", sequence_set, query))
|
||||
.and_then(|lines| parse_fetches(lines))
|
||||
}
|
||||
|
||||
pub fn uid_store(&mut self, uid_set: &str, query: &str) -> Result<Vec<String>> {
|
||||
pub fn uid_store(&mut self, uid_set: &str, query: &str) -> ZeroCopyResult<Vec<Fetch>> {
|
||||
self.run_command_and_read_response(&format!("UID STORE {} {}", uid_set, query))
|
||||
.and_then(|lines| parse_fetches(lines))
|
||||
}
|
||||
|
||||
/// Copy copies the specified message to the end of the specified destination mailbox.
|
||||
|
|
@ -415,12 +408,12 @@ impl<T: Read + Write> Client<T> {
|
|||
&mut self,
|
||||
reference_name: &str,
|
||||
mailbox_search_pattern: &str,
|
||||
) -> Result<Vec<String>> {
|
||||
self.run_command_and_parse(&format!(
|
||||
) -> ZeroCopyResult<Vec<Name>> {
|
||||
self.run_command_and_read_response(&format!(
|
||||
"LIST {} {}",
|
||||
quote!(reference_name),
|
||||
mailbox_search_pattern
|
||||
))
|
||||
)).and_then(|lines| parse_names(lines))
|
||||
}
|
||||
|
||||
/// The LSUB command returns a subset of names from the set of names
|
||||
|
|
@ -429,17 +422,21 @@ impl<T: Read + Write> Client<T> {
|
|||
&mut self,
|
||||
reference_name: &str,
|
||||
mailbox_search_pattern: &str,
|
||||
) -> Result<Vec<String>> {
|
||||
self.run_command_and_parse(&format!(
|
||||
) -> ZeroCopyResult<Vec<Name>> {
|
||||
self.run_command_and_read_response(&format!(
|
||||
"LSUB {} {}",
|
||||
quote!(reference_name),
|
||||
mailbox_search_pattern
|
||||
))
|
||||
)).and_then(|lines| parse_names(lines))
|
||||
}
|
||||
|
||||
/// The STATUS command requests the status of the indicated mailbox.
|
||||
pub fn status(&mut self, mailbox_name: &str, status_data_items: &str) -> Result<Vec<String>> {
|
||||
self.run_command_and_parse(&format!("STATUS {} {}", mailbox_name, status_data_items))
|
||||
pub fn status(&mut self, mailbox_name: &str, status_data_items: &str) -> Result<Mailbox> {
|
||||
self.run_command_and_read_response(&format!(
|
||||
"STATUS {} {}",
|
||||
validate_str(mailbox_name)?,
|
||||
status_data_items
|
||||
)).and_then(|lines| parse_mailbox(&lines[..]))
|
||||
}
|
||||
|
||||
/// Returns a handle that can be used to block until the state of the currently selected
|
||||
|
|
@ -449,28 +446,24 @@ impl<T: Read + Write> Client<T> {
|
|||
}
|
||||
|
||||
/// The APPEND command adds a mail to a mailbox.
|
||||
pub fn append(&mut self, folder: &str, content: &[u8]) -> Result<Vec<String>> {
|
||||
try!(self.run_command(&format!("APPEND \"{}\" {{{}}}", folder, content.len())));
|
||||
let line = try!(self.readline());
|
||||
if !line.starts_with(b"+") {
|
||||
pub fn append(&mut self, folder: &str, content: &[u8]) -> Result<()> {
|
||||
try!(self.run_command(
|
||||
&format!("APPEND \"{}\" {{{}}}", folder, content.len())
|
||||
));
|
||||
let mut v = Vec::new();
|
||||
try!(self.readline(&mut v));
|
||||
if !v.starts_with(b"+") {
|
||||
return Err(Error::Append);
|
||||
}
|
||||
try!(self.stream.write_all(content));
|
||||
try!(self.stream.write_all(b"\r\n"));
|
||||
try!(self.stream.flush());
|
||||
self.read_response()
|
||||
self.read_response().map(|_| ())
|
||||
}
|
||||
|
||||
/// Runs a command and checks if it returns OK.
|
||||
pub fn run_command_and_check_ok(&mut self, command: &str) -> Result<()> {
|
||||
let lines = try!(self.run_command_and_read_response(command));
|
||||
parse_response_ok(lines)
|
||||
}
|
||||
|
||||
// Run a command and parse the status response.
|
||||
pub fn run_command_and_parse(&mut self, command: &str) -> Result<Vec<String>> {
|
||||
let lines = try!(self.run_command_and_read_response(command));
|
||||
parse_response(lines)
|
||||
self.run_command_and_read_response(command).map(|_| ())
|
||||
}
|
||||
|
||||
/// Runs any command passed to it.
|
||||
|
|
@ -479,49 +472,110 @@ impl<T: Read + Write> Client<T> {
|
|||
self.write_line(command.into_bytes().as_slice())
|
||||
}
|
||||
|
||||
pub fn run_command_and_read_response(&mut self, untagged_command: &str) -> Result<Vec<String>> {
|
||||
pub fn run_command_and_read_response(&mut self, untagged_command: &str) -> Result<Vec<u8>> {
|
||||
try!(self.run_command(untagged_command));
|
||||
self.read_response()
|
||||
}
|
||||
|
||||
fn read_response(&mut self) -> Result<Vec<String>> {
|
||||
let mut found_tag_line = false;
|
||||
let start_str = format!("{}{} ", TAG_PREFIX, self.tag);
|
||||
let mut lines: Vec<String> = Vec::new();
|
||||
fn read_response(&mut self) -> Result<Vec<u8>> {
|
||||
let mut v = Vec::new();
|
||||
self.read_response_onto(&mut v)?;
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
while !found_tag_line {
|
||||
let raw_data = try!(self.readline());
|
||||
let line = String::from_utf8(raw_data)
|
||||
.map_err(|err| Error::Parse(ParseError::DataNotUtf8(err)))?;
|
||||
lines.push(line.clone());
|
||||
if (&*line).starts_with(&*start_str) {
|
||||
found_tag_line = true;
|
||||
fn read_response_onto(&mut self, data: &mut Vec<u8>) -> Result<()> {
|
||||
let mut continue_from = None;
|
||||
let mut try_first = !data.is_empty();
|
||||
let match_tag = format!("{}{}", TAG_PREFIX, self.tag);
|
||||
loop {
|
||||
let line_start = if try_first {
|
||||
try_first = false;
|
||||
0
|
||||
} else {
|
||||
let start_new = data.len();
|
||||
try!(self.readline(data));
|
||||
continue_from.take().unwrap_or(start_new)
|
||||
};
|
||||
|
||||
let break_with = {
|
||||
use imap_proto::{parse_response, Response, Status};
|
||||
let line = &data[line_start..];
|
||||
|
||||
match parse_response(line) {
|
||||
IResult::Done(
|
||||
_,
|
||||
Response::Done {
|
||||
tag,
|
||||
status,
|
||||
information,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
assert_eq!(tag.as_bytes(), match_tag.as_bytes());
|
||||
Some(match status {
|
||||
Status::Bad | Status::No => {
|
||||
Err((status, information.map(|s| s.to_string())))
|
||||
}
|
||||
Status::Ok => Ok(()),
|
||||
status => Err((status, None)),
|
||||
})
|
||||
}
|
||||
IResult::Done(..) => None,
|
||||
IResult::Incomplete(..) => {
|
||||
continue_from = Some(line_start);
|
||||
None
|
||||
}
|
||||
_ => Some(Err((Status::Bye, None))),
|
||||
}
|
||||
};
|
||||
|
||||
match break_with {
|
||||
Some(Ok(_)) => {
|
||||
data.truncate(line_start);
|
||||
break Ok(());
|
||||
}
|
||||
Some(Err((status, expl))) => {
|
||||
use imap_proto::Status;
|
||||
match status {
|
||||
Status::Bad => {
|
||||
break Err(Error::BadResponse(
|
||||
expl.unwrap_or("no explanation given".to_string()),
|
||||
))
|
||||
}
|
||||
Status::No => {
|
||||
break Err(Error::NoResponse(
|
||||
expl.unwrap_or("no explanation given".to_string()),
|
||||
))
|
||||
}
|
||||
_ => break Err(Error::Parse(ParseError::Invalid(data.split_off(0)))),
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(lines)
|
||||
}
|
||||
|
||||
fn read_greeting(&mut self) -> Result<()> {
|
||||
try!(self.readline());
|
||||
let mut v = Vec::new();
|
||||
try!(self.readline(&mut v));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn readline(&mut self) -> Result<Vec<u8>> {
|
||||
fn readline(&mut self, into: &mut Vec<u8>) -> Result<usize> {
|
||||
use std::io::BufRead;
|
||||
let mut line_buffer: Vec<u8> = Vec::new();
|
||||
if try!(self.stream.read_until(LF, &mut line_buffer)) == 0 {
|
||||
let read = try!(self.stream.read_until(LF, into));
|
||||
if read == 0 {
|
||||
return Err(Error::ConnectionLost);
|
||||
}
|
||||
|
||||
if self.debug {
|
||||
// Remove CRLF
|
||||
let len = line_buffer.len();
|
||||
let line = &line_buffer[..(len - 2)];
|
||||
let len = into.len();
|
||||
let line = &into[(len - read - 2)..(len - 2)];
|
||||
print!("S: {}\n", String::from_utf8_lossy(line));
|
||||
}
|
||||
|
||||
Ok(line_buffer)
|
||||
Ok(read)
|
||||
}
|
||||
|
||||
fn create_command(&mut self, command: String) -> String {
|
||||
|
|
@ -545,20 +599,27 @@ impl<T: Read + Write> Client<T> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use super::super::mock_stream::MockStream;
|
||||
use super::super::mailbox::Mailbox;
|
||||
use super::super::error::Result;
|
||||
|
||||
#[test]
|
||||
fn read_response() {
|
||||
let response = "a0 OK Logged in.\r\n";
|
||||
let expected_response: Vec<String> = vec![response.to_string()];
|
||||
let mock_stream = MockStream::new(response.as_bytes().to_vec());
|
||||
let mut client = Client::new(mock_stream);
|
||||
let actual_response = client.read_response().unwrap();
|
||||
assert!(
|
||||
expected_response == actual_response,
|
||||
"expected response doesn't equal actual"
|
||||
);
|
||||
assert_eq!(Vec::<u8>::new(), actual_response);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn fetch_body() {
|
||||
let response = "a0 OK Logged in.\r\n\
|
||||
* 2 FETCH (BODY[TEXT] {3}\r\nfoo)\r\n\
|
||||
a0 OK FETCH completed\r\n";
|
||||
let mock_stream = MockStream::new(response.as_bytes().to_vec());
|
||||
let mut client = Client::new(mock_stream);
|
||||
client.read_response().unwrap();
|
||||
client.read_response().unwrap();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -578,7 +639,9 @@ mod tests {
|
|||
.with_buf(greeting.as_bytes().to_vec())
|
||||
.with_delay();
|
||||
let mut client = Client::new(mock_stream);
|
||||
let actual_response = String::from_utf8(client.readline().unwrap()).unwrap();
|
||||
let mut v = Vec::new();
|
||||
client.readline(&mut v).unwrap();
|
||||
let actual_response = String::from_utf8(v).unwrap();
|
||||
assert_eq!(expected_response, actual_response);
|
||||
}
|
||||
|
||||
|
|
@ -586,7 +649,8 @@ mod tests {
|
|||
fn readline_eof() {
|
||||
let mock_stream = MockStream::default().with_eof();
|
||||
let mut client = Client::new(mock_stream);
|
||||
if let Err(Error::ConnectionLost) = client.readline() {
|
||||
let mut v = Vec::new();
|
||||
if let Err(Error::ConnectionLost) = client.readline(&mut v) {
|
||||
} else {
|
||||
unreachable!("EOF read did not return connection lost");
|
||||
}
|
||||
|
|
@ -598,7 +662,8 @@ mod tests {
|
|||
// TODO Check the error test
|
||||
let mock_stream = MockStream::default().with_err();
|
||||
let mut client = Client::new(mock_stream);
|
||||
client.readline().unwrap();
|
||||
let mut v = Vec::new();
|
||||
client.readline(&mut v).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -735,11 +800,17 @@ mod tests {
|
|||
a1 OK [READ-ONLY] Select completed.\r\n"
|
||||
.to_vec();
|
||||
let expected_mailbox = Mailbox {
|
||||
flags: String::from("(\\Answered \\Flagged \\Deleted \\Seen \\Draft)"),
|
||||
flags: vec![
|
||||
"\\Answered".to_string(),
|
||||
"\\Flagged".to_string(),
|
||||
"\\Deleted".to_string(),
|
||||
"\\Seen".to_string(),
|
||||
"\\Draft".to_string(),
|
||||
],
|
||||
exists: 1,
|
||||
recent: 1,
|
||||
unseen: Some(1),
|
||||
permanent_flags: Some(String::from("()")),
|
||||
permanent_flags: vec![],
|
||||
uid_next: Some(2),
|
||||
uid_validity: Some(1257842737),
|
||||
};
|
||||
|
|
@ -752,7 +823,7 @@ mod tests {
|
|||
client.stream.get_ref().written_buf == command.as_bytes().to_vec(),
|
||||
"Invalid examine command"
|
||||
);
|
||||
assert!(mailbox == expected_mailbox, "Unexpected mailbox returned");
|
||||
assert_eq!(mailbox, expected_mailbox);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -768,13 +839,24 @@ mod tests {
|
|||
a1 OK [READ-ONLY] Select completed.\r\n"
|
||||
.to_vec();
|
||||
let expected_mailbox = Mailbox {
|
||||
flags: String::from("(\\Answered \\Flagged \\Deleted \\Seen \\Draft)"),
|
||||
flags: vec![
|
||||
"\\Answered".to_string(),
|
||||
"\\Flagged".to_string(),
|
||||
"\\Deleted".to_string(),
|
||||
"\\Seen".to_string(),
|
||||
"\\Draft".to_string(),
|
||||
],
|
||||
exists: 1,
|
||||
recent: 1,
|
||||
unseen: Some(1),
|
||||
permanent_flags: Some(String::from(
|
||||
"(\\* \\Answered \\Flagged \\Deleted \\Draft \\Seen)",
|
||||
)),
|
||||
permanent_flags: vec![
|
||||
"\\*".to_string(),
|
||||
"\\Answered".to_string(),
|
||||
"\\Flagged".to_string(),
|
||||
"\\Deleted".to_string(),
|
||||
"\\Draft".to_string(),
|
||||
"\\Seen".to_string(),
|
||||
],
|
||||
uid_next: Some(2),
|
||||
uid_validity: Some(1257842737),
|
||||
};
|
||||
|
|
@ -787,7 +869,7 @@ mod tests {
|
|||
client.stream.get_ref().written_buf == command.as_bytes().to_vec(),
|
||||
"Invalid select command"
|
||||
);
|
||||
assert!(mailbox == expected_mailbox, "Unexpected mailbox returned");
|
||||
assert_eq!(mailbox, expected_mailbox);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -798,15 +880,15 @@ mod tests {
|
|||
let expected_capabilities = vec!["IMAP4rev1", "STARTTLS", "AUTH=GSSAPI", "LOGINDISABLED"];
|
||||
let mock_stream = MockStream::new(response);
|
||||
let mut client = Client::new(mock_stream);
|
||||
let capabilities = client.capability().unwrap();
|
||||
let capabilities = client.capabilities().unwrap();
|
||||
assert!(
|
||||
client.stream.get_ref().written_buf == b"a1 CAPABILITY\r\n".to_vec(),
|
||||
"Invalid capability command"
|
||||
);
|
||||
assert!(
|
||||
capabilities == expected_capabilities,
|
||||
"Unexpected capabilities response"
|
||||
);
|
||||
assert_eq!(capabilities.len(), 4);
|
||||
for e in expected_capabilities {
|
||||
assert!(capabilities.has(e));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
30
src/error.rs
30
src/error.rs
|
|
@ -5,6 +5,7 @@ use std::error::Error as StdError;
|
|||
use std::net::TcpStream;
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
use imap_proto::Response;
|
||||
use native_tls::HandshakeError as TlsHandshakeError;
|
||||
use native_tls::Error as TlsError;
|
||||
use bufstream::IntoInnerError as BufError;
|
||||
|
|
@ -21,9 +22,9 @@ pub enum Error {
|
|||
/// An error from the `native_tls` library while managing the socket.
|
||||
Tls(TlsError),
|
||||
/// A BAD response from the IMAP server.
|
||||
BadResponse(Vec<String>),
|
||||
BadResponse(String),
|
||||
/// A NO response from the IMAP server.
|
||||
NoResponse(Vec<String>),
|
||||
NoResponse(String),
|
||||
/// The connection was terminated unexpectedly.
|
||||
ConnectionLost,
|
||||
// Error parsing a server response.
|
||||
|
|
@ -58,6 +59,12 @@ impl From<TlsError> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Response<'a>> for Error {
|
||||
fn from(err: Response<'a>) -> Error {
|
||||
Error::Parse(ParseError::Unexpected(format!("{:?}", err)))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
|
|
@ -65,12 +72,9 @@ impl fmt::Display for Error {
|
|||
Error::Tls(ref e) => fmt::Display::fmt(e, f),
|
||||
Error::TlsHandshake(ref e) => fmt::Display::fmt(e, f),
|
||||
Error::Validate(ref e) => fmt::Display::fmt(e, f),
|
||||
Error::BadResponse(ref data) => write!(
|
||||
f,
|
||||
"{}: {}",
|
||||
&String::from(self.description()),
|
||||
&data.join("\n")
|
||||
),
|
||||
Error::NoResponse(ref data) | Error::BadResponse(ref data) => {
|
||||
write!(f, "{}: {}", &String::from(self.description()), data)
|
||||
}
|
||||
ref e => f.write_str(e.description()),
|
||||
}
|
||||
}
|
||||
|
|
@ -105,9 +109,9 @@ impl StdError for Error {
|
|||
#[derive(Debug)]
|
||||
pub enum ParseError {
|
||||
// Indicates an error parsing the status response. Such as OK, NO, and BAD.
|
||||
StatusResponse(Vec<String>),
|
||||
// Error parsing the cabability response.
|
||||
Capability(Vec<String>),
|
||||
Invalid(Vec<u8>),
|
||||
// An unexpected response was encountered.
|
||||
Unexpected(String),
|
||||
// Authentication errors.
|
||||
Authentication(String),
|
||||
DataNotUtf8(FromUtf8Error),
|
||||
|
|
@ -124,8 +128,8 @@ impl fmt::Display for ParseError {
|
|||
impl StdError for ParseError {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
ParseError::StatusResponse(_) => "Unable to parse status response",
|
||||
ParseError::Capability(_) => "Unable to parse capability response",
|
||||
ParseError::Invalid(_) => "Unable to parse status response",
|
||||
ParseError::Unexpected(_) => "Encountered unexpected parsed response",
|
||||
ParseError::Authentication(_) => "Unable to parse authentication response",
|
||||
ParseError::DataNotUtf8(_) => "Unable to parse data as UTF-8 text",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,19 @@
|
|||
//! imap is a IMAP client for Rust.
|
||||
|
||||
extern crate bufstream;
|
||||
extern crate imap_proto;
|
||||
extern crate native_tls;
|
||||
extern crate nom;
|
||||
extern crate regex;
|
||||
|
||||
mod types;
|
||||
mod parse;
|
||||
|
||||
pub mod authenticator;
|
||||
pub mod client;
|
||||
pub mod error;
|
||||
pub mod mailbox;
|
||||
|
||||
mod parse;
|
||||
pub use types::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock_stream;
|
||||
|
|
|
|||
321
src/parse.rs
321
src/parse.rs
|
|
@ -1,6 +1,8 @@
|
|||
use regex::Regex;
|
||||
use nom::IResult;
|
||||
use imap_proto::{self, Response};
|
||||
|
||||
use super::mailbox::Mailbox;
|
||||
use super::types::*;
|
||||
use super::error::{Error, ParseError, Result};
|
||||
|
||||
pub fn parse_authenticate_response(line: String) -> Result<String> {
|
||||
|
|
@ -14,102 +16,186 @@ pub fn parse_authenticate_response(line: String) -> Result<String> {
|
|||
Err(Error::Parse(ParseError::Authentication(line)))
|
||||
}
|
||||
|
||||
pub fn parse_capability(lines: Vec<String>) -> Result<Vec<String>> {
|
||||
let capability_regex = Regex::new(r"^\* CAPABILITY (.*)\r\n").unwrap();
|
||||
enum MapOrNot<T> {
|
||||
Map(T),
|
||||
Not(Response<'static>),
|
||||
}
|
||||
|
||||
//Check Ok
|
||||
match parse_response_ok(lines.clone()) {
|
||||
Ok(_) => (),
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
unsafe fn parse_many<T, F>(lines: Vec<u8>, mut map: F) -> ZeroCopyResult<Vec<T>>
|
||||
where
|
||||
F: FnMut(Response<'static>) -> MapOrNot<T>,
|
||||
{
|
||||
let f = |mut lines| {
|
||||
let mut things = Vec::new();
|
||||
loop {
|
||||
match imap_proto::parse_response(lines) {
|
||||
IResult::Done(rest, resp) => {
|
||||
lines = rest;
|
||||
|
||||
for line in lines.iter() {
|
||||
if capability_regex.is_match(line) {
|
||||
let cap = capability_regex.captures(line).unwrap();
|
||||
let capabilities_str = cap.get(1).unwrap().as_str();
|
||||
return Ok(capabilities_str.split(' ').map(|x| x.to_string()).collect());
|
||||
match map(resp) {
|
||||
MapOrNot::Map(t) => things.push(t),
|
||||
MapOrNot::Not(resp) => break Err(resp.into()),
|
||||
}
|
||||
|
||||
if lines.is_empty() {
|
||||
break Ok(things);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::Parse(ParseError::Capability(lines)))
|
||||
}
|
||||
|
||||
pub fn parse_response_ok(lines: Vec<String>) -> Result<()> {
|
||||
match parse_response(lines) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_response(lines: Vec<String>) -> Result<Vec<String>> {
|
||||
let regex = Regex::new(r"^([a-zA-Z0-9]+) (OK|NO|BAD)(.*)").unwrap();
|
||||
let last_line = match lines.last() {
|
||||
Some(l) => l,
|
||||
None => return Err(Error::Parse(ParseError::StatusResponse(lines.clone()))),
|
||||
};
|
||||
|
||||
for cap in regex.captures_iter(last_line) {
|
||||
let response_type = cap.get(2).map(|x| x.as_str()).unwrap_or("");
|
||||
match response_type {
|
||||
"OK" => return Ok(lines.clone()),
|
||||
"BAD" => return Err(Error::BadResponse(lines.clone())),
|
||||
"NO" => return Err(Error::NoResponse(lines.clone())),
|
||||
_ => {}
|
||||
ZeroCopy::new(lines, f)
|
||||
}
|
||||
|
||||
pub fn parse_names(lines: Vec<u8>) -> ZeroCopyResult<Vec<Name>> {
|
||||
use imap_proto::MailboxDatum;
|
||||
let f = |resp| match resp {
|
||||
// https://github.com/djc/imap-proto/issues/4
|
||||
Response::MailboxData(MailboxDatum::List {
|
||||
flags,
|
||||
delimiter,
|
||||
name,
|
||||
}) |
|
||||
Response::MailboxData(MailboxDatum::SubList {
|
||||
flags,
|
||||
delimiter,
|
||||
name,
|
||||
}) => MapOrNot::Map(Name {
|
||||
attributes: flags,
|
||||
delimiter,
|
||||
name,
|
||||
}),
|
||||
resp => MapOrNot::Not(resp),
|
||||
};
|
||||
|
||||
unsafe { parse_many(lines, f) }
|
||||
}
|
||||
|
||||
pub fn parse_fetches(lines: Vec<u8>) -> ZeroCopyResult<Vec<Fetch>> {
|
||||
let f = |resp| match resp {
|
||||
Response::Fetch(num, attrs) => {
|
||||
let mut fetch = Fetch {
|
||||
message: num,
|
||||
flags: vec![],
|
||||
uid: None,
|
||||
rfc822: None,
|
||||
};
|
||||
|
||||
for attr in attrs {
|
||||
use imap_proto::AttributeValue;
|
||||
match attr {
|
||||
AttributeValue::Flags(flags) => {
|
||||
fetch.flags.extend(flags);
|
||||
}
|
||||
AttributeValue::Uid(uid) => fetch.uid = Some(uid),
|
||||
AttributeValue::Rfc822(rfc) => fetch.rfc822 = rfc,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
MapOrNot::Map(fetch)
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::Parse(ParseError::StatusResponse(lines.clone())))
|
||||
}
|
||||
|
||||
pub fn parse_select_or_examine(lines: Vec<String>) -> Result<Mailbox> {
|
||||
let exists_regex = Regex::new(r"^\* (\d+) EXISTS\r\n").unwrap();
|
||||
|
||||
let recent_regex = Regex::new(r"^\* (\d+) RECENT\r\n").unwrap();
|
||||
|
||||
let flags_regex = Regex::new(r"^\* FLAGS (.+)\r\n").unwrap();
|
||||
|
||||
let unseen_regex = Regex::new(r"^\* OK \[UNSEEN (\d+)\](.*)\r\n").unwrap();
|
||||
|
||||
let uid_validity_regex = Regex::new(r"^\* OK \[UIDVALIDITY (\d+)\](.*)\r\n").unwrap();
|
||||
|
||||
let uid_next_regex = Regex::new(r"^\* OK \[UIDNEXT (\d+)\](.*)\r\n").unwrap();
|
||||
|
||||
let permanent_flags_regex = Regex::new(r"^\* OK \[PERMANENTFLAGS (.+)\](.*)\r\n").unwrap();
|
||||
|
||||
//Check Ok
|
||||
match parse_response_ok(lines.clone()) {
|
||||
Ok(_) => (),
|
||||
Err(e) => return Err(e),
|
||||
resp => MapOrNot::Not(resp),
|
||||
};
|
||||
|
||||
unsafe { parse_many(lines, f) }
|
||||
}
|
||||
|
||||
pub fn parse_capabilities(lines: Vec<u8>) -> ZeroCopyResult<Capabilities> {
|
||||
let f = |mut lines| {
|
||||
use std::collections::HashSet;
|
||||
let mut caps = HashSet::new();
|
||||
loop {
|
||||
match imap_proto::parse_response(lines) {
|
||||
IResult::Done(rest, Response::Capabilities(c)) => {
|
||||
lines = rest;
|
||||
caps.extend(c);
|
||||
|
||||
if lines.is_empty() {
|
||||
break Ok(Capabilities(caps));
|
||||
}
|
||||
}
|
||||
IResult::Done(_, resp) => {
|
||||
break Err(resp.into());
|
||||
}
|
||||
_ => {
|
||||
break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
unsafe { ZeroCopy::new(lines, f) }
|
||||
}
|
||||
|
||||
pub fn parse_mailbox(mut lines: &[u8]) -> Result<Mailbox> {
|
||||
let mut mailbox = Mailbox::default();
|
||||
|
||||
for line in lines.iter() {
|
||||
if exists_regex.is_match(line) {
|
||||
let cap = exists_regex.captures(line).unwrap();
|
||||
mailbox.exists = cap.get(1).unwrap().as_str().parse::<u32>().unwrap();
|
||||
} else if recent_regex.is_match(line) {
|
||||
let cap = recent_regex.captures(line).unwrap();
|
||||
mailbox.recent = cap.get(1).unwrap().as_str().parse::<u32>().unwrap();
|
||||
} else if flags_regex.is_match(line) {
|
||||
let cap = flags_regex.captures(line).unwrap();
|
||||
mailbox.flags = cap.get(1).unwrap().as_str().to_string();
|
||||
} else if unseen_regex.is_match(line) {
|
||||
let cap = unseen_regex.captures(line).unwrap();
|
||||
mailbox.unseen = Some(cap.get(1).unwrap().as_str().parse::<u32>().unwrap());
|
||||
} else if uid_validity_regex.is_match(line) {
|
||||
let cap = uid_validity_regex.captures(line).unwrap();
|
||||
mailbox.uid_validity = Some(cap.get(1).unwrap().as_str().parse::<u32>().unwrap());
|
||||
} else if uid_next_regex.is_match(line) {
|
||||
let cap = uid_next_regex.captures(line).unwrap();
|
||||
mailbox.uid_next = Some(cap.get(1).unwrap().as_str().parse::<u32>().unwrap());
|
||||
} else if permanent_flags_regex.is_match(line) {
|
||||
let cap = permanent_flags_regex.captures(line).unwrap();
|
||||
mailbox.permanent_flags = Some(cap.get(1).unwrap().as_str().to_string());
|
||||
loop {
|
||||
match imap_proto::parse_response(lines) {
|
||||
IResult::Done(rest, Response::Data { status, code, .. }) => {
|
||||
lines = rest;
|
||||
|
||||
if let imap_proto::Status::Ok = status {
|
||||
} else {
|
||||
// how can this happen for a Response::Data?
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
use imap_proto::ResponseCode;
|
||||
match code {
|
||||
Some(ResponseCode::UidValidity(uid)) => {
|
||||
mailbox.uid_validity = Some(uid);
|
||||
}
|
||||
Some(ResponseCode::UidNext(unext)) => {
|
||||
mailbox.uid_next = Some(unext);
|
||||
}
|
||||
Some(ResponseCode::Unseen(n)) => {
|
||||
mailbox.unseen = Some(n);
|
||||
}
|
||||
Some(ResponseCode::PermanentFlags(flags)) => {
|
||||
mailbox
|
||||
.permanent_flags
|
||||
.extend(flags.into_iter().map(|s| s.to_string()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
IResult::Done(rest, Response::MailboxData(m)) => {
|
||||
lines = rest;
|
||||
|
||||
use imap_proto::MailboxDatum;
|
||||
match m {
|
||||
MailboxDatum::Exists(e) => {
|
||||
mailbox.exists = e;
|
||||
}
|
||||
MailboxDatum::Recent(r) => {
|
||||
mailbox.recent = r;
|
||||
}
|
||||
MailboxDatum::Flags(flags) => {
|
||||
mailbox
|
||||
.flags
|
||||
.extend(flags.into_iter().map(|s| s.to_string()));
|
||||
}
|
||||
MailboxDatum::SubList { .. } | MailboxDatum::List { .. } => {}
|
||||
}
|
||||
}
|
||||
IResult::Done(_, resp) => {
|
||||
break Err(resp.into());
|
||||
}
|
||||
_ => {
|
||||
break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
|
||||
}
|
||||
}
|
||||
|
||||
if lines.is_empty() {
|
||||
break Ok(mailbox);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(mailbox)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -118,51 +204,46 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn parse_capability_test() {
|
||||
let expected_capabilities = vec![
|
||||
String::from("IMAP4rev1"),
|
||||
String::from("STARTTLS"),
|
||||
String::from("AUTH=GSSAPI"),
|
||||
String::from("LOGINDISABLED"),
|
||||
];
|
||||
let lines = vec![
|
||||
String::from("* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n"),
|
||||
String::from("a1 OK CAPABILITY completed\r\n"),
|
||||
];
|
||||
let capabilities = parse_capability(lines).unwrap();
|
||||
assert!(
|
||||
capabilities == expected_capabilities,
|
||||
"Unexpected capabilities parse response"
|
||||
);
|
||||
let expected_capabilities = vec!["IMAP4rev1", "STARTTLS", "AUTH=GSSAPI", "LOGINDISABLED"];
|
||||
let lines = b"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n";
|
||||
let capabilities = parse_capabilities(lines.to_vec()).unwrap();
|
||||
assert_eq!(capabilities.len(), 4);
|
||||
for e in expected_capabilities {
|
||||
assert!(capabilities.has(e));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn parse_capability_invalid_test() {
|
||||
let lines = vec![
|
||||
String::from("* JUNK IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n"),
|
||||
String::from("a1 OK CAPABILITY completed\r\n"),
|
||||
];
|
||||
parse_capability(lines).unwrap();
|
||||
let lines = b"* JUNK IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n";
|
||||
parse_capabilities(lines.to_vec()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_response_test() {
|
||||
let lines = vec![
|
||||
String::from("* LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n"),
|
||||
String::from("a2 OK List completed.\r\n"),
|
||||
];
|
||||
let expected_lines = lines.clone();
|
||||
let actual_lines = parse_response(lines).unwrap();
|
||||
assert!(expected_lines == actual_lines, "Unexpected parse response");
|
||||
fn parse_names_test() {
|
||||
let lines = b"* LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n";
|
||||
let names = parse_names(lines.to_vec()).unwrap();
|
||||
assert_eq!(names.len(), 1);
|
||||
assert_eq!(names[0].attributes(), &["\\HasNoChildren"]);
|
||||
assert_eq!(names[0].delimiter(), ".");
|
||||
assert_eq!(names[0].name(), "INBOX");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn parse_response_invalid_test() {
|
||||
let lines = vec![
|
||||
String::from("* LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n"),
|
||||
String::from("a2 BAD broken.\r\n"),
|
||||
];
|
||||
parse_response(lines).unwrap();
|
||||
fn parse_fetches_test() {
|
||||
let lines = b"\
|
||||
* 24 FETCH (FLAGS (\\Seen) UID 4827943)\r\n\
|
||||
* 25 FETCH (FLAGS (\\Seen))\r\n";
|
||||
let fetches = parse_fetches(lines.to_vec()).unwrap();
|
||||
assert_eq!(fetches.len(), 2);
|
||||
assert_eq!(fetches[0].message, 24);
|
||||
assert_eq!(fetches[0].flags(), &["\\Seen"]);
|
||||
assert_eq!(fetches[0].uid, Some(4827943));
|
||||
assert_eq!(fetches[0].rfc822(), None);
|
||||
assert_eq!(fetches[1].message, 25);
|
||||
assert_eq!(fetches[1].flags(), &["\\Seen"]);
|
||||
assert_eq!(fetches[1].uid, None);
|
||||
assert_eq!(fetches[1].rfc822(), None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
src/types/capabilities.rs
Normal file
29
src/types/capabilities.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Note that none of these fields are *actually* 'static.
|
||||
// Rather, they are tied to the lifetime of the `ZeroCopy` that contains this `Name`.
|
||||
use std::collections::HashSet;
|
||||
use std::collections::hash_set::Iter;
|
||||
pub struct Capabilities(pub(crate) HashSet<&'static str>);
|
||||
|
||||
use std::hash::Hash;
|
||||
use std::borrow::Borrow;
|
||||
impl Capabilities {
|
||||
pub fn has<S: ?Sized>(&self, s: &S) -> bool
|
||||
where
|
||||
for<'a> &'a str: Borrow<S>,
|
||||
S: Hash + Eq,
|
||||
{
|
||||
self.0.contains(s)
|
||||
}
|
||||
|
||||
pub fn iter<'a>(&'a self) -> Iter<'a, &'a str> {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
19
src/types/fetch.rs
Normal file
19
src/types/fetch.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Note that none of these fields are *actually* 'static.
|
||||
// Rather, they are tied to the lifetime of the `ZeroCopy` that contains this `Name`.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Fetch {
|
||||
pub message: u32,
|
||||
pub(crate) flags: Vec<&'static str>,
|
||||
pub uid: Option<u32>,
|
||||
pub(crate) rfc822: Option<&'static [u8]>,
|
||||
}
|
||||
|
||||
impl Fetch {
|
||||
pub fn flags<'a>(&'a self) -> &'a [&'a str] {
|
||||
&self.flags[..]
|
||||
}
|
||||
|
||||
pub fn rfc822<'a>(&'a self) -> Option<&'a [u8]> {
|
||||
self.rfc822
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,11 @@ use std::fmt;
|
|||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct Mailbox {
|
||||
pub flags: String,
|
||||
pub flags: Vec<String>,
|
||||
pub exists: u32,
|
||||
pub recent: u32,
|
||||
pub unseen: Option<u32>,
|
||||
pub permanent_flags: Option<String>,
|
||||
pub permanent_flags: Vec<String>,
|
||||
pub uid_next: Option<u32>,
|
||||
pub uid_validity: Option<u32>,
|
||||
}
|
||||
|
|
@ -14,11 +14,11 @@ pub struct Mailbox {
|
|||
impl Default for Mailbox {
|
||||
fn default() -> Mailbox {
|
||||
Mailbox {
|
||||
flags: "".to_string(),
|
||||
flags: Vec::new(),
|
||||
exists: 0,
|
||||
recent: 0,
|
||||
unseen: None,
|
||||
permanent_flags: None,
|
||||
permanent_flags: Vec::new(),
|
||||
uid_next: None,
|
||||
uid_validity: None,
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ impl fmt::Display for Mailbox {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"flags: {}, exists: {}, recent: {}, unseen: {:?}, permanent_flags: {:?},\
|
||||
"flags: {:?}, exists: {}, recent: {}, unseen: {:?}, permanent_flags: {:?},\
|
||||
uid_next: {:?}, uid_validity: {:?}",
|
||||
self.flags,
|
||||
self.exists,
|
||||
127
src/types/mod.rs
Normal file
127
src/types/mod.rs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
mod mailbox;
|
||||
pub use self::mailbox::Mailbox;
|
||||
|
||||
mod fetch;
|
||||
pub use self::fetch::Fetch;
|
||||
|
||||
mod name;
|
||||
pub use self::name::Name;
|
||||
|
||||
mod capabilities;
|
||||
pub use self::capabilities::Capabilities;
|
||||
|
||||
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 unsafe fn new<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)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
use super::error::Error;
|
||||
pub 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
|
||||
}
|
||||
fn ne(&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()
|
||||
}
|
||||
}
|
||||
22
src/types/name.rs
Normal file
22
src/types/name.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Note that none of these fields are *actually* 'static.
|
||||
// Rather, they are tied to the lifetime of the `ZeroCopy` that contains this `Name`.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Name {
|
||||
pub(crate) attributes: Vec<&'static str>,
|
||||
pub(crate) delimiter: &'static str,
|
||||
pub(crate) name: &'static str,
|
||||
}
|
||||
|
||||
impl Name {
|
||||
pub fn attributes<'a>(&'a self) -> &'a [&'a str] {
|
||||
&self.attributes[..]
|
||||
}
|
||||
|
||||
pub fn delimiter<'a>(&'a self) -> &'a str {
|
||||
self.delimiter
|
||||
}
|
||||
|
||||
pub fn name<'a>(&'a self) -> &'a str {
|
||||
self.name
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue