Refactor attachment content types
parent
710920c67b
commit
c8611926fa
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue