diff --git a/Cargo.toml b/Cargo.toml index bbb50a7..58a7f03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,12 +9,14 @@ description = "MBOX reader." repository = "https://github.com/meh/rust-mailbox" keywords = ["mail", "mbox"] +edition = "2021" + [dependencies] -bitflags = "0.9" -owning_ref = "0.3" +bitflags = "1.3" +owning_ref = "0.4" fnv = "1" -nom = "3.0" +nom = "7" casing = "0.1" chrono = "0.4" mime = "0.3" diff --git a/examples/read.rs b/examples/read.rs index bb632e5..58a3177 100644 --- a/examples/read.rs +++ b/examples/read.rs @@ -1,12 +1,12 @@ extern crate mailbox; -use std::fs::File; use std::env; +use std::fs::File; fn main() { - let mbox = mailbox::read(File::open(env::args().nth(1).expect("no file given")).unwrap()); + let mbox = mailbox::read(File::open(env::args().nth(1).expect("no file given")).unwrap()); - for mail in mbox { - println!("{:?}", mail); - } + for mail in mbox { + println!("{:?}", mail); + } } diff --git a/examples/status.rs b/examples/status.rs index 99bcd07..0048653 100644 --- a/examples/status.rs +++ b/examples/status.rs @@ -1,64 +1,64 @@ extern crate mailbox; use mailbox::header; -use std::fs::File; use std::env; +use std::fs::File; #[derive(Eq, PartialEq, Copy, Clone, Default, Debug)] pub struct Status { - pub total: usize, - pub seen: usize, - pub old: usize, - pub answered: usize, - pub flagged: usize, - pub draft: usize, - pub deleted: usize, + pub total: usize, + pub seen: usize, + pub old: usize, + pub answered: usize, + pub flagged: usize, + pub draft: usize, + pub deleted: usize, } fn main() { - let mut status = Status::default(); + let mut status = Status::default(); - for path in env::args().skip(1) { - for mail in mailbox::read(File::open(path).unwrap()).body(false) { - if let Ok(mail) = mail { - let mut current = header::Status::empty(); + for path in env::args().skip(1) { + for mail in mailbox::read(File::open(path).unwrap()).body(false) { + if let Ok(mail) = mail { + let mut current = header::Status::empty(); - if let Some(Ok(s)) = mail.headers().get::() { - current |= s; - } + if let Some(Ok(s)) = mail.headers().get::() { + current |= s; + } - if let Some(Ok(s)) = mail.headers().get_from::("X-Status") { - current |= s; - } + if let Some(Ok(s)) = mail.headers().get_from::("X-Status") { + current |= s; + } - status.total += 1; + status.total += 1; - if current.contains(header::status::SEEN) { - status.seen += 1; - } + if current.contains(header::status::Status::SEEN) { + status.seen += 1; + } - if current.contains(header::status::OLD) { - status.old += 1; - } + if current.contains(header::status::Status::OLD) { + status.old += 1; + } - if current.contains(header::status::ANSWERED) { - status.answered += 1; - } + if current.contains(header::status::Status::ANSWERED) { + status.answered += 1; + } - if current.contains(header::status::FLAGGED) { - status.flagged += 1; - } + if current.contains(header::status::Status::FLAGGED) { + status.flagged += 1; + } - if current.contains(header::status::DRAFT) { - status.draft += 1; - } + if current.contains(header::status::Status::DRAFT) { + status.draft += 1; + } - if current.contains(header::status::DELETED) { - status.deleted += 1; - } - } - } - } + if current.contains(header::status::Status::DELETED) { + status.deleted += 1; + } + } + } + } - println!("{:#?}", status); + println!("{:#?}", status); } diff --git a/examples/stream.rs b/examples/stream.rs index 4d22eb9..e2c819d 100644 --- a/examples/stream.rs +++ b/examples/stream.rs @@ -1,12 +1,12 @@ extern crate mailbox; -use std::fs::File; use std::env; +use std::fs::File; fn main() { - let path = env::args().nth(1).expect("no file given"); + let path = env::args().nth(1).expect("no file given"); - for entry in mailbox::stream::entries(File::open(path).unwrap()) { - println!("{:?}", entry); - } + for entry in mailbox::stream::entries(File::open(path).unwrap()) { + println!("{:?}", entry); + } } diff --git a/src/header/bcc.rs b/src/header/bcc.rs index c692a42..e9603c4 100644 --- a/src/header/bcc.rs +++ b/src/header/bcc.rs @@ -12,40 +12,40 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; +use crate::util::Address; use std::io; use std::ops::Deref; -use stream::entry::header; -use util::Address; -use super::Header; pub struct Bcc(Vec
); impl Header for Bcc { - #[inline(always)] - fn name() -> &'static str { - "Bcc" - } + #[inline(always)] + fn name() -> &'static str { + "Bcc" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - let mut to = Vec::new(); - let string = values[0].clone(); + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + let mut to = Vec::new(); + let string = values[0].clone(); - for slice in string.split(',') { - let start = slice.as_ptr() as usize - string.as_ptr() as usize; - let end = start + slice.len(); + for slice in string.split(',') { + let start = slice.as_ptr() as usize - string.as_ptr() as usize; + let end = start + slice.len(); - to.push(try!(Address::new(string.clone().map(|s| &s[start..end])))); - } + to.push(Address::new(string.clone().map(|s| &s[start..end]))?); + } - Ok(Bcc(to)) - } + Ok(Bcc(to)) + } } impl Deref for Bcc { - type Target = [Address]; + type Target = [Address]; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/cc.rs b/src/header/cc.rs index 2984bd9..0ee3f87 100644 --- a/src/header/cc.rs +++ b/src/header/cc.rs @@ -12,40 +12,40 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; +use crate::util::Address; use std::io; use std::ops::Deref; -use stream::entry::header; -use util::Address; -use super::Header; pub struct Cc(Vec
); impl Header for Cc { - #[inline(always)] - fn name() -> &'static str { - "Cc" - } + #[inline(always)] + fn name() -> &'static str { + "Cc" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - let mut to = Vec::new(); - let string = values[0].clone(); + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + let mut to = Vec::new(); + let string = values[0].clone(); - for slice in string.split(',') { - let start = slice.as_ptr() as usize - string.as_ptr() as usize; - let end = start + slice.len(); + for slice in string.split(',') { + let start = slice.as_ptr() as usize - string.as_ptr() as usize; + let end = start + slice.len(); - to.push(try!(Address::new(string.clone().map(|s| &s[start..end])))); - } + to.push(Address::new(string.clone().map(|s| &s[start..end]))?); + } - Ok(Cc(to)) - } + Ok(Cc(to)) + } } impl Deref for Cc { - type Target = [Address]; + type Target = [Address]; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/content_length.rs b/src/header/content_length.rs index 2ef80a8..adc08cd 100644 --- a/src/header/content_length.rs +++ b/src/header/content_length.rs @@ -12,32 +12,33 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; use std::io; use std::ops::Deref; -use stream::entry::header; -use super::Header; #[derive(Eq, PartialEq, Clone, Debug)] pub struct ContentLength(pub usize); impl Header for ContentLength { - #[inline(always)] - fn name() -> &'static str { - "Content-Length" - } + #[inline(always)] + fn name() -> &'static str { + "Content-Length" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - Ok(ContentLength(try!(values[0].parse().map_err(|_| - io::Error::new(io::ErrorKind::InvalidInput, "invalid content length"))))) - } + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + Ok(ContentLength(values[0].parse().map_err(|_| { + io::Error::new(io::ErrorKind::InvalidInput, "invalid content length") + })?)) + } } impl Deref for ContentLength { - type Target = usize; + type Target = usize; - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/content_transfer_encoding.rs b/src/header/content_transfer_encoding.rs index 5fe2bde..556c170 100644 --- a/src/header/content_transfer_encoding.rs +++ b/src/header/content_transfer_encoding.rs @@ -12,67 +12,73 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. -use std::io; -use stream::entry::header; use super::Header; +use crate::stream::entry::header; use casing::Casing; +use std::io; #[derive(Eq, PartialEq, Clone, Debug)] pub enum ContentTransferEncoding { - Ascii, - ExtendedAscii, - Binary, - QuotedPrintable, - Base64, - Token(String), + Ascii, + ExtendedAscii, + Binary, + QuotedPrintable, + Base64, + Token(String), } impl Header for ContentTransferEncoding { - #[inline(always)] - fn name() -> &'static str { - "Content-Transfer-Encoding" - } + #[inline(always)] + fn name() -> &'static str { + "Content-Transfer-Encoding" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - Ok(match values[0].lower(Default::default()).as_ref() { - "7bit" => ContentTransferEncoding::Ascii, - "8bit" => ContentTransferEncoding::ExtendedAscii, - "binary" => ContentTransferEncoding::Binary, - "quoted-printable" => ContentTransferEncoding::QuotedPrintable, - "base64" => ContentTransferEncoding::Base64, - token => ContentTransferEncoding::Token(token.into()), - }) - } + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + Ok(match values[0].lower(Default::default()).as_ref() { + "7bit" => ContentTransferEncoding::Ascii, + "8bit" => ContentTransferEncoding::ExtendedAscii, + "binary" => ContentTransferEncoding::Binary, + "quoted-printable" => ContentTransferEncoding::QuotedPrintable, + "base64" => ContentTransferEncoding::Base64, + token => ContentTransferEncoding::Token(token.into()), + }) + } } #[cfg(test)] mod test { - use super::*; - use stream::entry::header; - use header::Header; + use super::*; + use crate::header::Header; + use crate::stream::entry::header; - macro_rules! parse { - ($str:expr) => ( - ::parse(&[header::item($str)]) - ); - } + macro_rules! parse { + ($str:expr) => { + ::parse(&[header::item($str)]) + }; + } - #[test] - fn insensitive() { - assert_eq!(parse!("7Bit").unwrap(), ContentTransferEncoding::Ascii); - assert_eq!(parse!("bAsE64").unwrap(), ContentTransferEncoding::Base64); - } + #[test] + fn insensitive() { + assert_eq!(parse!("7Bit").unwrap(), ContentTransferEncoding::Ascii); + assert_eq!(parse!("bAsE64").unwrap(), ContentTransferEncoding::Base64); + } - #[test] - fn ascii() { - assert_eq!(parse!("7BiT").unwrap(), ContentTransferEncoding::Ascii); - assert_eq!(parse!("7bit").unwrap(), ContentTransferEncoding::Ascii); - } + #[test] + fn ascii() { + assert_eq!(parse!("7BiT").unwrap(), ContentTransferEncoding::Ascii); + assert_eq!(parse!("7bit").unwrap(), ContentTransferEncoding::Ascii); + } - #[test] - fn extended_ascii() { - assert_eq!(parse!("8BiT").unwrap(), ContentTransferEncoding::ExtendedAscii); - assert_eq!(parse!("8bit").unwrap(), ContentTransferEncoding::ExtendedAscii); - } + #[test] + fn extended_ascii() { + assert_eq!( + parse!("8BiT").unwrap(), + ContentTransferEncoding::ExtendedAscii + ); + assert_eq!( + parse!("8bit").unwrap(), + ContentTransferEncoding::ExtendedAscii + ); + } } diff --git a/src/header/content_type.rs b/src/header/content_type.rs index fc1e9ac..b27da4c 100644 --- a/src/header/content_type.rs +++ b/src/header/content_type.rs @@ -12,33 +12,34 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; +use mime::Mime; use std::io; use std::ops::Deref; -use mime::Mime; -use stream::entry::header; -use super::Header; #[derive(Eq, PartialEq, Clone, Debug)] pub struct ContentType(Mime); impl Header for ContentType { - #[inline(always)] - fn name() -> &'static str { - "Content-Type" - } + #[inline(always)] + fn name() -> &'static str { + "Content-Type" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - Ok(ContentType(try!(values[0].parse().map_err(|_| - io::Error::new(io::ErrorKind::InvalidInput, "invalid MIME type"))))) - } + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + Ok(ContentType(values[0].parse().map_err(|_| { + io::Error::new(io::ErrorKind::InvalidInput, "invalid MIME type") + })?)) + } } impl Deref for ContentType { - type Target = Mime; + type Target = Mime; - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/date.rs b/src/header/date.rs index d43a4b1..5981212 100644 --- a/src/header/date.rs +++ b/src/header/date.rs @@ -12,34 +12,34 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; +use chrono::{DateTime, FixedOffset}; use std::io; use std::ops::Deref; -use chrono::{DateTime, FixedOffset}; -use stream::entry::header; -use super::Header; #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug)] pub struct Date(DateTime); impl Header for Date { - #[inline(always)] - fn name() -> &'static str { - "Date" - } + #[inline(always)] + fn name() -> &'static str { + "Date" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - Ok(try!(DateTime::parse_from_rfc2822(values[0].as_ref()) - .map(Date) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid date")))) - } + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + DateTime::parse_from_rfc2822(values[0].as_ref()) + .map(Date) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid date")) + } } impl Deref for Date { - type Target = DateTime; + type Target = DateTime; - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/delivered_to.rs b/src/header/delivered_to.rs index f194a1e..1a3dbac 100644 --- a/src/header/delivered_to.rs +++ b/src/header/delivered_to.rs @@ -12,30 +12,30 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; +use crate::util::Address; use std::io; use std::ops::Deref; -use stream::entry::header; -use util::Address; -use super::Header; pub struct DeliveredTo(Address); impl Header for DeliveredTo { - #[inline(always)] - fn name() -> &'static str { - "Delivered-To" - } + #[inline(always)] + fn name() -> &'static str { + "Delivered-To" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - Ok(DeliveredTo(try!(Address::new(values[0].clone())))) - } + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + Ok(DeliveredTo(Address::new(values[0].clone())?)) + } } impl Deref for DeliveredTo { - type Target = Address; + type Target = Address; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/from.rs b/src/header/from.rs index c274c60..60614db 100644 --- a/src/header/from.rs +++ b/src/header/from.rs @@ -12,30 +12,30 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; +use crate::util::Address; use std::io; use std::ops::Deref; -use stream::entry::header; -use util::Address; -use super::Header; pub struct From(Address); impl Header for From { - #[inline(always)] - fn name() -> &'static str { - "From" - } + #[inline(always)] + fn name() -> &'static str { + "From" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - Ok(From(try!(Address::new(values[0].clone())))) - } + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + Ok(From(Address::new(values[0].clone())?)) + } } impl Deref for From { - type Target = Address; + type Target = Address; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/lines.rs b/src/header/lines.rs index 3db7890..f6df761 100644 --- a/src/header/lines.rs +++ b/src/header/lines.rs @@ -12,32 +12,33 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; use std::io; use std::ops::Deref; -use stream::entry::header; -use super::Header; #[derive(Eq, PartialEq, Clone, Debug)] pub struct Lines(pub usize); impl Header for Lines { - #[inline(always)] - fn name() -> &'static str { - "Lines" - } + #[inline(always)] + fn name() -> &'static str { + "Lines" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - Ok(Lines(try!(values[0].parse().map_err(|_| - io::Error::new(io::ErrorKind::InvalidInput, "invalid lines"))))) - } + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + Ok(Lines(values[0].parse().map_err(|_| { + io::Error::new(io::ErrorKind::InvalidInput, "invalid lines") + })?)) + } } impl Deref for Lines { - type Target = usize; + type Target = usize; - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/message_id.rs b/src/header/message_id.rs index ac2ee8a..d9a8d43 100644 --- a/src/header/message_id.rs +++ b/src/header/message_id.rs @@ -12,39 +12,39 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. -use std::io; -use stream::entry::header; -use util::Address; use super::Header; +use crate::stream::entry::header; +use crate::util::Address; +use std::io; pub struct MessageId(pub Address); impl Header for MessageId { - #[inline(always)] - fn name() -> &'static str { - "Message-ID" - } + #[inline(always)] + fn name() -> &'static str { + "Message-ID" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - let address = try!(Address::new(values[0].clone())); + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + let address = Address::new(values[0].clone())?; - if address.host().is_none() { - return Err(io::Error::new(io::ErrorKind::InvalidInput, "missing host")); - } + if address.host().is_none() { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "missing host")); + } - Ok(MessageId(address)) - } + Ok(MessageId(address)) + } } impl MessageId { - #[inline] - pub fn id(&self) -> &str { - self.0.user() - } - - #[inline] - pub fn host(&self) -> &str { - self.0.host().unwrap() - } + #[inline] + pub fn id(&self) -> &str { + self.0.user() + } + + #[inline] + pub fn host(&self) -> &str { + self.0.host().unwrap() + } } diff --git a/src/header/mod.rs b/src/header/mod.rs index 2e1f1cd..327a25e 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -12,12 +12,12 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use crate::stream::entry::header; use std::io; -use stream::entry::header; pub trait Header: Sized { - fn name() -> &'static str; - fn parse(entries: &[header::Item]) -> io::Result; + fn name() -> &'static str; + fn parse(entries: &[header::Item]) -> io::Result; } pub mod status; diff --git a/src/header/references.rs b/src/header/references.rs index f21db20..49d9ece 100644 --- a/src/header/references.rs +++ b/src/header/references.rs @@ -12,40 +12,42 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::{Header, MessageId}; +use crate::stream::entry::header; +use crate::util::Address; use std::io; use std::ops::Deref; -use stream::entry::header; -use util::Address; -use super::{Header, MessageId}; pub struct References(Vec); impl Header for References { - #[inline(always)] - fn name() -> &'static str { - "References" - } - - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - let mut ids = Vec::new(); - let string = values[0].clone(); - - for slice in string.split(',') { - let start = slice.as_ptr() as usize - string.as_ptr() as usize; - let end = start + slice.len(); - - ids.push(MessageId(try!(Address::new(string.clone().map(|s| &s[start..end]))))); - } - - Ok(References(ids)) - } + #[inline(always)] + fn name() -> &'static str { + "References" + } + + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + let mut ids = Vec::new(); + let string = values[0].clone(); + + for slice in string.split(',') { + let start = slice.as_ptr() as usize - string.as_ptr() as usize; + let end = start + slice.len(); + + ids.push(MessageId(Address::new( + string.clone().map(|s| &s[start..end]), + )?)); + } + + Ok(References(ids)) + } } impl Deref for References { - type Target = [MessageId]; + type Target = [MessageId]; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/reply_to.rs b/src/header/reply_to.rs index 0eecb3b..5b9c8cf 100644 --- a/src/header/reply_to.rs +++ b/src/header/reply_to.rs @@ -12,30 +12,30 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; +use crate::util::Address; use std::io; use std::ops::Deref; -use stream::entry::header; -use util::Address; -use super::Header; pub struct ReplyTo(Address); impl Header for ReplyTo { - #[inline(always)] - fn name() -> &'static str { - "Reply-To" - } + #[inline(always)] + fn name() -> &'static str { + "Reply-To" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - Ok(ReplyTo(try!(Address::new(values[0].clone())))) - } + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + Ok(ReplyTo(Address::new(values[0].clone())?)) + } } impl Deref for ReplyTo { - type Target = Address; + type Target = Address; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/return_path.rs b/src/header/return_path.rs index a777f6c..dcd8711 100644 --- a/src/header/return_path.rs +++ b/src/header/return_path.rs @@ -12,30 +12,30 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; +use crate::util::Address; use std::io; use std::ops::Deref; -use stream::entry::header; -use util::Address; -use super::Header; pub struct ReturnPath(Address); impl Header for ReturnPath { - #[inline(always)] - fn name() -> &'static str { - "Return-Path" - } + #[inline(always)] + fn name() -> &'static str { + "Return-Path" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - Ok(ReturnPath(try!(Address::new(values[0].clone())))) - } + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + Ok(ReturnPath(Address::new(values[0].clone())?)) + } } impl Deref for ReturnPath { - type Target = Address; + type Target = Address; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/sender.rs b/src/header/sender.rs index b8192ec..6aaa415 100644 --- a/src/header/sender.rs +++ b/src/header/sender.rs @@ -12,30 +12,30 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; +use crate::util::Address; use std::io; use std::ops::Deref; -use stream::entry::header; -use util::Address; -use super::Header; pub struct Sender(Address); impl Header for Sender { - #[inline(always)] - fn name() -> &'static str { - "Sender" - } + #[inline(always)] + fn name() -> &'static str { + "Sender" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - Ok(Sender(try!(Address::new(values[0].clone())))) - } + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + Ok(Sender(Address::new(values[0].clone())?)) + } } impl Deref for Sender { - type Target = Address; + type Target = Address; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/status.rs b/src/header/status.rs index 5942e58..8235f40 100644 --- a/src/header/status.rs +++ b/src/header/status.rs @@ -12,106 +12,116 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. -use std::io; -use stream::entry::header; use super::Header; +use crate::stream::entry::header; +use std::io; bitflags! { - pub struct Status: u8 { - const NEW = 0b00000001; - const SEEN = 0b00000010; - const OLD = 0b00000100; - const ANSWERED = 0b00001000; - const FLAGGED = 0b00010000; - const DRAFT = 0b00100000; - const DELETED = 0b01000000; - } + pub struct Status: u8 { + const NEW = 0b00000001; + const SEEN = 0b00000010; + const OLD = 0b00000100; + const ANSWERED = 0b00001000; + const FLAGGED = 0b00010000; + const DRAFT = 0b00100000; + const DELETED = 0b01000000; + } } impl Header for Status { - #[inline] - fn name() -> &'static str { - "Status" - } - - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - let mut status = Status::empty(); - - for ch in values[0].chars() { - status |= match ch { - 'N' => NEW, - 'R' => SEEN, - 'O' => OLD, - 'A' => ANSWERED, - 'F' => FLAGGED, - 'T' => DRAFT, - 'D' => DELETED, - - _ => - return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid status")) - } - } - - Ok(status) - } + #[inline] + fn name() -> &'static str { + "Status" + } + + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + let mut status = Status::empty(); + + for ch in values[0].chars() { + status |= match ch { + 'N' => Status::NEW, + 'R' => Status::SEEN, + 'O' => Status::OLD, + 'A' => Status::ANSWERED, + 'F' => Status::FLAGGED, + 'T' => Status::DRAFT, + 'D' => Status::DELETED, + + _ => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid status", + )) + } + } + } + + Ok(status) + } } #[cfg(test)] mod test { - use super::*; - use stream::entry::header; - use header::Header; - - macro_rules! parse { - ($str:expr) => ( - ::parse(&[header::item($str)]) - ); - } - - #[test] - fn new() { - assert_eq!(parse!("N").unwrap(), NEW); - } - - #[test] - fn read() { - assert_eq!(parse!("R").unwrap(), SEEN); - } - - #[test] - fn old() { - assert_eq!(parse!("O").unwrap(), OLD); - } - - #[test] - fn answered() { - assert_eq!(parse!("A").unwrap(), ANSWERED); - } - - #[test] - fn flagged() { - assert_eq!(parse!("F").unwrap(), FLAGGED); - } - - #[test] - fn draft() { - assert_eq!(parse!("T").unwrap(), DRAFT); - } - - #[test] - fn deleted() { - assert_eq!(parse!("D").unwrap(), DELETED); - } - - #[test] - fn mixed() { - assert_eq!(parse!("ROD").unwrap(), SEEN | OLD | DELETED); - assert_eq!(parse!("FTA").unwrap(), FLAGGED | DRAFT | ANSWERED); - } - - #[test] - fn fail() { - assert!(parse!("ANTANI").is_err()); - } + use super::*; + use crate::header::Header; + use crate::stream::entry::header; + + macro_rules! parse { + ($str:expr) => { + ::parse(&[header::item($str)]) + }; + } + + #[test] + fn new() { + assert_eq!(parse!("N").unwrap(), Status::NEW); + } + + #[test] + fn read() { + assert_eq!(parse!("R").unwrap(), Status::SEEN); + } + + #[test] + fn old() { + assert_eq!(parse!("O").unwrap(), Status::OLD); + } + + #[test] + fn answered() { + assert_eq!(parse!("A").unwrap(), Status::ANSWERED); + } + + #[test] + fn flagged() { + assert_eq!(parse!("F").unwrap(), Status::FLAGGED); + } + + #[test] + fn draft() { + assert_eq!(parse!("T").unwrap(), Status::DRAFT); + } + + #[test] + fn deleted() { + assert_eq!(parse!("D").unwrap(), Status::DELETED); + } + + #[test] + fn mixed() { + assert_eq!( + parse!("ROD").unwrap(), + Status::SEEN | Status::OLD | Status::DELETED + ); + assert_eq!( + parse!("FTA").unwrap(), + Status::FLAGGED | Status::DRAFT | Status::ANSWERED + ); + } + + #[test] + fn fail() { + assert!(parse!("ANTANI").is_err()); + } } diff --git a/src/header/subject.rs b/src/header/subject.rs index 97698a3..4d49a83 100644 --- a/src/header/subject.rs +++ b/src/header/subject.rs @@ -12,30 +12,30 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; use std::io; use std::ops::Deref; -use stream::entry::header; -use super::Header; pub struct Subject(header::Item); impl Header for Subject { - #[inline(always)] - fn name() -> &'static str { - "Subject" - } + #[inline(always)] + fn name() -> &'static str { + "Subject" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - Ok(Subject(values[0].clone())) - } + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + Ok(Subject(values[0].clone())) + } } impl Deref for Subject { - type Target = str; + type Target = str; - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/to.rs b/src/header/to.rs index 029ec0b..562fd5d 100644 --- a/src/header/to.rs +++ b/src/header/to.rs @@ -12,30 +12,30 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; +use crate::util::Address; use std::io; use std::ops::Deref; -use stream::entry::header; -use util::Address; -use super::Header; pub struct To(Address); impl Header for To { - #[inline(always)] - fn name() -> &'static str { - "To" - } + #[inline(always)] + fn name() -> &'static str { + "To" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - Ok(To(try!(Address::new(values[0].clone())))) - } + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + Ok(To(Address::new(values[0].clone())?)) + } } impl Deref for To { - type Target = Address; + type Target = Address; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/user_agent.rs b/src/header/user_agent.rs index 81bc4a9..cea32fc 100644 --- a/src/header/user_agent.rs +++ b/src/header/user_agent.rs @@ -12,30 +12,30 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; use std::io; use std::ops::Deref; -use stream::entry::header; -use super::Header; pub struct UserAgent(header::Item); impl Header for UserAgent { - #[inline(always)] - fn name() -> &'static str { - "User-Agent" - } + #[inline(always)] + fn name() -> &'static str { + "User-Agent" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - Ok(UserAgent(values[0].clone())) - } + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + Ok(UserAgent(values[0].clone())) + } } impl Deref for UserAgent { - type Target = str; + type Target = str; - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/x_envelope_from.rs b/src/header/x_envelope_from.rs index 5346381..a4fe026 100644 --- a/src/header/x_envelope_from.rs +++ b/src/header/x_envelope_from.rs @@ -12,30 +12,30 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; +use crate::util::Address; use std::io; use std::ops::Deref; -use stream::entry::header; -use util::Address; -use super::Header; pub struct XEnvelopeFrom(Address); impl Header for XEnvelopeFrom { - #[inline(always)] - fn name() -> &'static str { - "X-Envelope-From" - } + #[inline(always)] + fn name() -> &'static str { + "X-Envelope-From" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - Ok(XEnvelopeFrom(try!(Address::new(values[0].clone())))) - } + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + Ok(XEnvelopeFrom(Address::new(values[0].clone())?)) + } } impl Deref for XEnvelopeFrom { - type Target = Address; + type Target = Address; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/header/x_remote_addr.rs b/src/header/x_remote_addr.rs index 2279398..3981f32 100644 --- a/src/header/x_remote_addr.rs +++ b/src/header/x_remote_addr.rs @@ -12,33 +12,34 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::Header; +use crate::stream::entry::header; use std::io; -use std::ops::Deref; use std::net::IpAddr; -use stream::entry::header; -use super::Header; +use std::ops::Deref; #[derive(Eq, PartialEq, Clone, Debug)] pub struct XRemoteAddr(IpAddr); impl Header for XRemoteAddr { - #[inline(always)] - fn name() -> &'static str { - "X-Remote-Addr" - } + #[inline(always)] + fn name() -> &'static str { + "X-Remote-Addr" + } - #[inline] - fn parse(values: &[header::Item]) -> io::Result { - Ok(XRemoteAddr(try!(values[0].parse().map_err(|_| - io::Error::new(io::ErrorKind::InvalidInput, "invalid IP address"))))) - } + #[inline] + fn parse(values: &[header::Item]) -> io::Result { + Ok(XRemoteAddr(values[0].parse().map_err(|_| { + io::Error::new(io::ErrorKind::InvalidInput, "invalid IP address") + })?)) + } } impl Deref for XRemoteAddr { - type Target = IpAddr; + type Target = IpAddr; - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/src/iter.rs b/src/iter.rs index 94d2d76..b758b43 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -12,109 +12,97 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use crate::mail::{Body, Headers, Mail}; +use crate::stream::{self, Entry}; use std::io::{self, Read}; -use stream::{self, Entry}; -use mail::{Mail, Headers, Body}; pub struct Iter { - input: stream::Iter, - body: bool, + input: stream::Iter, + body: bool, } impl Iter { - #[inline] - pub fn new(input: R) -> Self { - Iter { - input: stream::entries(input), - body: true, - } - } - - #[inline] - pub fn body(&mut self, value: bool) -> &mut Self { - self.body = value; - self - } + #[inline] + pub fn new(input: R) -> Self { + Iter { + input: stream::entries(input), + body: true, + } + } + + #[inline] + pub fn body(&mut self, value: bool) -> &mut Self { + self.body = value; + self + } } impl Iterator for Iter { - type Item = io::Result; - - fn next(&mut self) -> Option { - macro_rules! eof { - ($body:expr) => ( - if let Some(value) = $body { - value - } - else { - return None; - } - ); - } - - macro_rules! try { - ($body:expr) => ( - match $body { - Ok(value) => - value, - - Err(err) => - return Some(Err(err.into())) - } - ); - } - - // The first entry must be an `Entry::Begin`. - let (offset, origin) = if let Entry::Begin(offset, origin) = try!(eof!(self.input.next())) { - (offset, origin) - } - else { - return Some(Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid state"))); - }; - - let mut headers = Headers::default(); - let mut body = Body::default(); - let mut ended = false; - - // Read headers. - loop { - match try!(eof!(self.input.next())) { - // This shouldn't happen. - Entry::Begin(..) => { - return Some(Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid state"))); - } - - // Insert the header. - Entry::Header(header) => { - headers.insert(header); - } - - // The body started. - Entry::Body(value) => { - if self.body { - body.append(value); - } - - break; - } - - // There was no body. - Entry::End => { - ended = true; - break; - } - } - } - - // Read body if there is one. - if !ended { - while let Entry::Body(value) = try!(eof!(self.input.next())) { - if self.body { - body.append(value); - } - } - } - - Some(Ok(Mail::new(offset, origin, headers, body))) - } + type Item = io::Result; + + fn next(&mut self) -> Option { + macro_rules! eof { + ($body:expr) => { + self.input.next()? + }; + } + + // The first entry must be an `Entry::Begin`. + let (offset, origin) = if let Entry::Begin(offset, origin) = eof!(self.input.next()).ok()? { + (offset, origin) + } else { + return Some(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid state", + ))); + }; + + let mut headers = Headers::default(); + let mut body = Body::default(); + let mut ended = false; + + // Read headers. + loop { + match eof!(self.input.next()).ok()? { + // This shouldn't happen. + Entry::Begin(..) => { + return Some(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid state", + ))); + } + + // Insert the header. + Entry::Header(header) => { + headers.insert(header); + } + + // The body started. + Entry::Body(value) => { + if self.body { + body.append(value); + } + + break; + } + + // There was no body. + Entry::End => { + ended = true; + break; + } + } + } + + // Read body if there is one. + if !ended { + while let Entry::Body(value) = eof!(self.input.next()).ok()? { + if self.body { + body.append(value); + } + } + } + + Some(Ok(Mail::new(offset, origin, headers, body))) + } } diff --git a/src/lib.rs b/src/lib.rs index 975df77..57d2e38 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,33 +14,32 @@ #[macro_use] extern crate bitflags; -extern crate owning_ref; extern crate fnv; +extern crate owning_ref; -#[macro_use] -extern crate nom; extern crate casing; extern crate chrono; extern crate mime; +extern crate nom; #[macro_use] mod util; -pub use util::Address; +pub use crate::util::Address; pub mod header; -pub use header::Header; +pub use crate::header::Header; pub mod stream; pub mod mail; -pub use mail::Mail; +pub use crate::mail::Mail; mod iter; -pub use iter::Iter; +pub use crate::iter::Iter; use std::io::Read; #[inline] pub fn read(input: R) -> Iter { - Iter::new(input) + Iter::new(input) } diff --git a/src/mail/body.rs b/src/mail/body.rs index 07957ee..0f03308 100644 --- a/src/mail/body.rs +++ b/src/mail/body.rs @@ -18,114 +18,113 @@ use std::slice; pub struct Body(Vec>); impl Body { - #[inline] - pub(crate) fn append(&mut self, data: Vec) { - self.0.push(data); - } - - #[inline] - pub fn len(&self) -> usize { - self.0.iter().map(|v| v.len()).sum::() + self.0.len() * 2 - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - #[inline] - pub fn iter(&self) -> Iter { - Iter { - parent: self.0.iter(), - child: None, - separator: Separator::None, - } - } + #[inline] + pub(crate) fn append(&mut self, data: Vec) { + self.0.push(data); + } + + #[inline] + pub fn len(&self) -> usize { + self.0.iter().map(|v| v.len()).sum::() + self.0.len() * 2 + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + #[inline] + pub fn iter(&self) -> Iter { + Iter { + parent: self.0.iter(), + child: None, + separator: Separator::None, + } + } } impl<'a> IntoIterator for &'a Body { - type Item = u8; - type IntoIter = Iter<'a>; + type Item = u8; + type IntoIter = Iter<'a>; - #[inline] - fn into_iter(self) -> Iter<'a> { - self.iter() - } + #[inline] + fn into_iter(self) -> Iter<'a> { + self.iter() + } } pub struct Iter<'a> { - parent: slice::Iter<'a, Vec>, - child: Option>, - separator: Separator, + parent: slice::Iter<'a, Vec>, + child: Option>, + separator: Separator, } #[derive(Eq, PartialEq, Copy, Clone, Debug)] enum Separator { - CarriageReturn, - LineFeed, - None, + CarriageReturn, + LineFeed, + None, } impl<'a> Iterator for Iter<'a> { - type Item = u8; - - fn next(&mut self) -> Option { - loop { - match self.separator { - Separator::CarriageReturn => { - self.separator = Separator::LineFeed; - return Some(b'\r'); - } - - Separator::LineFeed => { - self.separator = Separator::None; - return Some(b'\n'); - } - - Separator::None => { - if self.child.is_some() { - if let Some(&byte) = self.child.as_mut().unwrap().next() { - return Some(byte); - } - else { - self.child = None; - self.separator = Separator::CarriageReturn; - } - } - else { - self.child = self.parent.next().map(|v| v.iter()); - - if self.child.is_none() { - return None; - } - } - } - } - } - } + type Item = u8; + + fn next(&mut self) -> Option { + loop { + match self.separator { + Separator::CarriageReturn => { + self.separator = Separator::LineFeed; + return Some(b'\r'); + } + + Separator::LineFeed => { + self.separator = Separator::None; + return Some(b'\n'); + } + + Separator::None => { + if self.child.is_some() { + if let Some(&byte) = self.child.as_mut().unwrap().next() { + return Some(byte); + } else { + self.child = None; + self.separator = Separator::CarriageReturn; + } + } else { + self.child = self.parent.next().map(|v| v.iter()); + + self.child.as_ref()?; + } + } + } + } + } } #[cfg(test)] mod test { - use super::*; - - #[test] - fn len() { - let mut body = Body::default(); - body.append(vec![1, 2, 3]); - body.append(vec![4]); - body.append(vec![5, 6]); - - assert_eq!(body.len(), 12); - } - - #[test] - fn iter() { - let mut body = Body::default(); - body.append(vec![1, 2, 3]); - body.append(vec![4]); - body.append(vec![5, 6]); - - assert_eq!(body.iter().collect::>(), vec![1, 2, 3, b'\r', b'\n', 4, b'\r', b'\n', 5, 6, b'\r', b'\n']); - } + use super::*; + + #[test] + fn len() { + let mut body = Body::default(); + body.append(vec![1, 2, 3]); + body.append(vec![4]); + body.append(vec![5, 6]); + + assert_eq!(body.len(), 12); + } + + #[test] + fn iter() { + let mut body = Body::default(); + body.append(vec![1, 2, 3]); + body.append(vec![4]); + body.append(vec![5, 6]); + + assert_eq!( + body.iter().collect::>(), + vec![1, 2, 3, b'\r', b'\n', 4, b'\r', b'\n', 5, 6, b'\r', b'\n'] + ); + } } diff --git a/src/mail/headers.rs b/src/mail/headers.rs index fdaf8c3..b59469b 100644 --- a/src/mail/headers.rs +++ b/src/mail/headers.rs @@ -12,124 +12,125 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use crate::header::Header; +use crate::stream::entry::{self, header}; +use casing::Casing; +use fnv::FnvHasher; use std::collections::{hash_map, HashMap}; use std::hash::BuildHasherDefault; use std::io; -use fnv::FnvHasher; -use casing::Casing; -use header::Header; -use stream::entry::{self, header}; #[derive(Clone, Default, Debug)] pub struct Headers(HashMap, BuildHasherDefault>); impl Headers { - #[inline] - pub(crate) fn insert(&mut self, header: entry::Header) { - self.0.entry(header.key()).or_insert_with(Vec::new).push(header.value()); - } - - #[inline] - pub fn get(&self) -> Option> { - self.0.get(H::name()) - .map(|v| H::parse(v.as_ref())) - } - - #[inline] - pub fn get_from>(&self, key: T) -> Option> { - self.0.get(key.as_ref().header(Default::default()).as_ref()) - .map(|v| H::parse(v.as_ref())) - } - - #[inline] - pub fn get_raw>(&self, key: T) -> Option<&[header::Item]> { - self.0.get(key.as_ref().header(Default::default()).as_ref()) - .map(|v| v.as_ref()) - } - - #[inline] - pub fn len(&self) -> usize { - self.0.len() - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - #[inline] - pub fn contains_key>(&self, key: T) -> bool { - self.0.contains_key(key.as_ref().header(Default::default()).as_ref()) - } - - #[inline] - pub fn contains(&self) -> bool { - self.0.contains_key(H::name()) - } - - #[inline] - pub fn keys(&self) -> hash_map::Keys> { - self.0.keys() - } - - #[inline] - pub fn iter(&self) -> Iter { - Iter(self.0.iter()) - } + #[inline] + pub(crate) fn insert(&mut self, header: entry::Header) { + self.0 + .entry(header.key()) + .or_insert_with(Vec::new) + .push(header.value()); + } + + #[inline] + pub fn get(&self) -> Option> { + self.0.get(H::name()).map(|v| H::parse(v.as_ref())) + } + + #[inline] + pub fn get_from>(&self, key: T) -> Option> { + self.0 + .get(key.as_ref().header(Default::default()).as_ref()) + .map(|v| H::parse(v.as_ref())) + } + + #[inline] + pub fn get_raw>(&self, key: T) -> Option<&[header::Item]> { + self.0 + .get(key.as_ref().header(Default::default()).as_ref()) + .map(|v| v.as_ref()) + } + + #[inline] + pub fn len(&self) -> usize { + self.0.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + #[inline] + pub fn contains_key>(&self, key: T) -> bool { + self.0 + .contains_key(key.as_ref().header(Default::default()).as_ref()) + } + + #[inline] + pub fn contains(&self) -> bool { + self.0.contains_key(H::name()) + } + + #[inline] + pub fn keys(&self) -> hash_map::Keys> { + self.0.keys() + } + + #[inline] + pub fn iter(&self) -> Iter { + Iter(self.0.iter()) + } } pub struct HeaderView<'a> { - key: &'a header::Item, - values: &'a [header::Item], + key: &'a header::Item, + values: &'a [header::Item], } impl<'a> HeaderView<'a> { - #[inline] - pub fn is(&self) -> bool { - self.key.as_ref() == H::name() - } - - #[inline] - pub fn name(&self) -> &str { - self.key.as_ref() - } - - #[inline] - pub fn value(&self) -> io::Result { - H::parse(self.values) - } - - #[inline] - pub fn raw(&self) -> &[header::Item] { - self.values - } + #[inline] + pub fn is(&self) -> bool { + self.key.as_ref() == H::name() + } + + #[inline] + pub fn name(&self) -> &str { + self.key.as_ref() + } + + #[inline] + pub fn value(&self) -> io::Result { + H::parse(self.values) + } + + #[inline] + pub fn raw(&self) -> &[header::Item] { + self.values + } } impl<'a> IntoIterator for &'a Headers { - type Item = HeaderView<'a>; - type IntoIter = Iter<'a>; + type Item = HeaderView<'a>; + type IntoIter = Iter<'a>; - #[inline] - fn into_iter(self) -> Iter<'a> { - self.iter() - } + #[inline] + fn into_iter(self) -> Iter<'a> { + self.iter() + } } pub struct Iter<'a>(hash_map::Iter<'a, header::Item, Vec>); impl<'a> Iterator for Iter<'a> { - type Item = HeaderView<'a>; - - #[inline] - fn next(&mut self) -> Option { - if let Some((key, values)) = self.0.next() { - Some(HeaderView { - key: key, - values: values, - }) - } - else { - None - } - } + type Item = HeaderView<'a>; + + #[inline] + fn next(&mut self) -> Option { + if let Some((key, values)) = self.0.next() { + Some(HeaderView { key, values }) + } else { + None + } + } } diff --git a/src/mail/mail.rs b/src/mail/mail.rs index edae41a..05978ce 100644 --- a/src/mail/mail.rs +++ b/src/mail/mail.rs @@ -12,45 +12,45 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. -use stream; -use super::{Headers, Body}; +use super::{Body, Headers}; +use crate::stream; #[derive(Clone, Debug)] pub struct Mail { - offset: u64, - origin: stream::entry::Begin, - headers: Headers, - body: Body, + offset: u64, + origin: stream::entry::Begin, + headers: Headers, + body: Body, } impl Mail { - #[inline] - pub fn new(offset: u64, origin: stream::entry::Begin, headers: Headers, body: Body) -> Self { - Mail { - offset: offset, - origin: origin, - headers: headers, - body: body, - } - } + #[inline] + pub fn new(offset: u64, origin: stream::entry::Begin, headers: Headers, body: Body) -> Self { + Mail { + offset, + origin, + headers, + body, + } + } - #[inline] - pub fn offset(&self) -> u64 { - self.offset - } + #[inline] + pub fn offset(&self) -> u64 { + self.offset + } - #[inline] - pub fn origin(&self) -> &stream::entry::Begin { - &self.origin - } + #[inline] + pub fn origin(&self) -> &stream::entry::Begin { + &self.origin + } - #[inline] - pub fn headers(&self) -> &Headers { - &self.headers - } + #[inline] + pub fn headers(&self) -> &Headers { + &self.headers + } - #[inline] - pub fn body(&self) -> &Body { - &self.body - } + #[inline] + pub fn body(&self) -> &Body { + &self.body + } } diff --git a/src/stream/entry/begin.rs b/src/stream/entry/begin.rs index 1a5a40a..2da440d 100644 --- a/src/stream/entry/begin.rs +++ b/src/stream/entry/begin.rs @@ -12,103 +12,122 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. -use std::ops::Range; use std::io; -use nom::IResult; +use std::ops::Range; +use std::str; /// The beginning of a new email. #[derive(Eq, PartialEq, Clone, Debug)] pub struct Begin { - inner: String, + inner: String, - address: Range, - timestamp: Range, + address: Range, + timestamp: Range, } impl Begin { - #[inline] - pub(crate) fn ranges>(string: T) -> io::Result<(Range, Range)> { - let string = string.as_ref(); - - if let IResult::Done(_, (address, timestamp)) = parser::parse(string) { - if timestamp.len() == 24 { - let a = address.as_ptr() as usize - string.as_ptr() as usize; - let t = timestamp.as_ptr() as usize - string.as_ptr() as usize; - - return Ok(( - Range { start: a, end: a + address.len() }, - Range { start: t, end: t + timestamp.len() }, - )); - } - } - - Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid beginning")) - } - - /// Create a new `Begin` from the given `String`. - #[inline] - pub fn new>>(string: T) -> io::Result { - let string = string.into(); - let (address, timestamp) = try!(Begin::ranges(&string)); - - Ok(Begin { - // The parser verifies the content is US-ASCII, so it's safe. - inner: unsafe { String::from_utf8_unchecked(string) }, - - address: address, - timestamp: timestamp, - }) - } - - /// The origin address, by RFC this can be any address ever used in any - /// system at any time. - #[inline] - pub fn address(&self) -> &str { - &self.inner[Range { start: self.address.start, end: self.address.end }] - } - - /// The timestamp. - #[inline] - pub fn timestamp(&self) -> &str { - &self.inner[Range { start: self.timestamp.start, end: self.timestamp.end }] - } + #[inline] + pub(crate) fn ranges>(string: T) -> io::Result<(Range, Range)> { + let string = string.as_ref(); + + if let Ok((_, (address, timestamp))) = parser::parse(string) { + let a = address.as_ptr() as usize - string.as_ptr() as usize; + let t = timestamp.as_ptr() as usize - string.as_ptr() as usize; + + return Ok(( + Range { + start: a, + end: a + address.len(), + }, + Range { + start: t, + end: t + timestamp.len(), + }, + )); + } + + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid beginning", + )) + } + + /// Create a new `Begin` from the given `String`. + #[inline] + pub fn new>>(string: T) -> io::Result { + let string = string.into(); + let (address, timestamp) = Begin::ranges(&string)?; + + Ok(Begin { + // The parser verifies the content is US-ASCII, so it's safe. + inner: unsafe { String::from_utf8_unchecked(string) }, + + address, + timestamp, + }) + } + + /// The origin address, by RFC this can be any address ever used in any + /// system at any time. + #[inline] + pub fn address(&self) -> &str { + &self.inner[Range { + start: self.address.start, + end: self.address.end, + }] + } + + /// The timestamp. + #[inline] + pub fn timestamp(&self) -> &str { + &self.inner[Range { + start: self.timestamp.start, + end: self.timestamp.end, + }] + } } mod parser { - use util::parser::{is_ws, is_printable, is_printable_or_ws}; - - named!(pub parse(&[u8]) -> (&[u8], &[u8]), - do_parse!( - tag!("From ") >> - take_while!(is_ws) >> - addr: address >> - take_while!(is_ws) >> - time: timestamp >> - eof!() >> - - (addr, time))); - - named!(address(&[u8]) -> &[u8], - take_while!(is_printable)); - - named!(timestamp(&[u8]) -> &[u8], - take_while_n!(24, is_printable_or_ws)); + use crate::util::parser::{is_printable, is_printable_or_ws, is_ws}; + use nom::bytes::complete::{tag, take_while, take_while1}; + use nom::sequence::tuple; + use nom::IResult; + + pub fn parse(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> { + let (input, (_, _, address, _, timestamp)) = tuple(( + tag("From"), + take_while1(is_ws), + address, + take_while1(is_ws), + timestamp, + ))(input)?; + Ok((input, (address, timestamp))) + } + + pub fn address(input: &[u8]) -> IResult<&[u8], &[u8]> { + take_while(is_printable)(input) + } + + pub fn timestamp(input: &[u8]) -> IResult<&[u8], &[u8]> { + take_while(is_printable_or_ws)(input) + } } #[cfg(test)] mod test { - use super::*; - - #[test] - fn ok() { - let v = Begin::new("From foo@example.com Wed Nov 17 14:35:53 2010").unwrap(); - assert_eq!(v.address(), "foo@example.com"); - assert_eq!(v.timestamp(), "Wed Nov 17 14:35:53 2010"); - } - - #[test] - fn fail() { - assert!(Begin::new("From foo@example.com").is_err()); - assert!(Begin::new("From foo@example.com Wed Nov 17 14:35:53 20109").is_err()); - } + use super::*; + + #[test] + fn ok() { + let v = Begin::new("From foo@example.com Wed Nov 17 14:35:53 2010").unwrap(); + assert_eq!(v.address(), "foo@example.com"); + assert_eq!(v.timestamp(), "Wed Nov 17 14:35:53 2010"); + } + + #[test] + fn ok_gmail() { + let v = Begin::new("From 1668703170433825012@xxx Fri Jun 05 23:22:35 +0000 2020").unwrap(); + assert_eq!(v.address(), "1668703170433825012@xxx"); + assert_eq!(v.timestamp(), "Fri Jun 05 23:22:35 +0000 2020"); + } } diff --git a/src/stream/entry/header.rs b/src/stream/entry/header.rs index 133db9a..ba99172 100644 --- a/src/stream/entry/header.rs +++ b/src/stream/entry/header.rs @@ -12,21 +12,20 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. -use std::ops::Range; +use casing::Casing; +use owning_ref::OwningRef; +use std::borrow::Cow; use std::io; +use std::ops::Range; use std::rc::Rc; -use std::borrow::Cow; -use owning_ref::OwningRef; -use nom::IResult; -use casing::Casing; /// A header in an email. #[derive(Eq, PartialEq, Clone, Debug)] pub struct Header { - inner: Item, + inner: Item, - key: Range, - value: Range, + key: Range, + value: Range, } /// A header item. @@ -37,94 +36,118 @@ pub type Item = OwningRef, str>; #[inline(always)] pub(crate) fn item>(string: T) -> Item { - OwningRef::new(Rc::new(string.into())).map(|s| s.as_ref()) + OwningRef::new(Rc::new(string.into())).map(|s| s.as_ref()) } impl Header { - #[inline] - pub(crate) fn ranges>(string: T) -> io::Result<(Range, Range)> { - let string = string.as_ref(); - - if let IResult::Done(_, (key, value)) = parser::parse(string) { - let k = key.as_ptr() as usize - string.as_ptr() as usize; - let v = value.as_ptr() as usize - string.as_ptr() as usize; - - Ok(( - Range { start: k, end: k + key.len() }, - Range { start: v, end: v + value.len() }, - )) - } - else { - Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid header")) - } - } - - /// Create a new `Header` from the given `String`. - #[inline] - pub fn new>>(string: T) -> io::Result { - let string = string.into(); - let (key, value) = try!(Header::ranges(&string)); - - Ok(Header { - // The parser verifies the content is US-ASCII, so it's safe. - inner: item(unsafe { String::from_utf8_unchecked(string) }), - - key: key, - value: value, - }) - } - - /// The header key in the proper case. - /// - /// Note that this allocates only if the key is not already in the proper case. - #[inline] - pub fn key(&self) -> Item { - match (&self.inner[Range { start: self.key.start, end: self.key.end }]).header(Default::default()) { - Cow::Borrowed(_) => self.inner.clone().map(|s| &s[Range { start: self.key.start, end: self.key.end }]), - Cow::Owned(string) => OwningRef::new(Rc::new(string)).map(|s| s.as_ref()), - } - } - - /// The header value. - #[inline] - pub fn value(&self) -> Item { - self.inner.clone().map(|s| &s[Range { start: self.value.start, end: self.value.end }]) - } + #[inline] + pub(crate) fn ranges>(string: T) -> io::Result<(Range, Range)> { + let string = string.as_ref(); + + if let Ok((_, (key, value))) = parser::parse(string) { + let k = key.as_ptr() as usize - string.as_ptr() as usize; + let v = value.as_ptr() as usize - string.as_ptr() as usize; + + Ok(( + Range { + start: k, + end: k + key.len(), + }, + Range { + start: v, + end: v + value.len(), + }, + )) + } else { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid header", + )) + } + } + + /// Create a new `Header` from the given `String`. + #[inline] + pub fn new>>(string: T) -> io::Result { + let string = string.into(); + let (key, value) = Header::ranges(&string)?; + + Ok(Header { + // The parser verifies the content is US-ASCII, so it's safe. + inner: item(unsafe { String::from_utf8_unchecked(string) }), + + key, + value, + }) + } + + /// The header key in the proper case. + /// + /// Note that this allocates only if the key is not already in the proper case. + #[inline] + pub fn key(&self) -> Item { + match (&self.inner[Range { + start: self.key.start, + end: self.key.end, + }]) + .header(Default::default()) + { + Cow::Borrowed(_) => self.inner.clone().map(|s| { + &s[Range { + start: self.key.start, + end: self.key.end, + }] + }), + Cow::Owned(string) => OwningRef::new(Rc::new(string)).map(|s| s.as_ref()), + } + } + + /// The header value. + #[inline] + pub fn value(&self) -> Item { + self.inner.clone().map(|s| { + &s[Range { + start: self.value.start, + end: self.value.end, + }] + }) + } } mod parser { - use util::parser::{is_ws, is_printable_no_colon, is_printable_or_ws}; - - named!(pub parse(&[u8]) -> (&[u8], &[u8]), - do_parse!( - key: key >> - char!(':') >> - take_while!(is_ws) >> - value: value >> - eof!() >> - - (key, value))); - - named!(key(&[u8]) -> &[u8], - take_while!(is_printable_no_colon)); - - named!(value(&[u8]) -> &[u8], - take_while!(is_printable_or_ws)); + use crate::util::parser::{is_printable_no_colon, is_printable_or_ws, is_ws}; + use nom::bytes::complete::take_while; + use nom::character::complete::char; + use nom::sequence::tuple; + use nom::IResult; + + pub fn parse(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> { + let (input, (key, _, _, value)) = tuple((key, char(':'), take_while(is_ws), value))(input)?; + Ok((input, (key, value))) + } + + pub fn key(input: &[u8]) -> IResult<&[u8], &[u8]> { + take_while(is_printable_no_colon)(input) + } + + pub fn value(input: &[u8]) -> IResult<&[u8], &[u8]> { + take_while(is_printable_or_ws)(input) + } } #[cfg(test)] mod test { - use super::*; - - #[test] - fn ok() { - let v = Header::new("From: meh. ").unwrap(); - assert_eq!(&*v.key(), "From"); - assert_eq!(&*v.value(), "meh. "); - } - - #[test] - fn fail() { - assert!(Header::new("From foo@example.com").is_err()); - } + use super::*; + + #[test] + fn ok() { + let v = Header::new("From: meh. ").unwrap(); + assert_eq!(&*v.key(), "From"); + assert_eq!(&*v.value(), "meh. "); + } + + #[test] + fn fail() { + assert!(Header::new("From foo@example.com").is_err()); + } } diff --git a/src/stream/entry/mod.rs b/src/stream/entry/mod.rs index b4d3362..d5ebe50 100644 --- a/src/stream/entry/mod.rs +++ b/src/stream/entry/mod.rs @@ -17,18 +17,18 @@ /// Note that there are no allocations or copies from `stream::Lines`. #[derive(Clone, Debug)] pub enum Entry { - /// The beginning of an email, includes the absolute offset from the input - /// and the origin. - Begin(u64, Begin), + /// The beginning of an email, includes the absolute offset from the input + /// and the origin. + Begin(u64, Begin), - /// A header. - Header(Header), + /// A header. + Header(Header), - /// A line of body. - Body(Vec), + /// A line of body. + Body(Vec), - /// The end of the email. - End, + /// The end of the email. + End, } mod begin; @@ -39,29 +39,29 @@ pub use self::header::Header; #[cfg(test)] mod test { - use super::*; + use super::*; - #[test] - fn begin_ok() { - let v = Begin::new("From foo@example.com Wed Nov 17 14:35:53 2010").unwrap(); - assert_eq!(v.address(), "foo@example.com"); - assert_eq!(v.timestamp(), "Wed Nov 17 14:35:53 2010"); - } + #[test] + fn begin_ok() { + let v = Begin::new("From foo@example.com Wed Nov 17 14:35:53 2010").unwrap(); + assert_eq!(v.address(), "foo@example.com"); + assert_eq!(v.timestamp(), "Wed Nov 17 14:35:53 2010"); + } - #[test] - fn begin_fail() { - assert!(Begin::new("From foo@example.com").is_err()); - } + #[test] + fn begin_fail() { + assert!(Begin::new("From foo@example.com").is_err()); + } - #[test] - fn header_ok() { - let v = Header::new("From: meh. ").unwrap(); - assert_eq!(&*v.key(), "From"); - assert_eq!(&*v.value(), "meh. "); - } + #[test] + fn header_ok() { + let v = Header::new("From: meh. ").unwrap(); + assert_eq!(&*v.key(), "From"); + assert_eq!(&*v.value(), "meh. "); + } - #[test] - fn header_fail() { - assert!(Header::new("From foo@example.com").is_err()); - } + #[test] + fn header_fail() { + assert!(Header::new("From foo@example.com").is_err()); + } } diff --git a/src/stream/iter.rs b/src/stream/iter.rs index 593ae79..63c17f5 100644 --- a/src/stream/iter.rs +++ b/src/stream/iter.rs @@ -12,9 +12,9 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use super::{entry, Entry, Lines}; use std::io::{self, BufReader, Read}; use std::iter::Peekable; -use super::{entry, Entry, Lines}; /// `Iterator` over line based entries. /// @@ -31,183 +31,169 @@ use super::{entry, Entry, Lines}; /// This is then leveraged by `mail::Iter` to expose a more ergonomic API over /// actual `Mail`s. pub struct Iter { - input: Peekable>>, - state: State, + input: Peekable>>, + state: State, } #[derive(Eq, PartialEq, Copy, Clone, Debug)] enum State { - Begin, - Header, - Body, + Begin, + Header, + Body, } impl Iter { - /// Create a new `Iterator` from the given input. - #[inline] - pub fn new(input: R) -> Self { - Iter { - input: super::lines(input).peekable(), - state: State::Begin, - } - } + /// Create a new `Iterator` from the given input. + #[inline] + pub fn new(input: R) -> Self { + Iter { + input: super::lines(input).peekable(), + state: State::Begin, + } + } } impl Iterator for Iter { - type Item = io::Result; - - fn next(&mut self) -> Option { - macro_rules! eof { - ($body:expr) => ( - if let Some(value) = $body { - value - } - else { - if self.state == State::Body { - self.state = State::Begin; - return Some(Ok(Entry::End)); - } - - return None; - } - ); - } - - macro_rules! try { - ($body:expr) => ( - match $body { - Ok(value) => - value, - - Err(err) => - return Some(Err(err.into())) - } - ); - } - - macro_rules! utf8 { - ($body:expr) => ( - match $body { - Ok(value) => - value, - - Err(_) => - return Some(Err(io::Error::new(io::ErrorKind::InvalidData, "stream did not contain valid UTF-8"))) - } - ); - } - - loop { - let (offset, line) = try!(eof!(self.input.next())); - - match self.state { - State::Begin => { - // Parse the beginning and return any errors. - let value = try!(entry::Begin::new(utf8!(String::from_utf8(line)))); - self.state = State::Header; - - return Some(Ok(Entry::Begin(offset, value))); - } - - State::Header => { - // If the line is empty the header section is over. - if line.is_empty() { - self.state = State::Body; - continue; - } - - // There's an escaped line after the beginning. - if line[0] == b'>' { - continue; - } - - let mut line = line; - - // Read lines until there are no folded headers. - loop { - let consumed; - - if let Ok((_, ref current)) = *eof!(self.input.peek()) { - match current.first() { - Some(&b' ') | Some(&b'\t') => { - line.extend_from_slice(current); - consumed = true; - } - - _ => break - } - } - else { - break; - } - - if consumed { - self.input.next(); - } - } - - // Parse the header and return any errors. - return Some(Ok(Entry::Header(try!(entry::Header::new(line))))); - } - - State::Body => { - // If the line is empty there's a newline in the content or a new - // mail is beginning. - if line.is_empty() { - if let Ok((_, ref current)) = *eof!(self.input.peek()) { - // Try to parse the beginning, if it parses it's a new mail. - if entry::Begin::ranges(current).is_ok() { - self.state = State::Begin; - return Some(Ok(Entry::End)); - } - } - } - - return Some(Ok(Entry::Body(line))); - } - } - } - } + type Item = io::Result; + + fn next(&mut self) -> Option { + macro_rules! eof { + ($body:expr) => { + if let Some(value) = $body { + value + } else { + if self.state == State::Body { + self.state = State::Begin; + return Some(Ok(Entry::End)); + } + + return None; + } + }; + } + + macro_rules! utf8 { + ($body:expr) => { + match $body { + Ok(value) => value, + + Err(_) => { + return Some(Err(io::Error::new( + io::ErrorKind::InvalidData, + "stream did not contain valid UTF-8", + ))) + } + } + }; + } + + loop { + let (offset, line) = eof!(self.input.next()).ok()?; + + match self.state { + State::Begin => { + // Parse the beginning and return any errors. + let value = entry::Begin::new(utf8!(String::from_utf8(line))).ok()?; + self.state = State::Header; + + return Some(Ok(Entry::Begin(offset, value))); + } + + State::Header => { + // If the line is empty the header section is over. + if line.is_empty() { + self.state = State::Body; + continue; + } + + // There's an escaped line after the beginning. + if line[0] == b'>' { + continue; + } + + let mut line = line; + + // Read lines until there are no folded headers. + loop { + let consumed; + + if let Ok((_, ref current)) = *eof!(self.input.peek()) { + match current.first() { + Some(&b' ') | Some(&b'\t') => { + line.extend_from_slice(current); + consumed = true; + } + + _ => break, + } + } else { + break; + } + + if consumed { + self.input.next(); + } + } + + // Parse the header and return any errors. + return Some(Ok(Entry::Header(entry::Header::new(line).ok()?))); + } + + State::Body => { + // If the line is empty there's a newline in the content or a new + // mail is beginning. + if line.is_empty() { + if let Ok((_, ref current)) = *eof!(self.input.peek()) { + // Try to parse the beginning, if it parses it's a new mail. + if entry::Begin::ranges(current).is_ok() { + self.state = State::Begin; + return Some(Ok(Entry::End)); + } + } + } + + return Some(Ok(Entry::Body(line))); + } + } + } + } } #[cfg(test)] mod test { - use std::io::Cursor; - use super::*; - use super::super::Entry; - - #[test] - fn simple() { - let mut iter = Iter::new(Cursor::new("From meh@schizofreni.co Wed Nov 17 14:35:53 2010\r\nSubject: I like trains\r\nFoo: bar\r\n baz\r\n\r\nHi!\r\n")); - - { - if let Entry::Begin(_, item) = iter.next().unwrap().unwrap() { - assert_eq!(item.address(), "meh@schizofreni.co"); - assert_eq!(item.timestamp(), "Wed Nov 17 14:35:53 2010"); - } - else { - assert!(false); - } - } - - { - if let Entry::Header(item) = iter.next().unwrap().unwrap() { - assert_eq!(&*item.key(), "Subject"); - assert_eq!(&*item.value(), "I like trains"); - } - else { - assert!(false); - } - } - - { - if let Entry::Header(item) = iter.next().unwrap().unwrap() { - assert_eq!(&*item.key(), "Foo"); - assert_eq!(&*item.value(), "bar baz"); - } - else { - assert!(false); - } - } - } + use super::super::Entry; + use super::*; + use std::io::Cursor; + + #[test] + fn simple() { + let mut iter = Iter::new(Cursor::new("From meh@schizofreni.co Wed Nov 17 14:35:53 2010\r\nSubject: I like trains\r\nFoo: bar\r\n baz\r\n\r\nHi!\r\n")); + + { + if let Entry::Begin(_, item) = iter.next().unwrap().unwrap() { + assert_eq!(item.address(), "meh@schizofreni.co"); + assert_eq!(item.timestamp(), "Wed Nov 17 14:35:53 2010"); + } else { + assert!(false); + } + } + + { + if let Entry::Header(item) = iter.next().unwrap().unwrap() { + assert_eq!(&*item.key(), "Subject"); + assert_eq!(&*item.value(), "I like trains"); + } else { + assert!(false); + } + } + + { + if let Entry::Header(item) = iter.next().unwrap().unwrap() { + assert_eq!(&*item.key(), "Foo"); + assert_eq!(&*item.value(), "bar baz"); + } else { + assert!(false); + } + } + } } diff --git a/src/stream/lines.rs b/src/stream/lines.rs index b996da3..65c03b1 100644 --- a/src/stream/lines.rs +++ b/src/stream/lines.rs @@ -20,43 +20,39 @@ use std::io::{self, BufRead}; pub struct Lines(R, u64); impl Lines { - /// Create a new `Iterator` from the given input. - #[inline] - pub fn new(input: R) -> Self { - Lines(input, 0) - } + /// Create a new `Iterator` from the given input. + #[inline] + pub fn new(input: R) -> Self { + Lines(input, 0) + } } impl Iterator for Lines { - type Item = io::Result<(u64, Vec)>; + type Item = io::Result<(u64, Vec)>; - #[inline] - fn next(&mut self) -> Option { - let mut buffer = Vec::new(); - let offset = self.1; + #[inline] + fn next(&mut self) -> Option { + let mut buffer = Vec::new(); + let offset = self.1; - match self.0.read_until(b'\n', &mut buffer) { - Ok(0) => { - None - } + match self.0.read_until(b'\n', &mut buffer) { + Ok(0) => None, - Ok(_) => { - self.1 += buffer.len() as u64; + Ok(_) => { + self.1 += buffer.len() as u64; - if buffer.last() == Some(&b'\n') { - buffer.pop(); + if buffer.last() == Some(&b'\n') { + buffer.pop(); - if buffer.last() == Some(&b'\r') { - buffer.pop(); - } - } + if buffer.last() == Some(&b'\r') { + buffer.pop(); + } + } - Some(Ok((offset, buffer))) - } + Some(Ok((offset, buffer))) + } - Err(e) => { - Some(Err(e)) - } - } - } + Err(e) => Some(Err(e)), + } + } } diff --git a/src/stream/mod.rs b/src/stream/mod.rs index dd7d69a..6b09201 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -21,16 +21,16 @@ pub use self::lines::Lines; mod iter; pub use self::iter::Iter; -use std::io::{Read, BufReader}; +use std::io::{BufReader, Read}; /// Create an `Iterator` over line based entries. #[inline] pub fn entries(input: R) -> Iter { - Iter::new(input) + Iter::new(input) } /// Create an `Iterator` over lines. #[inline] pub fn lines(input: R) -> Lines> { - Lines::new(BufReader::new(input)) + Lines::new(BufReader::new(input)) } diff --git a/src/util/address.rs b/src/util/address.rs index c83fde4..a5fcbc2 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -12,225 +12,252 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. +use crate::stream::entry::header; use std::fmt::{self, Write}; -use std::ops::Range; use std::io; -use nom::IResult; -use stream::entry::header; +use std::ops::Range; /// Represents an email address, composed of name, user and host. #[derive(Eq, PartialEq, Clone, Debug)] pub struct Address { - inner: header::Item, + inner: header::Item, - name: Option>, - user: Range, - host: Option>, + name: Option>, + user: Range, + host: Option>, } impl Address { - pub(crate) fn ranges>(string: T) -> io::Result<(Option>, Range, Option>)> { - let string = string.as_ref(); - - if let IResult::Done(_, (name, user, host)) = parser::parse(string.as_bytes()) { - let n = name.map(|n| n.as_ptr() as usize - string.as_ptr() as usize); - let u = user.as_ptr() as usize - string.as_ptr() as usize; - let h = host.map(|h| h.as_ptr() as usize - string.as_ptr() as usize); - - Ok(( - n.map(|n| Range { start: n, end: n + name.unwrap().len() }), - Range { start: u, end: u + user.len() }, - h.map(|h| Range { start: h, end: h + host.unwrap().len() }), - )) - } - else { - Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid address")) - } - } - - #[inline] - pub fn parse>(string: T) -> io::Result { - Address::new(header::item(string.as_ref())) - } - - #[inline] - pub(crate) fn new(string: header::Item) -> io::Result { - let (name, user, host) = try!(Address::ranges(&string)); - - Ok(Address { - inner: string, - - name: name, - user: user, - host: host, - }) - } - - /// The name if any. - /// - /// This is the first part of an address, which can be bare or quoted, for - /// example `"Someone Somewhere" ` or `Someone Somewhere - /// `. - #[inline] - pub fn name(&self) -> Option<&str> { - self.name.as_ref().map(|r| &self.inner[Range { start: r.start, end: r.end }]) - } - - /// The user. - /// - /// This is the only mandatory part of an address, for instance it can be - /// preceded by the `name` and followed by a `@` and the host, or be the - /// only part of an address. - #[inline] - pub fn user(&self) -> &str { - &self.inner[Range { start: self.user.start, end: self.user.end }] - } - - /// The host if any. - /// - /// This is the part after the `user` preceded by a `@`. - #[inline] - pub fn host(&self) -> Option<&str> { - self.host.as_ref().map(|r| &self.inner[Range { start: r.start, end: r.end }]) - } + pub(crate) fn ranges>( + string: T, + ) -> io::Result<(Option>, Range, Option>)> { + let string = string.as_ref(); + + if let Ok((_, (name, user, host))) = parser::parse(string.as_bytes()) { + let n = name.map(|n| n.as_ptr() as usize - string.as_ptr() as usize); + let u = user.as_ptr() as usize - string.as_ptr() as usize; + let h = host.map(|h| h.as_ptr() as usize - string.as_ptr() as usize); + + Ok(( + n.map(|n| Range { + start: n, + end: n + name.unwrap().len(), + }), + Range { + start: u, + end: u + user.len(), + }, + h.map(|h| Range { + start: h, + end: h + host.unwrap().len(), + }), + )) + } else { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid address", + )) + } + } + + #[inline] + pub fn parse>(string: T) -> io::Result { + Address::new(header::item(string.as_ref())) + } + + #[inline] + pub(crate) fn new(string: header::Item) -> io::Result { + let (name, user, host) = Address::ranges(&string)?; + + Ok(Address { + inner: string, + name, + user, + host, + }) + } + + /// The name if any. + /// + /// This is the first part of an address, which can be bare or quoted, for + /// example `"Someone Somewhere" ` or `Someone Somewhere + /// `. + #[inline] + pub fn name(&self) -> Option<&str> { + self.name.as_ref().map(|r| { + &self.inner[Range { + start: r.start, + end: r.end, + }] + }) + } + + /// The user. + /// + /// This is the only mandatory part of an address, for instance it can be + /// preceded by the `name` and followed by a `@` and the host, or be the + /// only part of an address. + #[inline] + pub fn user(&self) -> &str { + &self.inner[Range { + start: self.user.start, + end: self.user.end, + }] + } + + /// The host if any. + /// + /// This is the part after the `user` preceded by a `@`. + #[inline] + pub fn host(&self) -> Option<&str> { + self.host.as_ref().map(|r| { + &self.inner[Range { + start: r.start, + end: r.end, + }] + }) + } } impl fmt::Display for Address { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - if let Some(name) = self.name() { - try!(f.write_char('"')); - try!(f.write_str(name)); - try!(f.write_char('"')); - try!(f.write_char(' ')); - try!(f.write_char('<')); - } - - try!(f.write_str(&self.user())); - - if let Some(host) = self.host() { - try!(f.write_char('@')); - try!(f.write_str(host)); - } - - if self.name().is_some() { - try!(f.write_char('>')); - } - - Ok(()) - } + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + if let Some(name) = self.name() { + f.write_char('"')?; + f.write_str(name)?; + f.write_char('"')?; + f.write_char(' ')?; + f.write_char('<')?; + } + + f.write_str(self.user())?; + + if let Some(host) = self.host() { + f.write_char('@')?; + f.write_str(host)?; + } + + if self.name().is_some() { + f.write_char('>')?; + } + + Ok(()) + } } mod parser { - use std::str; - use util::parser::{WSP, is_ws}; - - named!(pub parse(&[u8]) -> (Option<&str>, &str, Option<&str>), - do_parse!( - take_while!(is_ws) >> - name: opt!(complete!(name)) >> - take_while!(is_ws) >> - addr: address >> - eof!() >> - - (unsafe { - let name = name.and_then(|s| { - let value = str::from_utf8_unchecked(s).trim(); - - if value.len() > 0 { - Some(value) - } - else { - None - } - }); - - let user = str::from_utf8_unchecked(addr.0); - let host = addr.1.map(|s| str::from_utf8_unchecked(s)); - - (name, user, host) - }))); - - named!(name(&[u8]) -> &[u8], - alt!(name_quoted | name_bare)); - - named!(name_quoted(&[u8]) -> &[u8], - do_parse!( - name: delimited!(char!('"'), is_not!("\""), char!('"')) >> - take_until!("<") >> - - (name))); - - named!(name_bare(&[u8]) -> &[u8], - do_parse!( - take_while!(is_ws) >> - name: take_until!("<") >> - - (name))); - - named!(address(&[u8]) -> (&[u8], Option<&[u8]>), - alt!(address_quoted | address_bare | address_user_only)); - - named!(address_quoted(&[u8]) -> (&[u8], Option<&[u8]>), - do_parse!( - char!('<') >> - user: take_until!("@") >> - char!('@') >> - host: take_until!(">") >> - char!('>') >> - - (user, Some(host)))); - - named!(address_bare(&[u8]) -> (&[u8], Option<&[u8]>), - do_parse!( - user: take_until!("@") >> - char!('@') >> - host: take_until_either_or_eof!(WSP) >> - - (user, Some(host)))); - - named!(address_user_only(&[u8]) -> (&[u8], Option<&[u8]>), - map!(take_until_either_or_eof!(WSP), - |user| (user, None))); + use crate::util::parser::is_ws; + use nom::branch::alt; + use nom::bytes::complete::{is_not, take_till, take_until, take_while}; + use nom::character::complete::char; + use nom::combinator::{complete, map, opt}; + use nom::sequence::{delimited, tuple}; + use nom::IResult; + use std::str; + + pub fn parse(input: &[u8]) -> IResult<&[u8], (Option<&str>, &str, Option<&str>)> { + let (input, (_, name, _, address)) = tuple(( + take_while(is_ws), + opt(complete(name)), + take_while(is_ws), + address, + ))(input)?; + + let name = name.and_then(|s| { + let value = str::from_utf8(s).unwrap().trim(); + + if !value.is_empty() { + Some(value) + } else { + None + } + }); + + let user = str::from_utf8(address.0).unwrap(); + let host = address.1.map(|s| str::from_utf8(s).unwrap()); + + Ok((input, (name, user, host))) + } + + pub fn name(input: &[u8]) -> IResult<&[u8], &[u8]> { + alt((name_quoted, name_bare))(input) + } + + pub fn name_quoted(input: &[u8]) -> IResult<&[u8], &[u8]> { + let (input, (name, _)) = tuple(( + delimited(char('"'), is_not("\""), char('"')), + take_until("<"), + ))(input)?; + Ok((input, (name))) + } + + pub fn name_bare(input: &[u8]) -> IResult<&[u8], &[u8]> { + take_until("<")(input) + } + + pub fn address(input: &[u8]) -> IResult<&[u8], (&[u8], Option<&[u8]>)> { + alt((address_quoted, address_bare, address_user_only))(input) + } + + pub fn address_quoted(input: &[u8]) -> IResult<&[u8], (&[u8], Option<&[u8]>)> { + let (input, (_, user, _, host, _)) = tuple(( + char('<'), + take_until("@"), + char('@'), + take_until(">"), + char('>'), + ))(input)?; + Ok((input, (user, Some(host)))) + } + + pub fn address_bare(input: &[u8]) -> IResult<&[u8], (&[u8], Option<&[u8]>)> { + let (input, (user, _, host)) = + tuple((take_until("@"), char('@'), take_till(is_ws)))(input)?; + Ok((input, (user, Some(host)))) + } + + pub fn address_user_only(input: &[u8]) -> IResult<&[u8], (&[u8], Option<&[u8]>)> { + map(take_till(is_ws), move |user: &[u8]| (user, None))(input) + } } #[cfg(test)] mod test { - use super::*; - - #[test] - fn parse_name_bare() { - let v = Address::parse(r#"culone "#).unwrap(); - assert_eq!(v.name(), Some("culone")); - assert_eq!(v.user(), "culo"); - assert_eq!(v.host(), Some("culetto")); - } - - #[test] - fn parse_name_quoted() { - let v = Address::parse(r#""culone" "#).unwrap(); - assert_eq!(v.name(), Some("culone")); - assert_eq!(v.user(), "culo"); - assert_eq!(v.host(), Some("culetto")); - } - - #[test] - fn parse_no_name() { - let v = Address::parse(r#"culo@culetto"#).unwrap(); - assert_eq!(v.user(), "culo"); - assert_eq!(v.host(), Some("culetto")); - } - - #[test] - fn parse_no_name_quoted() { - let v = Address::parse(r#""#).unwrap(); - assert!(v.name().is_none()); - assert_eq!(v.user(), "culo"); - assert_eq!(v.host(), Some("culetto")); - } - - #[test] - fn parse_just_name() { - let v = Address::parse(r#"culo"#).unwrap(); - assert_eq!(v.user(), "culo"); - } + use super::*; + + #[test] + fn parse_name_bare() { + let v = Address::parse(r#"culone "#).unwrap(); + assert_eq!(v.name(), Some("culone")); + assert_eq!(v.user(), "culo"); + assert_eq!(v.host(), Some("culetto")); + } + + #[test] + fn parse_name_quoted() { + let v = Address::parse(r#""culone" "#).unwrap(); + assert_eq!(v.name(), Some("culone")); + assert_eq!(v.user(), "culo"); + assert_eq!(v.host(), Some("culetto")); + } + + #[test] + fn parse_no_name() { + let v = Address::parse(r#"culo@culetto"#).unwrap(); + assert_eq!(v.user(), "culo"); + assert_eq!(v.host(), Some("culetto")); + } + + #[test] + fn parse_no_name_quoted() { + let v = Address::parse(r#""#).unwrap(); + assert!(v.name().is_none()); + assert_eq!(v.user(), "culo"); + assert_eq!(v.host(), Some("culetto")); + } + + #[test] + fn parse_just_name() { + let v = Address::parse(r#"culo"#).unwrap(); + assert_eq!(v.user(), "culo"); + } } diff --git a/src/util/parser.rs b/src/util/parser.rs index 39b668a..8ce4faf 100644 --- a/src/util/parser.rs +++ b/src/util/parser.rs @@ -12,176 +12,318 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. -pub const WSP: &'static [u8] = b" \t"; - -const NONE: u8 = 0b000; +const NONE: u8 = 0b000; const PRINT: u8 = 0b001; const COLON: u8 = 0b010; const SPACE: u8 = 0b100; // Ugly table of DOOM, gotta run and gun. static ASCII: [u8; 256] = [ - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, SPACE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - SPACE, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, - PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, - PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, - PRINT, PRINT, PRINT | COLON, PRINT, PRINT, PRINT, PRINT, PRINT, - PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, - PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, - PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, - PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, - PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, - PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, - PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, - PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, PRINT, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, - NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + SPACE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + SPACE, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT | COLON, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + PRINT, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, + NONE, ]; #[inline(always)] pub fn is_ws(ch: u8) -> bool { - unsafe { ASCII.get_unchecked(ch as usize) & SPACE != 0 } + ASCII[ch as usize] & SPACE != 0 } #[inline(always)] pub fn is_printable(ch: u8) -> bool { - unsafe { ASCII.get_unchecked(ch as usize) & PRINT != 0 } + ASCII[ch as usize] & PRINT != 0 } #[inline(always)] pub fn is_printable_or_ws(ch: u8) -> bool { - unsafe { ASCII.get_unchecked(ch as usize) & (PRINT | SPACE) != 0 } + ASCII[ch as usize] & (PRINT | SPACE) != 0 } #[inline(always)] pub fn is_printable_no_colon(ch: u8) -> bool { - unsafe { ASCII.get_unchecked(ch as usize) & (PRINT | COLON) == PRINT } -} - -macro_rules! take_until_either_or_eof { - ($i:expr, $inp:expr) => ({ - #[inline(always)] - fn as_bytes(b: &T) -> &[u8] { - b.as_bytes() - } - - let expected = $inp; - let bytes = as_bytes(&expected); - take_until_either_bytes_or_eof!($i, bytes) - }); -} - -macro_rules! take_until_either_bytes_or_eof { - ($i:expr, $bytes:expr) => ({ - let res: $crate::nom::IResult<_,_> = if 1 > $i.len() { - $crate::nom::IResult::Incomplete($crate::nom::Needed::Size(1)) - } - else { - let mut index = 0; - let mut parsed = false; - - for idx in 0..$i.len() { - if idx + 1 > $i.len() { - index = idx; - break; - } - for &t in $bytes.iter() { - if $i[idx] == t { - parsed = true; - index = idx; - break; - } - } - if parsed { break; } - } - - if parsed { - $crate::nom::IResult::Done(&$i[index..], &$i[0..index]) - } - else { - $crate::nom::IResult::Done(b"", &$i[..]) - } - }; - - res - }); -} - -macro_rules! take_while_n { - ($input:expr, $n:expr, $submac:ident!( $($args:tt)* )) => ({ - let count = $n; - - if $input.len() < count { - return $crate::nom::IResult::Incomplete($crate::nom::Needed::Size(count)); - } - - match $input.iter().take(count).position(|c| !$submac!(*c, $($args)*)) { - Some(n) => { - let res:$crate::nom::IResult<_,_> = if n == count { - $crate::nom::IResult::Done(&$input[n..], &$input[..n]) - } - else { - $crate::nom::IResult::Error($crate::nom::ErrorKind::Tag) - }; - - res - }, - - None => { - $crate::nom::IResult::Done(&$input[($input).len()..], $input) - } - } - }); - - ($input:expr, $n:expr, $f:expr) => ( - take_while_n!($input, $n, call!($f)); - ); + ASCII[ch as usize] & (PRINT | COLON) == PRINT } #[cfg(test)] mod test { - use super::*; + use super::*; - #[test] - fn ws() { - assert!(is_ws(b' ')); - assert!(!is_ws(b'a')); - } + #[test] + fn ws() { + assert!(is_ws(b' ')); + assert!(!is_ws(b'a')); + } - #[test] - fn printable() { - assert!(is_printable(b'a')); - assert!(!is_printable(b' ')); - } + #[test] + fn printable() { + assert!(is_printable(b'a')); + assert!(!is_printable(b' ')); + } - #[test] - fn printable_or_ws() { - assert!(is_printable_or_ws(b'a')); - assert!(is_printable_or_ws(b' ')); - assert!(is_printable_or_ws(b'\t')); - } + #[test] + fn printable_or_ws() { + assert!(is_printable_or_ws(b'a')); + assert!(is_printable_or_ws(b' ')); + assert!(is_printable_or_ws(b'\t')); + } - #[test] - fn printable_no_colon() { - assert!(is_printable_no_colon(b'a')); - assert!(!is_printable_no_colon(b':')); - assert!(!is_printable_no_colon(b' ')); - } + #[test] + fn printable_no_colon() { + assert!(is_printable_no_colon(b'a')); + assert!(!is_printable_no_colon(b':')); + assert!(!is_printable_no_colon(b' ')); + } }