Refactor attachment content types

embed
Manos Pitsidianakis 2018-08-17 13:21:24 +03:00
parent 710920c67b
commit c8611926fa
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
4 changed files with 367 additions and 319 deletions

View File

@ -1,7 +1,32 @@
use mailbox::email::parser::BytesExt;
use mailbox::email::attachments::Attachment;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::str;
// TODO: rename.
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Default)]
pub struct SliceBuild {
offset: usize,
end: usize,
}
impl SliceBuild {
pub fn new(offset: usize, length: usize) -> Self {
SliceBuild {
offset,
end: offset + length,
}
}
//fn length(&self) -> usize {
// self.end - self.offset + 1
//}
pub fn get<'a>(&self, slice:&'a [u8]) -> &'a [u8] {
&slice[self.offset..self.end]
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum Charset {
Ascii,
@ -57,7 +82,12 @@ pub enum MultipartType {
Mixed,
Alternative,
Digest,
Unsupported { tag: Vec<u8> },
}
impl Default for MultipartType {
fn default() -> Self {
MultipartType::Mixed
}
}
impl Display for MultipartType {
@ -66,23 +96,21 @@ impl Display for MultipartType {
MultipartType::Mixed => write!(f, "multipart/mixed"),
MultipartType::Alternative => write!(f, "multipart/alternative"),
MultipartType::Digest => write!(f, "multipart/digest"),
MultipartType::Unsupported { tag: ref t } => {
write!(f, "multipart/{}", String::from_utf8_lossy(t))
}
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ContentType {
Text { charset: Charset },
Multipart { boundary: Vec<u8> },
Text { kind: Text, charset: Charset },
Multipart { boundary: SliceBuild, kind: MultipartType, subattachments: Vec<Attachment>},
Unsupported { tag: Vec<u8> },
}
impl Default for ContentType {
fn default() -> Self {
ContentType::Text {
kind: Text::Plain,
charset: Charset::UTF8,
}
}
@ -90,9 +118,9 @@ impl Default for ContentType {
impl Display for ContentType {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match *self {
ContentType::Text { .. } => write!(f, "text"),
ContentType::Multipart { .. } => write!(f, "multipart"),
match self {
ContentType::Text { kind: t, .. } => t.fmt(f),
ContentType::Multipart { kind: k, .. } => k.fmt(f),
ContentType::Unsupported { tag: ref t } => write!(f, "{}", String::from_utf8_lossy(t)),
}
}
@ -106,18 +134,8 @@ impl ContentType {
false
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ContentSubType {
Plain,
Html,
Other { tag: Vec<u8> },
}
impl ContentSubType {
pub fn is_html(&self) -> bool {
if let ContentSubType::Html = self {
pub fn is_text_html(&self) -> bool {
if let ContentType::Text { kind: Text::Html, .. } = self {
true
} else {
false
@ -125,12 +143,31 @@ impl ContentSubType {
}
}
impl Display for ContentSubType {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Text {
Plain,
Html,
Rfc822,
Other { tag: Vec<u8> },
}
impl Text {
pub fn is_html(&self) -> bool {
if let Text::Html = self {
true
} else {
false
}
}
}
impl Display for Text {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match *self {
ContentSubType::Plain => write!(f, "plain"),
ContentSubType::Html => write!(f, "html"),
ContentSubType::Other { tag: ref t } => write!(f, "{}", String::from_utf8_lossy(t)),
Text::Plain => write!(f, "text/plain"),
Text::Html => write!(f, "text/html"),
Text::Rfc822 => write!(f, "text/rfc822"),
Text::Other { tag: ref t } => write!(f, "text/{}", String::from_utf8_lossy(t)),
}
}
}
@ -142,3 +179,9 @@ pub enum ContentTransferEncoding {
QuotedPrintable,
Other { tag: Vec<u8> },
}
impl Default for ContentTransferEncoding {
fn default() -> Self {
ContentTransferEncoding::_7Bit
}
}

View File

@ -25,37 +25,6 @@ use std::str;
pub use mailbox::email::attachment_types::*;
#[derive(Clone, Serialize, Deserialize)]
pub enum AttachmentType {
Data {
tag: Vec<u8>,
},
Text {
content: Vec<u8>,
},
Multipart {
of_type: MultipartType,
subattachments: Vec<Attachment>,
},
}
impl fmt::Debug for AttachmentType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AttachmentType::Data { .. } => write!(f, "AttachmentType::Data {{ .. }}"),
AttachmentType::Text { .. } => write!(f, "AttachmentType::Text {{ .. }}"),
AttachmentType::Multipart {
of_type,
subattachments,
} => write!(
f,
"AttachmentType::Multipart {{ of_type: {:?},\nsubattachments: {:?} }}",
of_type, subattachments
),
}
}
}
/*
*
* Data
@ -64,8 +33,9 @@ impl fmt::Debug for AttachmentType {
*/
// TODO: Add example.
//
#[derive(Default)]
pub struct AttachmentBuilder {
content_type: (ContentType, ContentSubType),
content_type: ContentType,
content_transfer_encoding: ContentTransferEncoding,
raw: Vec<u8>,
@ -73,37 +43,25 @@ pub struct AttachmentBuilder {
#[derive(Clone, Serialize, Deserialize)]
pub struct Attachment {
content_type: (ContentType, ContentSubType),
content_type: ContentType,
content_transfer_encoding: ContentTransferEncoding,
raw: Vec<u8>,
attachment_type: AttachmentType,
}
impl fmt::Debug for Attachment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Attachment {{\n content_type: {:?},\n content_transfer_encoding: {:?},\n raw: Vec of {} bytes\n attachment_type: {:?}\n }}",
write!(f, "Attachment {{\n content_type: {:?},\n content_transfer_encoding: {:?},\n raw: Vec of {} bytes\n }}",
self.content_type,
self.content_transfer_encoding,
self.raw.len(),
self.attachment_type)
}
}
impl fmt::Display for AttachmentType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AttachmentType::Data { tag: ref t } => write!(f, "{}", String::from_utf8_lossy(t)),
AttachmentType::Text { content: ref c } => write!(f, "{}", String::from_utf8_lossy(c)),
AttachmentType::Multipart { of_type: ref t, .. } => write!(f, "{}", t),
}
self.raw.len())
}
}
impl AttachmentBuilder {
pub fn new(content: &[u8]) -> Self {
AttachmentBuilder {
content_type: (Default::default(), ContentSubType::Plain),
content_type: Default::default(),
content_transfer_encoding: ContentTransferEncoding::_7Bit,
raw: content.to_vec(),
}
@ -114,42 +72,63 @@ impl AttachmentBuilder {
let mut boundary = None;
for (n, v) in params {
if n.eq_ignore_ascii_case(b"boundary") {
let mut vec: Vec<u8> = Vec::with_capacity(v.len() + 4);
vec.extend_from_slice(b"--");
vec.extend(v);
vec.extend_from_slice(b"--");
boundary = Some(vec);
boundary = Some(v);
break;
}
}
assert!(boundary.is_some());
self.content_type.0 = ContentType::Multipart {
boundary: boundary.unwrap(),
let _boundary = boundary.unwrap();
let offset = _boundary.as_ptr() as usize - value.as_ptr() as usize;
let boundary = SliceBuild::new(offset, _boundary.len());
let subattachments = Self::subattachments(&self.raw, boundary.get(&value));
eprintln!("boundary is {} and suba is {:?}", str::from_utf8(_boundary).unwrap(), subattachments);
self.content_type = ContentType::Multipart {
boundary,
kind: if cst.eq_ignore_ascii_case(b"mixed") {
MultipartType::Mixed
} else if cst.eq_ignore_ascii_case(b"alternative") {
MultipartType::Alternative
} else if cst.eq_ignore_ascii_case(b"digest") {
MultipartType::Digest
} else {
Default::default()
},
subattachments
};
self.content_type.1 = ContentSubType::Other { tag: cst.into() };
} else if ct.eq_ignore_ascii_case(b"text") {
self.content_type.0 = Default::default();
self.content_type = ContentType::Text {
kind: Text::Plain,
charset: Charset::UTF8,
};
for (n, v) in params {
if n.eq_ignore_ascii_case(b"charset") {
self.content_type.0 = ContentType::Text {
charset: Charset::from(v),
};
break;
match self.content_type {
ContentType::Text { charset: ref mut c, .. } => {
*c = Charset::from(v);
},
_ => {},
}
break;
}
}
if cst.eq_ignore_ascii_case(b"html") {
self.content_type.1 = ContentSubType::Html;
match self.content_type {
ContentType::Text { kind: ref mut k, .. } => {
*k = Text::Html;
},
_ => {},
}
} else if !cst.eq_ignore_ascii_case(b"plain") {
self.content_type.1 = ContentSubType::Other {
tag: cst.to_ascii_lowercase(),
};
match self.content_type {
ContentType::Text { kind: ref mut k, .. } => {
*k = Text::Other { tag: cst.into() };
},
_ => {},
}
}
} else {
self.content_type.0 = ContentType::Unsupported {
tag: ct.to_ascii_lowercase(),
};
self.content_type.1 = ContentSubType::Other {
tag: cst.to_ascii_lowercase(),
self.content_type = ContentType::Unsupported {
tag: ct.into(),
};
},
Err(v) => {
@ -176,8 +155,8 @@ impl AttachmentBuilder {
}
fn decode(&self) -> Vec<u8> {
// TODO merge this and standalone decode() function
let charset = match self.content_type.0 {
ContentType::Text { charset: c } => c,
let charset = match self.content_type {
ContentType::Text { charset: c, .. } => c,
_ => Default::default(),
};
@ -194,73 +173,54 @@ impl AttachmentBuilder {
| ContentTransferEncoding::Other { .. } => self.raw.to_vec(),
};
let decoded_result = parser::decode_charset(&bytes, charset);
decoded_result
.as_ref()
.map(|v| v.as_bytes())
.unwrap_or_else(|_| &self.raw)
.to_vec()
if let Ok(b) = parser::decode_charset(&bytes, charset) {
b.into_bytes()
} else {
self.raw.to_vec()
}
}
pub fn build(self) -> Attachment {
let attachment_type = match self.content_type.0 {
ContentType::Text { .. } => AttachmentType::Text {
content: self.decode(),
},
ContentType::Multipart { boundary: ref b } => {
let multipart_type = match self.content_type.1 {
ContentSubType::Other { ref tag } => match &tag[..] {
b"mixed" | b"Mixed" | b"MIXED" => MultipartType::Mixed,
b"alternative" | b"Alternative" | b"ALTERNATIVE" => {
MultipartType::Alternative
}
b"digest" | b"Digest" | b"DIGEST" => MultipartType::Digest,
_ => MultipartType::Unsupported { tag: tag.clone() },
},
_ => panic!(),
};
AttachmentType::Multipart {
of_type: multipart_type,
subattachments: Self::subattachments(&self.raw, b),
}
}
ContentType::Unsupported { ref tag } => AttachmentType::Data { tag: tag.clone() },
};
Attachment {
content_type: self.content_type,
content_transfer_encoding: self.content_transfer_encoding,
raw: self.raw,
attachment_type: attachment_type,
}
}
pub fn subattachments(raw: &[u8], boundary: &[u8]) -> Vec<Attachment> {
let boundary_length = boundary.len();
match parser::attachments(raw, &boundary[0..boundary_length - 2], boundary).to_full_result()
eprintln!("subattachments boundary {}", str::from_utf8(boundary).unwrap());
match parser::attachments(raw, boundary).to_full_result()
{
Ok(attachments) => {
let mut vec = Vec::with_capacity(attachments.len());
for a in attachments {
let (headers, body) = match parser::attachment(a).to_full_result() {
Ok(v) => v,
Err(_) => {
eprintln!("error in parsing attachment");
eprintln!("\n-------------------------------");
eprintln!("{}\n", ::std::string::String::from_utf8_lossy(a));
eprintln!("-------------------------------\n");
let mut builder = AttachmentBuilder::default();
let body_slice = {
let (headers, body) = match parser::attachment(&a).to_full_result() {
Ok(v) => v,
Err(_) => {
eprintln!("error in parsing attachment");
eprintln!("\n-------------------------------");
eprintln!("{}\n", ::std::string::String::from_utf8_lossy(a));
eprintln!("-------------------------------\n");
continue;
continue;
}
};
for (name, value) in headers {
if name.eq_ignore_ascii_case(b"content-type") {
builder.content_type(value);
} else if name.eq_ignore_ascii_case(b"content-transfer-encoding") {
builder.content_transfer_encoding(value);
}
}
let offset = body.as_ptr() as usize - a.as_ptr() as usize;
SliceBuild::new(offset, body.len())
};
let mut builder = AttachmentBuilder::new(body);
for (name, value) in headers {
if name.eq_ignore_ascii_case(b"content-type") {
builder.content_type(value);
} else if name.eq_ignore_ascii_case(b"content-transfer-encoding") {
builder.content_transfer_encoding(value);
}
}
builder.raw = body_slice.get(a).into();
vec.push(builder.build());
}
eprintln!("subattachments {:?}", vec);
vec
}
a => {
@ -278,16 +238,17 @@ impl AttachmentBuilder {
impl fmt::Display for Attachment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.attachment_type {
AttachmentType::Data { .. } => {
match self.content_type {
ContentType::Unsupported { .. } => {
write!(f, "Data attachment of type {}", self.mime_type())
}
AttachmentType::Text { .. } => {
ContentType::Text { .. } => {
write!(f, "Text attachment of type {}", self.mime_type())
}
AttachmentType::Multipart {
of_type: ref multipart_type,
ContentType::Multipart {
kind: ref multipart_type,
subattachments: ref sub_att_vec,
..
} => if *multipart_type == MultipartType::Alternative {
write!(
f,
@ -312,29 +273,31 @@ impl Attachment {
&self.raw
}
fn get_text_recursive(&self, text: &mut Vec<u8>) {
match self.attachment_type {
AttachmentType::Data { .. } => {
//text.push_str(&format!("Data attachment of type {}", self.mime_type()));
}
AttachmentType::Text { .. } => {
match self.content_type {
ContentType::Text { .. } => {
text.extend(decode(self, None));
}
AttachmentType::Multipart {
of_type: ref multipart_type,
ContentType::Multipart {
kind: ref multipart_type,
subattachments: ref sub_att_vec,
} => if *multipart_type == MultipartType::Alternative {
for a in sub_att_vec {
if a.content_type.1 == ContentSubType::Plain {
a.get_text_recursive(text);
break;
..
} => match *multipart_type {
MultipartType::Alternative => {
for a in sub_att_vec {
if let ContentType::Text { kind: Text::Plain, .. } = a.content_type {
a.get_text_recursive(text);
break;
}
}
}
} else {
for a in sub_att_vec {
a.get_text_recursive(text);
text.extend_from_slice(b"\n\n");
}
},
},
MultipartType::Mixed | MultipartType::Digest => {
for a in sub_att_vec {
a.get_text_recursive(text);
text.extend_from_slice(b"\n\n");
}
},
}
_ => {},
}
}
pub fn text(&self) -> String {
@ -346,16 +309,16 @@ impl Attachment {
self.attachments().iter().map(|a| a.text()).collect()
}
pub fn mime_type(&self) -> String {
format!("{}/{}", self.content_type.0, self.content_type.1).to_string()
format!("{}", self.content_type).to_string()
}
pub fn attachments(&self) -> Vec<Attachment> {
let mut ret = Vec::new();
fn count_recursive(att: &Attachment, ret: &mut Vec<Attachment>) {
match att.attachment_type {
AttachmentType::Data { .. } | AttachmentType::Text { .. } => ret.push(att.clone()),
AttachmentType::Multipart {
of_type: _,
match att.content_type {
ContentType::Unsupported { .. } | ContentType::Text { .. } => ret.push(att.clone()),
ContentType::Multipart {
subattachments: ref sub_att_vec,
..
} => {
ret.push(att.clone());
// TODO: Fix this, wrong count
@ -372,17 +335,17 @@ impl Attachment {
pub fn count_attachments(&self) -> usize {
self.attachments().len()
}
pub fn attachment_type(&self) -> &AttachmentType {
&self.attachment_type
}
pub fn content_type(&self) -> &(ContentType, ContentSubType) {
pub fn content_type(&self) -> &ContentType {
&self.content_type
}
pub fn content_transfer_encoding(&self) -> &ContentTransferEncoding {
&self.content_transfer_encoding
}
pub fn is_html(&self) -> bool {
self.content_type().0.is_text() && self.content_type().1.is_html()
match self.content_type {
ContentType::Text { kind: Text::Html, .. } => true,
_ => false,
}
}
}
@ -395,15 +358,16 @@ fn decode_rec_helper(a: &Attachment, filter: &Option<Box<Fn(&Attachment) -> Vec<
if let Some(filter) = filter {
return filter(a);
}
match a.attachment_type {
AttachmentType::Data { .. } => Vec::new(),
AttachmentType::Text { .. } => decode_helper(a, filter),
AttachmentType::Multipart {
of_type: ref multipart_type,
match a.content_type {
ContentType::Unsupported { .. } => Vec::new(),
ContentType::Text { .. } => decode_helper(a, filter),
ContentType::Multipart {
kind: ref multipart_type,
subattachments: ref sub_att_vec,
..
} => if *multipart_type == MultipartType::Alternative {
for a in sub_att_vec {
if a.content_type.1 == ContentSubType::Plain {
if let ContentType::Text { kind: Text::Plain, .. } = a.content_type {
return decode_helper(a, filter);
}
}
@ -426,8 +390,8 @@ fn decode_helper(a: &Attachment, filter: &Option<Box<Fn(&Attachment) -> Vec<u8>>
return filter(a);
}
let charset = match a.content_type.0 {
ContentType::Text { charset: c } => c,
let charset = match a.content_type {
ContentType::Text { charset: c, .. } => c,
_ => Default::default(),
};
@ -444,13 +408,12 @@ fn decode_helper(a: &Attachment, filter: &Option<Box<Fn(&Attachment) -> Vec<u8>>
| ContentTransferEncoding::Other { .. } => a.bytes().to_vec(),
};
if a.content_type().0.is_text() {
let decoded_result = parser::decode_charset(&bytes, charset);
decoded_result
.as_ref()
.map(|v| v.as_bytes())
.unwrap_or_else(|_| a.bytes())
.to_vec()
if a.content_type.is_text() {
if let Ok(v) = parser::decode_charset(&bytes, charset) {
v.into_bytes()
} else {
a.bytes().to_vec()
}
} else {
bytes.to_vec()
}

View File

@ -22,8 +22,11 @@ use super::*;
use chrono;
use data_encoding::BASE64_MIME;
use encoding::{DecoderTrap, Encoding};
use nom::{is_hex_digit, le_u8, };
use nom::FindSubstring;
use nom::Slice;
use nom::{is_hex_digit, le_u8};
use nom::{ErrorKind, IResult, Needed};
use encoding::all::*;
use std;
@ -47,7 +50,7 @@ pub trait BytesExt {
impl BytesExt for [u8] {
fn rtrim(&self) -> &Self {
if let Some(last) = self.iter().rposition(|b| !is_whitespace!(*b)) {
&self[..last+1]
&self[..last + 1]
} else {
&[]
}
@ -192,7 +195,6 @@ fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
}
let tag_end_idx = tag_end_idx.unwrap();
if input[2 + tag_end_idx] != b'?' {
return IResult::Error(error_code!(ErrorKind::Custom(43)));
}
@ -225,17 +227,14 @@ fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
_ => return IResult::Error(error_code!(ErrorKind::Custom(46))),
};
let charset = Charset::from(&input[2..tag_end_idx]);
if let Charset::UTF8 = charset {
IResult::Done(&input[encoded_end_idx + 2..], s)
} else {
match decode_charset(&s, charset) {
Ok(v) => {
IResult::Done(&input[encoded_end_idx + 2..], v.into_bytes())
},
_ => IResult::Error(error_code!(ErrorKind::Custom(43))),
Ok(v) => IResult::Done(&input[encoded_end_idx + 2..], v.into_bytes()),
_ => IResult::Error(error_code!(ErrorKind::Custom(43))),
}
}
}
@ -269,7 +268,10 @@ fn quoted_printable_soft_break(input: &[u8]) -> IResult<&[u8], &[u8]> {
}
}
named!(qp_underscore_header<u8>, do_parse!(tag!(b"_") >> ({ 0x20 })));
named!(
qp_underscore_header<u8>,
do_parse!(tag!(b"_") >> ({ 0x20 }))
);
// With MIME, headers in quoted printable format can contain underscores that represent spaces.
// In non-header context, an underscore is just a plain underscore.
@ -289,7 +291,6 @@ named!(
))
);
fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
if input.is_empty() || input.len() < 3 {
IResult::Incomplete(Needed::Size(1))
@ -440,7 +441,6 @@ fn group(input: &[u8]) -> IResult<&[u8], Address> {
named!(address<Address>, ws!(alt_complete!(mailbox | group)));
named!(pub rfc2822address_list<Vec<Address>>, ws!( separated_list!(is_a!(","), address)));
named!(pub address_list<String>, ws!(do_parse!(
@ -519,34 +519,35 @@ fn message_id_peek(input: &[u8]) -> IResult<&[u8], &[u8]> {
named!(pub references<Vec<&[u8]>>, separated_list!(complete!(is_a!(" \n\t\r")), message_id_peek));
named_args!(pub attachments<'a>(boundary: &'a [u8], boundary_end: &'a [u8]) < Vec<&'this_is_probably_unique_i_hope_please [u8]> >,
named_args!(pub attachments<'a>(boundary: &'a [u8]) < Vec<&'this_is_probably_unique_i_hope_please [u8]> >,
alt_complete!(do_parse!(
take_until!(boundary) >>
take_until_and_consume!(boundary) >>
vecs: many0!(complete!(do_parse!(
tag!(boundary) >>
tag!("\n") >>
body: take_until1!(boundary) >>
body: take_until_and_consume1!(boundary) >>
( { body } )))) >>
tag!(boundary_end) >>
tag!(b"--") >>
tag!("\n") >>
take_while!(call!(|_| { true })) >>
( {
vecs
} )
) | do_parse!(
take_until!(boundary_end) >>
tag!(boundary_end) >>
take_until_and_consume!(&b"--"[..]) >>
take_until_and_consume!(boundary) >>
( { Vec::<&[u8]>::new() } ))
));
named!(
content_type_parameter<(&[u8], &[u8])>,
do_parse!(
tag!(";") >> name: terminated!(ws!(take_until!("=")), tag!("="))
tag!(";")
>> name: terminated!(ws!(take_until!("=")), tag!("="))
>> value:
ws!(alt_complete!(
delimited!(tag!("\""), take_until!("\""), tag!("\"")) | is_not!(";")
)) >> ({ (name, value) })
))
>> ({ (name, value) })
)
);
@ -565,32 +566,34 @@ named!(pub space, eat_separator!(&b" \t\r\n"[..]));
named!(
encoded_word_list<Vec<u8>>,
ws!(do_parse!(
list: separated_nonempty_list!(call!(space), encoded_word) >> ({
let list_len = list.iter().fold(0, |mut acc, x| {
acc += x.len();
acc
});
let bytes = list
.iter()
.fold(Vec::with_capacity(list_len), |mut acc, x| {
acc.append(&mut x.clone());
list: separated_nonempty_list!(call!(space), encoded_word)
>> ({
let list_len = list.iter().fold(0, |mut acc, x| {
acc += x.len();
acc
});
bytes
})
let bytes = list
.iter()
.fold(Vec::with_capacity(list_len), |mut acc, x| {
acc.append(&mut x.clone());
acc
});
bytes
})
))
);
named!(
ascii_token<Vec<u8>>,
do_parse!(
word: alt_complete!(
terminated!(take_until1!(" =?"), peek!(preceded!(tag!(b" "), call!(encoded_word))))
| take_while!(call!(|_| true))
terminated!(
take_until1!(" =?"),
peek!(preceded!(tag!(b" "), call!(encoded_word)))
) | take_while!(call!(|_| true))
) >> ({ word.into() })
)
);
pub fn phrase(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
if input.is_empty() {
return IResult::Done(&[], Vec::with_capacity(0));
@ -635,10 +638,18 @@ pub fn phrase(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
ptr += 1;
}
if ptr >= input.len() {
acc.extend(ascii_token(&input[ascii_s..ascii_e]).to_full_result().unwrap());
acc.extend(
ascii_token(&input[ascii_s..ascii_e])
.to_full_result()
.unwrap(),
);
break;
}
acc.extend(ascii_token(&input[ascii_s..ascii_e]).to_full_result().unwrap());
acc.extend(
ascii_token(&input[ascii_s..ascii_e])
.to_full_result()
.unwrap(),
);
if ptr != ascii_e {
acc.push(b' ');
}
@ -649,108 +660,139 @@ pub fn phrase(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
#[cfg(test)]
mod tests {
use super::*;
use super::*;
#[test]
fn test_subject() {
let words = b"=?iso-8859-7?B?W215Y291cnNlcy5udHVhLmdyIC0gyvXs4fTp6t4g6uHpIMri4e306ere?=
#[test]
fn test_subject() {
let words = b"=?iso-8859-7?B?W215Y291cnNlcy5udHVhLmdyIC0gyvXs4fTp6t4g6uHpIMri4e306ere?=
=?iso-8859-7?B?INb18+nq3l0gzd3hIMHt4erv3+358+c6IMzF0c/TIMHQz9TFy8XTzMHU?=
=?iso-8859-7?B?2c0gwiDUzC4gysHNLiDFzsXUwdPH0yAyMDE3LTE4OiDTx8zFydnTxw==?=";
assert!("[mycourses.ntua.gr - Κυματική και Κβαντική Φυσική] Νέα Ανακοίνωση: ΜΕΡΟΣ ΑΠΟΤΕΛΕΣΜΑΤΩΝ Β ΤΜ. ΚΑΝ. ΕΞΕΤΑΣΗΣ 2017-18: ΣΗΜΕΙΩΣΗ" == std::str::from_utf8(&phrase(words.trim()).to_full_result().unwrap()).unwrap());
let words = b"=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?= =?UTF-8?Q?=CF=84=CE=B7_=CE=B5=CE=BE=CE=B5=CF=84?= =?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?=";
assert!("Πρόσθετη εξεταστική" == std::str::from_utf8(&phrase(words.trim()).to_full_result().unwrap()).unwrap());
let words = b"[Advcomparch] =?utf-8?b?zqPPhc68z4DOtc+BzrnPhs6/z4HOrCDPg861IGZs?=\n\t=?utf-8?b?dXNoIM67z4zOs8+JIG1pc3ByZWRpY3Rpb24gzrrOsc+Ezqwgz4TOt869?=\n\t=?utf-8?b?IM61zrrPhM6tzrvOtc+Dzrcgc3RvcmU=?=";
assert!("[Advcomparch] Συμπεριφορά σε flush λόγω misprediction κατά την εκτέλεση store" == std::str::from_utf8(&phrase(words.trim()).to_full_result().unwrap()).unwrap());
let words = b"Re: [Advcomparch] =?utf-8?b?zqPPhc68z4DOtc+BzrnPhs6/z4HOrCDPg861IGZs?=
assert!("[mycourses.ntua.gr - Κυματική και Κβαντική Φυσική] Νέα Ανακοίνωση: ΜΕΡΟΣ ΑΠΟΤΕΛΕΣΜΑΤΩΝ Β ΤΜ. ΚΑΝ. ΕΞΕΤΑΣΗΣ 2017-18: ΣΗΜΕΙΩΣΗ" == std::str::from_utf8(&phrase(words.trim()).to_full_result().unwrap()).unwrap());
let words = b"=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?= =?UTF-8?Q?=CF=84=CE=B7_=CE=B5=CE=BE=CE=B5=CF=84?= =?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?=";
assert!(
"Πρόσθετη εξεταστική"
== std::str::from_utf8(&phrase(words.trim()).to_full_result().unwrap()).unwrap()
);
let words = b"[Advcomparch] =?utf-8?b?zqPPhc68z4DOtc+BzrnPhs6/z4HOrCDPg861IGZs?=\n\t=?utf-8?b?dXNoIM67z4zOs8+JIG1pc3ByZWRpY3Rpb24gzrrOsc+Ezqwgz4TOt869?=\n\t=?utf-8?b?IM61zrrPhM6tzrvOtc+Dzrcgc3RvcmU=?=";
assert!(
"[Advcomparch] Συμπεριφορά σε flush λόγω misprediction κατά την εκτέλεση store"
== std::str::from_utf8(&phrase(words.trim()).to_full_result().unwrap()).unwrap()
);
let words = b"Re: [Advcomparch] =?utf-8?b?zqPPhc68z4DOtc+BzrnPhs6/z4HOrCDPg861IGZs?=
=?utf-8?b?dXNoIM67z4zOs8+JIG1pc3ByZWRpY3Rpb24gzrrOsc+Ezqwgz4TOt869?=
=?utf-8?b?IM61zrrPhM6tzrvOtc+Dzrcgc3RvcmU=?=";
assert!("Re: [Advcomparch] Συμπεριφορά σε flush λόγω misprediction κατά την εκτέλεση store" == std::str::from_utf8(&phrase(words.trim()).to_full_result().unwrap()).unwrap());
let words = b"sdf";
assert!("sdf" == std::str::from_utf8(&phrase(words).to_full_result().unwrap()).unwrap());
let words = b"=?iso-8859-7?b?U2VnIGZhdWx0IPP05+0g5er03evl8+cg9O/1?= =?iso-8859-7?q?_example_ru_n_=5Fsniper?=";
assert!("Seg fault στην εκτέλεση του example ru n _sniper" == std::str::from_utf8(&phrase(words).to_full_result().unwrap()).unwrap());
let words = b"Re: [Advcomparch]
assert!(
"Re: [Advcomparch] Συμπεριφορά σε flush λόγω misprediction κατά την εκτέλεση store"
== std::str::from_utf8(&phrase(words.trim()).to_full_result().unwrap()).unwrap()
);
let words = b"sdf";
assert!("sdf" == std::str::from_utf8(&phrase(words).to_full_result().unwrap()).unwrap());
let words = b"=?iso-8859-7?b?U2VnIGZhdWx0IPP05+0g5er03evl8+cg9O/1?= =?iso-8859-7?q?_example_ru_n_=5Fsniper?=";
assert!(
"Seg fault στην εκτέλεση του example ru n _sniper"
== std::str::from_utf8(&phrase(words).to_full_result().unwrap()).unwrap()
);
let words = b"Re: [Advcomparch]
=?iso-8859-7?b?U2VnIGZhdWx0IPP05+0g5er03evl8+cg9O/1?=
=?iso-8859-7?q?_example_ru_n_=5Fsniper?=";
//TODO Fix this
assert!("Re: [Advcomparch] Seg fault στην εκτέλεση του example run_sniper" == std::str::from_utf8(&phrase(words).to_full_result().unwrap()).unwrap());
}
//TODO Fix this
assert!(
"Re: [Advcomparch] Seg fault στην εκτέλεση του example run_sniper"
== std::str::from_utf8(&phrase(words).to_full_result().unwrap()).unwrap()
);
}
#[test]
fn test_address() {
let s = b"Obit Oppidum <user@domain>,
#[test]
fn test_address() {
let s = b"Obit Oppidum <user@domain>,
list <list@domain.tld>, list2 <list2@domain.tld>,
Bobit Boppidum <user@otherdomain.com>, Cobit Coppidum <user2@otherdomain.com>";
println!("{:?}", rfc2822address_list(s).unwrap());
}
println!("{:?}", rfc2822address_list(s).unwrap());
}
#[test]
fn test_date() {
let s = b"Thu, 31 Aug 2017 13:43:37 +0000 (UTC)";
let _s = b"Thu, 31 Aug 2017 13:43:37 +0000";
let __s = b"=?utf-8?q?Thu=2C_31_Aug_2017_13=3A43=3A37_-0000?=";
eprintln!("{:?}, {:?}", date(s), date(_s));
eprintln!("{:?}", date(__s));
assert_eq!(date(s).unwrap(), date(_s).unwrap());
assert_eq!(date(_s).unwrap(), date(__s).unwrap());
}
#[test]
fn test_attachments() {
use std::io::Read;
let mut buffer: Vec<u8> = Vec::new();
let _ = std::fs::File::open("test/attachment_test")
#[test]
fn test_date() {
let s = b"Thu, 31 Aug 2017 13:43:37 +0000 (UTC)";
let _s = b"Thu, 31 Aug 2017 13:43:37 +0000";
let __s = b"=?utf-8?q?Thu=2C_31_Aug_2017_13=3A43=3A37_-0000?=";
eprintln!("{:?}, {:?}", date(s), date(_s));
eprintln!("{:?}", date(__s));
assert_eq!(date(s).unwrap(), date(_s).unwrap());
assert_eq!(date(_s).unwrap(), date(__s).unwrap());
}
#[test]
fn test_attachments() {
use std::io::Read;
let mut buffer: Vec<u8> = Vec::new();
let _ = std::fs::File::open(
"./Trash/cur/1490727777_3.37623.post,U=51565,FMD5=7e33429f656f1e6e9d79b29c3f82c57e:2,S",
)
.unwrap()
.read_to_end(&mut buffer);
let boundary = b"--b1_4382d284f0c601a737bb32aaeda53160--";
let boundary_len = boundary.len();
let (_, body) = match mail(&buffer).to_full_result() {
Ok(v) => v,
Err(_) => panic!(),
};
let attachments = attachments(body, &boundary[0..boundary_len - 2], boundary)
.to_full_result()
.unwrap();
assert_eq!(attachments.len(), 4);
}
#[test]
fn test_addresses() {
{
let s = b"=?iso-8859-7?B?0/Th/fHv8iDM4ev03ebv8g==?= <maltezos@central.ntua.gr>";
let r = mailbox(s).unwrap().1;
match r {
Address::Mailbox(ref m) => assert!("Σταύρος Μαλτέζος" == std::str::from_utf8(&m.display_name.display_bytes(&m.raw)).unwrap() && std::str::from_utf8(&m.address_spec.display_bytes(&m.raw)).unwrap() == "maltezos@central.ntua.gr"),
_ => assert!(false),
let boundary = b"bb11dc565bd54a03b36cc119a6266ebd";
//let boundary = b"b1_4382d284f0c601a737bb32aaeda53160";
let boundary_len = boundary.len();
let (_, body) = match mail(&buffer).to_full_result() {
Ok(v) => v,
Err(_) => panic!(),
};
let attachments = attachments(body, boundary).to_full_result().unwrap();
assert_eq!(attachments.len(), 2);
let v: Vec<&str> = attachments
.iter()
.map(|v| std::str::from_utf8(v).unwrap())
.collect();
println!("attachments {:?}", v);
}
#[test]
fn test_addresses() {
{
let s = b"=?iso-8859-7?B?0/Th/fHv8iDM4ev03ebv8g==?= <maltezos@central.ntua.gr>";
let r = mailbox(s).unwrap().1;
match r {
Address::Mailbox(ref m) => assert!(
"Σταύρος Μαλτέζος"
== std::str::from_utf8(&m.display_name.display_bytes(&m.raw)).unwrap()
&& std::str::from_utf8(&m.address_spec.display_bytes(&m.raw)).unwrap()
== "maltezos@central.ntua.gr"
),
_ => assert!(false),
}
}
{
let s = b"user@domain";
let r = mailbox(s).unwrap().1;
match r {
Address::Mailbox(ref m) => assert!(
m.display_name.display_bytes(&m.raw) == b""
&& m.address_spec.display_bytes(&m.raw) == b"user@domain"
),
_ => assert!(false),
}
}
{
let s = b"Name <user@domain>";
let r = display_addr(s).unwrap().1;
match r {
Address::Mailbox(ref m) => assert!(
b"Name" == m.display_name.display_bytes(&m.raw)
&& b"user@domain" == m.address_spec.display_bytes(&m.raw)
),
_ => {}
}
}
{
let s = b"user@domain";
let r = mailbox(s).unwrap().1;
match r {
Address::Mailbox(ref m) => assert!(
b"" == m.display_name.display_bytes(&m.raw)
&& b"user@domain" == m.address_spec.display_bytes(&m.raw)
),
_ => {}
}
}
}
{
let s = b"user@domain";
let r = mailbox(s).unwrap().1;
match r {
Address::Mailbox(ref m) => assert!(m.display_name.display_bytes(&m.raw) == b""
&& m.address_spec.display_bytes(&m.raw) == b"user@domain"),
_ => assert!(false),
}
}
{
let s = b"Name <user@domain>";
let r = display_addr(s).unwrap().1;
match r {
Address::Mailbox(ref m) => assert!(b"Name" == m.display_name.display_bytes(&m.raw) &&
b"user@domain" == m.address_spec.display_bytes(&m.raw)),
_ => {},
}
}
{
let s = b"user@domain";
let r = mailbox(s).unwrap().1;
match r {
Address::Mailbox(ref m) => assert!(b"" == m.display_name.display_bytes(&m.raw) &&
b"user@domain" ==m.address_spec.display_bytes(&m.raw)),
_ => {},
}
}
}
}

View File

@ -90,7 +90,7 @@ impl MailView {
/// Returns the string to be displayed in the Viewer
fn attachment_to_text(&self, body: Attachment) -> String {
let finder = LinkFinder::new();
let body_text = if body.content_type().0.is_text() && body.content_type().1.is_html() {
let body_text = if body.content_type().is_text_html() {
let mut s =
String::from("Text piped through `w3m`. Press `v` to open in web browser. \n\n");
s.extend(
@ -390,7 +390,7 @@ impl Component for MailView {
let envelope: &Envelope = &mailbox.collection[envelope_idx];
let op = context.accounts[self.coordinates.0].backend.operation(envelope.hash());
if let Some(u) = envelope.body(op).attachments().get(lidx) {
match u.content_type().0 {
match u.content_type() {
ContentType::Text { .. } => {
self.mode = ViewMode::Attachment(lidx);
self.dirty = true;