├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── example.rs └── src ├── client.rs ├── connection.rs ├── imaperror.rs ├── lib.rs ├── mailbox.rs ├── parser.rs ├── response.rs └── validate_helpers.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | 13 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 14 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 15 | Cargo.lock 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "typed-imap" 3 | version = "0.1.0" 4 | authors = ["insanitybit "] 5 | 6 | [dependencies] 7 | openssl = "*" 8 | rand = "*" 9 | regex = "*" 10 | lazy_static = "0.1.*" 11 | nom = "*" 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Colin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/insanitybit/imap-rs/blob/master/LICENSE) 2 | 3 | # typed-imap 4 | A typesafe client library for the IMAP protocol. 5 | 6 | Currently in the fledgling stages. Entirely *not* working, no stable API. 7 | 8 | # Example 9 | 10 | ```rust 11 | extern crate typed_imap; 12 | 13 | use typed_imap::client::*; 14 | use typed_imap::connection::IMAPConnection; 15 | 16 | fn main() { 17 | let client = { 18 | let conn = IMAPConnection::new_tls("outlook.office365.com", 993).unwrap(); 19 | NotAuthenticated::new(conn).unwrap() 20 | }; 21 | 22 | let client = client.login("username", "passwd").unwrap(); 23 | 24 | let mut client = client.select("inbox").unwrap(); 25 | 26 | // We need to act on the mailbox in a new scope to prevent a situation where 27 | // a client has a mailbox but moves to a new state. 28 | { 29 | let mut mailbox = client.borrow_mailbox(); 30 | let emails = mailbox.fetch("2:4", "BODY").unwrap(); 31 | } 32 | 33 | client.logout(); 34 | } 35 | 36 | ``` 37 | 38 | See [examples/](https://github.com/insanitybit/typed-imap/tree/master/examples) for more. 39 | -------------------------------------------------------------------------------- /examples/example.rs: -------------------------------------------------------------------------------- 1 | extern crate typed_imap; 2 | 3 | use typed_imap::client::*; 4 | use typed_imap::connection::IMAPConnection; 5 | 6 | fn main() { 7 | let client = { 8 | let conn = IMAPConnection::new_tls("outlook.office365.com", 993).unwrap(); 9 | NotAuthenticated::new(conn).unwrap() 10 | }; 11 | 12 | let client = client.login("username", "passwd").unwrap(); 13 | 14 | let mut client = client.select("inbox").unwrap(); 15 | 16 | // We need to act on the mailbox in a new scope to prevent a situation where 17 | // a client has a mailbox but moves to a new state. 18 | { 19 | let mut mailbox = client.borrow_mailbox(); 20 | let emails = mailbox.fetch("2:4", "BODY").unwrap(); 21 | } 22 | 23 | client.logout(); 24 | } 25 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use connection::IMAPConnection; 2 | use imaperror::IMAPError; 3 | use response::{Response, parse_responses}; 4 | use validate_helpers::*; 5 | use mailbox::{Mailbox, ReadOnly}; 6 | use rand::thread_rng; 7 | use rand::Rng; 8 | use std::fmt; 9 | use std::mem; 10 | use std::ptr; 11 | 12 | #[derive(Debug)] 13 | pub struct Tag { 14 | tag_prefix: String, 15 | tag: u32, 16 | } 17 | 18 | #[derive(Debug)] 19 | pub struct NotAuthenticated { 20 | connection: IMAPConnection, 21 | parsed_responses: Vec, 22 | tag: Tag, 23 | } 24 | 25 | #[derive(Debug)] 26 | pub struct Authenticated { 27 | connection: IMAPConnection, 28 | parsed_responses: Vec, 29 | tag: Tag, 30 | } 31 | 32 | #[derive(Debug)] 33 | pub struct Selected { 34 | connection: IMAPConnection, 35 | parsed_responses: Vec, 36 | mailbox: Mailbox, 37 | tag: Tag, 38 | } 39 | 40 | #[derive(Debug)] 41 | pub struct LoggedOut { 42 | connection: IMAPConnection, 43 | parsed_responses: Vec, 44 | tag: Tag, 45 | } 46 | 47 | impl Tag { 48 | fn new() -> Tag { 49 | let mut rng = thread_rng(); 50 | let rstr: String = rng.gen_ascii_chars() 51 | .take(4) 52 | .collect(); 53 | Tag { 54 | tag_prefix: rstr, 55 | tag: rng.gen_ascii_chars().next().unwrap() as u32, 56 | } 57 | } 58 | 59 | /// Increments and then returns the tag. 60 | pub fn next_tag(mut self) -> Self { 61 | self.tag += 1; 62 | self 63 | } 64 | } 65 | 66 | impl fmt::Display for Tag { 67 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 68 | write!(f, "{}{:04}", self.tag_prefix, self.tag) 69 | } 70 | } 71 | 72 | impl NotAuthenticated { 73 | pub fn new(connection: IMAPConnection) -> Result { 74 | let mut connection = connection; 75 | let raw_res = try!(connection.command("")); 76 | let greeting = validate_greeting(&raw_res[..]); 77 | 78 | Ok(NotAuthenticated { 79 | connection: connection, 80 | parsed_responses: parse_responses(&raw_res[..]), 81 | tag: Tag::new(), 82 | }) 83 | 84 | } 85 | 86 | pub fn login(mut self, 87 | username: &str, 88 | passwd: &str) 89 | -> Result { 90 | 91 | let login_response = try_imap!(self, 92 | self.connection 93 | .command(&format!("{} LOGIN {} {}", 94 | self.tag, 95 | username, 96 | passwd))); 97 | 98 | let _ = try_imap!(self, validate_login(&login_response[..])); 99 | 100 | Ok(Authenticated { 101 | connection: self.connection, 102 | parsed_responses: parse_responses(&login_response[..]), 103 | tag: self.tag.next_tag(), 104 | }) 105 | 106 | } 107 | 108 | pub fn logout(mut self) -> Result { 109 | let logout_response = try_imap!(self, self.connection.command("fake login command")); 110 | 111 | Ok(LoggedOut { 112 | connection: self.connection, 113 | parsed_responses: parse_responses(&logout_response[..]), 114 | tag: self.tag.next_tag(), 115 | }) 116 | } 117 | } 118 | 119 | impl Authenticated { 120 | pub fn select(mut self, mailbox: &str) -> Result { 121 | let select_response = try_imap!(self, 122 | self.connection 123 | .command(&format!("{} SELECT {}", self.tag, mailbox))); 124 | 125 | let _ = try_imap!(self, validate_select(&select_response[..])); 126 | 127 | // This is unsfae due to the use of mem::uninitialized::() 128 | // Because we can not borrow &mut self.connection, as it is moved, 129 | // we have to temporarily create an unsafe Selected, and then create 130 | // a reference to *its* connection, to initialize the mailbox 131 | let mut selected = unsafe { 132 | let mut selected = Selected { 133 | connection: self.connection, 134 | parsed_responses: parse_responses(&select_response[..]), 135 | mailbox: mem::uninitialized(), 136 | tag: self.tag.next_tag(), 137 | }; 138 | ptr::write(&mut selected.mailbox, 139 | ReadOnly::new(&mut selected.connection)); 140 | selected 141 | }; 142 | 143 | Ok(selected) 144 | } 145 | 146 | pub fn logout(mut self) -> Result { 147 | let logout_response = try_imap!(self, self.connection.command("fake login command")); 148 | 149 | Ok(LoggedOut { 150 | connection: self.connection, 151 | parsed_responses: parse_responses(&logout_response[..]), 152 | tag: self.tag.next_tag(), 153 | }) 154 | } 155 | } 156 | 157 | impl Selected { 158 | pub fn borrow_mailbox(&mut self) -> &mut Mailbox { 159 | &mut self.mailbox 160 | } 161 | 162 | pub fn logout(mut self) -> Result { 163 | let logout_response = try_imap!(self, self.connection.command("fake logout command")); 164 | 165 | Ok(LoggedOut { 166 | connection: self.connection, 167 | parsed_responses: parse_responses(&logout_response[..]), 168 | tag: self.tag.next_tag(), 169 | }) 170 | } 171 | } 172 | 173 | impl LoggedOut {} 174 | 175 | #[cfg(test)] 176 | mod tests { 177 | #[test] 178 | fn it_works() {} 179 | } 180 | -------------------------------------------------------------------------------- /src/connection.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | extern crate openssl; 4 | 5 | use imaperror::IMAPError; 6 | use std::time::Duration; 7 | use std::net::TcpStream; 8 | use self::openssl::ssl::{SslContext, SslStream, SslMethod, Ssl}; 9 | use std::io::{Write, Read}; 10 | use std::str; 11 | 12 | #[derive(Debug)] 13 | pub enum IMAPConnection { 14 | Basic(TcpStream), 15 | Ssl(SslStream), 16 | Disconnected, 17 | } 18 | 19 | 20 | impl IMAPConnection { 21 | pub fn new() -> IMAPConnection { 22 | IMAPConnection::Disconnected 23 | } 24 | 25 | pub fn new_notls(host: &str, port: u32) -> Result { 26 | let host = host; 27 | let server = format!("{}:{}", host, port); 28 | 29 | let stream = try!(TcpStream::connect(&*server)); 30 | try!(stream.set_read_timeout(Some(Duration::from_secs(15)))); 31 | try!(stream.set_write_timeout(Some(Duration::from_secs(15)))); 32 | 33 | Ok(IMAPConnection::Basic(stream)) 34 | } 35 | 36 | pub fn new_tls(host: &str, port: u32) -> Result { 37 | let host = host; 38 | let server = format!("{}:{}", host, port); 39 | 40 | let stream = { 41 | let stream = try!(TcpStream::connect(&*server)); 42 | let _ = stream.set_read_timeout(Some(Duration::from_secs(15))); 43 | let _ = stream.set_write_timeout(Some(Duration::from_secs(15))); 44 | 45 | let sslcontext = try!(SslContext::new(SslMethod::Sslv23)); 46 | let ssl = try!(Ssl::new(&sslcontext)); 47 | let stream = try!(SslStream::connect(ssl, stream)); 48 | 49 | stream 50 | }; 51 | 52 | Ok(IMAPConnection::Ssl(stream)) 53 | } 54 | 55 | pub fn command(&mut self, cmd: &str) -> Result, IMAPError> { 56 | println!("{:?}", cmd); 57 | match *self { 58 | IMAPConnection::Basic(ref mut stream) => { 59 | let _ = stream.write(cmd.as_bytes()); 60 | let mut buf = Vec::new(); 61 | let mut counter = 5; 62 | 63 | while buf.is_empty() && counter > 0 { 64 | counter -= 1; 65 | let _ = stream.read_to_end(&mut buf); 66 | println!("{}", str::from_utf8(&buf).unwrap()); 67 | } 68 | Ok(buf) 69 | } 70 | IMAPConnection::Ssl(ref mut stream) => { 71 | let _ = stream.write(cmd.as_bytes()); 72 | let mut buf = Vec::new(); 73 | let mut counter = 5; 74 | 75 | while buf.is_empty() && counter > 0 { 76 | counter -= 1; 77 | let _ = stream.read_to_end(&mut buf); 78 | println!("{}", str::from_utf8(&buf).unwrap()); 79 | } 80 | Ok(buf) 81 | } 82 | IMAPConnection::Disconnected => { 83 | Err(IMAPError::LoginError("Not connected to server.".to_owned())) 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/imaperror.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | extern crate openssl; 3 | 4 | // use parseerror::ParseError; 5 | 6 | use std::error; 7 | use std::fmt; 8 | use std::io::Error as ioError; 9 | use self::openssl::ssl::error::*; 10 | 11 | #[derive(Debug)] 12 | pub enum IMAPError { 13 | IOError(ioError), 14 | SslError(SslError), 15 | LoginError(String), 16 | SelectError(String), 17 | ConnectError(String), 18 | GreetingError(String), // ParseError(ParseError), 19 | } 20 | 21 | impl fmt::Display for IMAPError { 22 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 23 | match *self { 24 | IMAPError::IOError(ref err) => write!(f, "IO error: {}", err), 25 | IMAPError::SslError(ref err) => write!(f, "Ssl error: {}", err), 26 | IMAPError::LoginError(ref err) => write!(f, "Login error: {}", err), 27 | IMAPError::SelectError(ref err) => write!(f, "Select error: {}", err), 28 | IMAPError::ConnectError(ref err) => write!(f, "Connect error: {}", err), 29 | IMAPError::GreetingError(ref err) => write!(f, "Greeting error: {}", err), 30 | // IMAPError::ParseError(ref err) => write!(f, "Error parsing IMAP response: {}", err), 31 | } 32 | } 33 | } 34 | 35 | impl error::Error for IMAPError { 36 | fn description(&self) -> &str { 37 | match *self { 38 | IMAPError::IOError(ref err) => err.description(), 39 | IMAPError::SslError(ref err) => err.description(), 40 | IMAPError::LoginError(ref err) => err, 41 | IMAPError::SelectError(ref err) => err, 42 | IMAPError::ConnectError(ref err) => err, 43 | IMAPError::GreetingError(ref err) => err, 44 | // IMAPError::ParseError(ref err) => err.description(), 45 | } 46 | } 47 | 48 | fn cause(&self) -> Option<&error::Error> { 49 | match *self { 50 | IMAPError::IOError(ref err) => Some(err), 51 | IMAPError::SslError(ref err) => Some(err), 52 | IMAPError::LoginError(_) => None, 53 | IMAPError::SelectError(_) => None, 54 | IMAPError::ConnectError(_) => None, 55 | IMAPError::GreetingError(_) => None, 56 | // IMAPError::ParseError(_) => None, 57 | } 58 | } 59 | } 60 | 61 | impl From for IMAPError { 62 | fn from(err: ioError) -> IMAPError { 63 | IMAPError::IOError(err) 64 | } 65 | } 66 | 67 | impl From for IMAPError { 68 | fn from(err: SslError) -> IMAPError { 69 | IMAPError::SslError(err) 70 | } 71 | } 72 | 73 | // impl From for IMAPError { 74 | // fn from(err: ParseError) -> IMAPError { 75 | // IMAPError::ParseError(err) 76 | // } 77 | // } 78 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(warnings)] 2 | extern crate rand; 3 | extern crate regex; 4 | #[macro_use] 5 | extern crate lazy_static; 6 | #[macro_use] 7 | extern crate nom; 8 | 9 | pub mod imaperror; 10 | 11 | #[macro_export] 12 | macro_rules! try_imap { 13 | ($old_state:expr, $expr:expr) => (match $expr { 14 | Result::Ok(val) => val, 15 | Result::Err(err) => { 16 | return Result::Err(($old_state, From::from(err))) 17 | } 18 | }) 19 | } 20 | 21 | pub mod validate_helpers; 22 | pub mod response; 23 | pub mod parser; 24 | pub mod client; 25 | pub mod connection; 26 | pub mod mailbox; 27 | -------------------------------------------------------------------------------- /src/mailbox.rs: -------------------------------------------------------------------------------- 1 | use connection::IMAPConnection; 2 | use imaperror::IMAPError; 3 | use std::fmt; 4 | 5 | pub struct Envelope; 6 | 7 | pub enum DataItem { 8 | Err((IMAPError, Vec)), 9 | Envelope(Envelope), 10 | } 11 | 12 | /// A mailbox's permissions will be exposed at the type level through the Mailbox enum 13 | /// Convenience functions that work on *either* type will be provided at the expense of incurring 14 | /// potential runtime errors (ie: trying to modify server when ReadOnly). 15 | #[derive(Debug)] 16 | pub enum Mailbox { 17 | ReadOnly(ReadOnly), // ReadWrite(ReadWrite), 18 | } 19 | 20 | #[derive(Debug)] 21 | pub struct ReadOnly { 22 | imap: IMAPConnection, 23 | } 24 | 25 | impl DataItem { 26 | pub fn is_err<'a>(&'a self) -> bool { 27 | match self { 28 | &DataItem::Err(_) => true, 29 | _ => false, 30 | } 31 | } 32 | 33 | pub fn as_err<'a>(&'a self) -> Option<&'a (IMAPError, Vec)> { 34 | match self { 35 | &DataItem::Err(ref e) => Some(e), 36 | _ => None, 37 | } 38 | } 39 | 40 | pub fn is_envelope<'a>(&'a self) -> bool { 41 | match self { 42 | &DataItem::Envelope(_) => true, 43 | _ => false, 44 | } 45 | } 46 | 47 | pub fn as_envelope<'a>(&'a self) -> Option<&'a Envelope> { 48 | match self { 49 | &DataItem::Envelope(ref e) => Some(e), 50 | _ => None, 51 | } 52 | } 53 | } 54 | 55 | impl ReadOnly { 56 | pub fn new(conn: &mut IMAPConnection) -> Mailbox { 57 | unimplemented!(); 58 | } 59 | } 60 | 61 | impl Mailbox { 62 | pub fn fetch(&mut self, 63 | sequence_set: &str, 64 | data_item_labels: &str) 65 | -> Result, IMAPError> { 66 | unimplemented!() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | use nom::IResult::*; 2 | use nom::*; 3 | 4 | 5 | 6 | #[derive(Debug)] 7 | pub enum SequenceSet<'a> { 8 | Number(&'a [u8]), 9 | Range(SeqRange<'a>), 10 | } 11 | 12 | #[derive(Debug)] 13 | pub struct Address<'a> { 14 | pub addr_name: &'a [u8], 15 | pub addr_adl: &'a [u8], 16 | pub addr_mailbox: &'a [u8], 17 | pub addr_host: &'a [u8], 18 | } 19 | 20 | #[derive(Debug)] 21 | pub struct Envelope<'a> { 22 | pub date: &'a [u8], 23 | pub subject: &'a [u8], 24 | pub from: Option>>, 25 | pub senter: Option>>, 26 | pub reply_to: Option>>, 27 | pub to: Option>>, 28 | pub cc: Option>>, 29 | pub bcc: Option>>, 30 | pub in_reply_to: &'a [u8], 31 | pub message_id: &'a [u8], 32 | } 33 | 34 | 35 | #[derive(Debug)] 36 | pub struct SeqRange<'a> { 37 | pub start: &'a [u8], 38 | pub end: &'a [u8], 39 | } 40 | 41 | named!(pub nz_digit, 42 | alt!(tag!(b"1") 43 | | tag!(b"2") 44 | | tag!(b"3") 45 | | tag!(b"4") 46 | | tag!(b"5") 47 | | tag!(b"6") 48 | | tag!(b"7") 49 | | tag!(b"8") 50 | | tag!(b"9") 51 | ) 52 | ); 53 | 54 | pub fn nz_number(bytes: &[u8]) -> IResult<&[u8], &[u8]> { 55 | let mut offset = 0; 56 | 57 | if nz_digit(&bytes[..1]).is_done() { 58 | offset += 1; 59 | } else { 60 | return take!(bytes, 0); 61 | } 62 | 63 | loop { 64 | if digit(&bytes[offset..]).is_done() { 65 | offset += 1; 66 | } else { 67 | break; 68 | } 69 | } 70 | take!(bytes, offset) 71 | } 72 | 73 | named!(pub nil, 74 | alt!( 75 | tag!(b"NIL") 76 | | tag!(b"nil") 77 | ) 78 | ); 79 | 80 | named!(pub resp_specials, tag!("]")); 81 | 82 | named!(pub quoted_specials, 83 | alt!( 84 | tag!("\"") 85 | | tag!("\\") 86 | ) 87 | ); 88 | 89 | named!(pub list_wildcards, 90 | alt!( 91 | tag!("%") 92 | | tag!("*") 93 | ) 94 | ); 95 | 96 | named!(pub atom_specials, 97 | alt!( 98 | tag!("(") 99 | | tag!(")") 100 | | tag!("{") 101 | | tag!(" ") 102 | | tag!("(") 103 | | list_wildcards 104 | | resp_specials 105 | | quoted_specials 106 | ) 107 | ); 108 | 109 | pub fn atom_char(bytes: &[u8]) -> IResult<&[u8], &[u8]> { 110 | cond_reduce!(bytes, 111 | !atom_specials(bytes).is_done(), 112 | chain!( 113 | alpha: peek!(alphanumeric) ~ 114 | _____: take!(1) , 115 | || { &alpha[..1] } 116 | )) 117 | } 118 | 119 | pub fn atom(bytes: &[u8]) -> IResult<&[u8], &[u8]> { 120 | let mut offset = 0; 121 | loop { 122 | if atom_char(&bytes[offset..]).is_done() { 123 | offset += 1; 124 | } else { 125 | break; 126 | } 127 | } 128 | take!(bytes, offset) 129 | } 130 | 131 | named!(pub nstring, 132 | alt!( 133 | delimited!( 134 | char!('"'), 135 | is_not!("\""), 136 | char!('"') 137 | ) 138 | | nil 139 | ) 140 | ); 141 | 142 | named!(pub astring_char, 143 | alt!( 144 | atom_char 145 | | resp_specials 146 | ) 147 | ); 148 | 149 | #[inline(always)] 150 | named!(many_addr_or_nil <&[u8], Option > >, 151 | alt!( 152 | chain!( 153 | char!('(') ~ 154 | addresses: many1!(address) ~ 155 | char!(')'), 156 | || { 157 | Some(addresses) 158 | } 159 | ) 160 | | chain!( 161 | nil, 162 | || { 163 | None 164 | } 165 | ) 166 | ) 167 | ); 168 | 169 | named!(pub env_bcc <&[u8], Option > >, 170 | chain!( 171 | maon: many_addr_or_nil, 172 | || { 173 | maon 174 | }) 175 | ); 176 | 177 | named!(pub env_cc <&[u8], Option > >, 178 | chain!( 179 | maon: many_addr_or_nil, 180 | || { 181 | maon 182 | }) 183 | ); 184 | 185 | named!(pub env_from <&[u8], Option > >, 186 | chain!( 187 | maon: many_addr_or_nil, 188 | || { 189 | maon 190 | }) 191 | ); 192 | 193 | named!(pub env_date, 194 | chain!( 195 | ns: nstring, 196 | || { 197 | ns 198 | }) 199 | ); 200 | 201 | named!(pub env_in_reply_to, 202 | chain!( 203 | ns: nstring, 204 | || { 205 | ns 206 | }) 207 | ); 208 | 209 | named!(pub env_message_id, 210 | chain!( 211 | ns: nstring, 212 | || { 213 | ns 214 | }) 215 | ); 216 | 217 | named!(pub env_reply_to <&[u8], Option > >, 218 | chain!( 219 | maon: many_addr_or_nil, 220 | || { 221 | maon 222 | }) 223 | ); 224 | 225 | named!(pub env_sender <&[u8], Option > >, 226 | chain!( 227 | maon: many_addr_or_nil, 228 | || { 229 | maon 230 | }) 231 | ); 232 | 233 | named!(pub env_subject, 234 | chain!( 235 | ns: nstring, 236 | || { 237 | ns 238 | }) 239 | ); 240 | 241 | named!(pub env_to <&[u8], Option > >, 242 | chain!( 243 | maon: many_addr_or_nil, 244 | || { 245 | maon 246 | }) 247 | ); 248 | 249 | named!(pub seq_number, 250 | alt!( 251 | nz_number 252 | | tag!("*") 253 | ) 254 | ); 255 | 256 | named!(pub seq_range <&[u8], SeqRange >, 257 | chain!( 258 | start: seq_number ~ 259 | tag!(":") ~ 260 | end: seq_number, 261 | || { 262 | SeqRange { 263 | start: start, 264 | end: end, 265 | } 266 | } 267 | ) 268 | ); 269 | 270 | named!(pub sequence_set <&[u8], Vec >, 271 | separated_nonempty_list!(tag!(","), 272 | alt!( 273 | chain!(num: seq_number, || { 274 | SequenceSet::Number(num) 275 | }) 276 | | chain!(range: seq_range, || { 277 | SequenceSet::Range(range) 278 | }) 279 | ) 280 | ) 281 | ); 282 | 283 | named!(pub address <&[u8], Address>, 284 | chain!( 285 | char!('(') ~ 286 | addr_name: nstring ~ 287 | char!(' ') ~ 288 | addr_adl: nstring ~ 289 | char!(' ') ~ 290 | addr_mailbox: nstring ~ 291 | char!(' ') ~ 292 | addr_host: nstring ~ 293 | char!(')'), 294 | || { 295 | Address { 296 | addr_name: addr_name, 297 | addr_adl: addr_adl, 298 | addr_mailbox: addr_mailbox, 299 | addr_host: addr_host, 300 | } 301 | } 302 | ) 303 | ); 304 | 305 | named!(pub envelope <&[u8], Envelope>, 306 | chain!( 307 | char!('(') ~ 308 | date: env_date ~ 309 | char!(' ') ~ 310 | subject: env_subject ~ 311 | char!(' ') ~ 312 | from: env_from ~ 313 | char!(' ') ~ 314 | senter: env_sender ~ 315 | char!(' ') ~ 316 | reply_to: env_reply_to ~ 317 | char!(' ') ~ 318 | to: env_to ~ 319 | char!(' ') ~ 320 | cc: env_cc ~ 321 | char!(' ') ~ 322 | bcc: env_bcc ~ 323 | char!(' ') ~ 324 | in_reply_to: env_in_reply_to ~ 325 | char!(' ') ~ 326 | message_id: env_message_id ~ 327 | 328 | char!(')'), 329 | || { 330 | Envelope { 331 | date: date, 332 | subject: subject, 333 | from: from, 334 | senter: senter, 335 | reply_to: reply_to, 336 | to: to, 337 | cc: cc, 338 | bcc: bcc, 339 | in_reply_to: in_reply_to, 340 | message_id: message_id, 341 | } 342 | } 343 | ) 344 | ); 345 | 346 | #[cfg(test)] 347 | mod tests { 348 | use nom::IResult::*; 349 | use nom::*; 350 | use super::*; 351 | use std::str; 352 | 353 | #[test] 354 | fn test_address_parse() { 355 | let addr = b"(\"name\" \"adl\" \"mailbox\" \"host\")"; 356 | match address(&addr[..]) { 357 | Done(remaining, addr) => { 358 | assert_eq!(remaining.len(), 0); 359 | assert_eq!(addr.addr_name, b"name"); 360 | assert_eq!(addr.addr_adl, b"adl"); 361 | assert_eq!(addr.addr_mailbox, b"mailbox"); 362 | assert_eq!(addr.addr_host, b"host"); 363 | } 364 | _ => panic!(), 365 | } 366 | } 367 | 368 | #[test] 369 | fn test_atom() { 370 | let s = b"abcd"; 371 | 372 | match atom(&s[..]) { 373 | Done(remaining, atom) => { 374 | assert_eq!(atom, s); 375 | } 376 | _ => panic!("NOT DONE"), 377 | } 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /src/response.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Copy, Clone, PartialEq)] 2 | pub enum Response {} 3 | 4 | pub fn parse_responses(parsed_responses: &[u8]) -> Vec { 5 | unimplemented!(); 6 | } 7 | -------------------------------------------------------------------------------- /src/validate_helpers.rs: -------------------------------------------------------------------------------- 1 | use imaperror::IMAPError; 2 | use regex::bytes::Regex; 3 | // use std::str; 4 | use std::str; 5 | 6 | pub fn validate_greeting(response: &[u8]) -> Result<(), IMAPError> { 7 | lazy_static! { 8 | static ref greeting_re: Regex = Regex::new("(?i)OK").unwrap(); 9 | }; 10 | if greeting_re.is_match(response) { 11 | Ok(()) 12 | } else { 13 | Err(IMAPError::GreetingError(str::from_utf8(response).unwrap().to_owned())) 14 | } 15 | } 16 | 17 | pub fn validate_login(response: &[u8]) -> Result<(), IMAPError> { 18 | lazy_static! { 19 | static ref login_re: Regex = Regex::new("(?i)OK LOGIN").unwrap(); 20 | }; 21 | if login_re.is_match(response) { 22 | Ok(()) 23 | } else { 24 | Err(IMAPError::GreetingError(str::from_utf8(response).unwrap().to_owned())) 25 | } 26 | } 27 | 28 | pub fn validate_select(response: &[u8]) -> Result<(), IMAPError> { 29 | lazy_static! { 30 | static ref select_re: Regex = Regex::new("(?i)OK SELECT").unwrap(); 31 | }; 32 | if select_re.is_match(response) { 33 | Ok(()) 34 | } else { 35 | Err(IMAPError::GreetingError(str::from_utf8(response).unwrap().to_owned())) 36 | } 37 | } 38 | 39 | pub fn validate_logout(response: &[u8]) -> Result<(), IMAPError> { 40 | unimplemented!(); 41 | } 42 | 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use super::*; 47 | 48 | #[test] 49 | fn test_validate_login() { 50 | let login_str = b"OK LOGIN"; 51 | super::validate_login(&login_str[..]).unwrap(); 52 | let login_str = b"ok login"; 53 | super::validate_login(&login_str[..]).unwrap(); 54 | } 55 | } 56 | --------------------------------------------------------------------------------