🐝 I really like where this mua is(was?) headed, but it seems as though there has not been much activity recently.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

479 lines
16 KiB

/*
* meli - attachments module
*
* Copyright 2017 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use data_encoding::BASE64_MIME;
use mailbox::email::parser;
use mailbox::email::parser::BytesExt;
use mailbox::email::EnvelopeWrapper;
use std::fmt;
use std::str;
pub use mailbox::email::attachment_types::*;
/*
*
* Data
* Text { content: Vec<u8> }
* Multipart
*/
// TODO: Add example.
//
#[derive(Default, PartialEq)]
pub struct AttachmentBuilder {
content_type: ContentType,
content_transfer_encoding: ContentTransferEncoding,
raw: Vec<u8>,
}
#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub struct Attachment {
content_type: ContentType,
content_transfer_encoding: ContentTransferEncoding,
raw: Vec<u8>,
}
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, body:\n{}\n }}",
self.content_type,
self.content_transfer_encoding,
self.raw.len(),
str::from_utf8(&self.raw).unwrap())
}
}
impl AttachmentBuilder {
pub fn new(content: &[u8]) -> Self {
AttachmentBuilder {
content_type: Default::default(),
content_transfer_encoding: ContentTransferEncoding::_7Bit,
raw: content.to_vec(),
}
}
pub fn content_type(&mut self, value: &[u8]) -> &Self {
match parser::content_type(value).to_full_result() {
Ok((ct, cst, params)) => {
if ct.eq_ignore_ascii_case(b"multipart") {
let mut boundary = None;
for (n, v) in params {
if n.eq_ignore_ascii_case(b"boundary") {
boundary = Some(v);
break;
}
}
assert!(boundary.is_some());
let _boundary = boundary.unwrap();
let offset =
(_boundary.as_ptr() as usize).wrapping_sub(value.as_ptr() as usize);
let boundary = SliceBuild::new(offset, _boundary.len());
let subattachments = Self::subattachments(&self.raw, boundary.get(&value));
assert!(!subattachments.is_empty());
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,
};
} else if ct.eq_ignore_ascii_case(b"text") {
self.content_type = ContentType::Text {
kind: Text::Plain,
charset: Charset::UTF8,
};
for (n, v) in params {
if n.eq_ignore_ascii_case(b"charset") {
if let ContentType::Text {
charset: ref mut c, ..
} = self.content_type
{
*c = Charset::from(v);
}
break;
}
}
if cst.eq_ignore_ascii_case(b"html") {
if let ContentType::Text {
kind: ref mut k, ..
} = self.content_type
{
*k = Text::Html;
}
} else if !cst.eq_ignore_ascii_case(b"plain") {
if let ContentType::Text {
kind: ref mut k, ..
} = self.content_type
{
*k = Text::Other { tag: cst.into() };
}
}
} else if ct.eq_ignore_ascii_case(b"message") && cst.eq_ignore_ascii_case(b"rfc822")
{
self.content_type = ContentType::MessageRfc822;
} else {
let mut tag: Vec<u8> = Vec::with_capacity(ct.len() + cst.len() + 1);
tag.extend(ct);
tag.push(b'/');
tag.extend(cst);
self.content_type = ContentType::Unsupported { tag };
}
}
Err(v) => {
eprintln!("parsing error in content_type: {:?} {:?}", value, v);
}
}
self
}
pub fn content_transfer_encoding(&mut self, value: &[u8]) -> &Self {
self.content_transfer_encoding = if value.eq_ignore_ascii_case(b"base64") {
ContentTransferEncoding::Base64
} else if value.eq_ignore_ascii_case(b"7bit") {
ContentTransferEncoding::_7Bit
} else if value.eq_ignore_ascii_case(b"8bit") {
ContentTransferEncoding::_8Bit
} else if value.eq_ignore_ascii_case(b"quoted-printable") {
ContentTransferEncoding::QuotedPrintable
} else {
ContentTransferEncoding::Other {
tag: value.to_ascii_lowercase(),
}
};
self
}
/*
fn decode(&self) -> Vec<u8> {
// TODO merge this and standalone decode() function
let charset = match self.content_type {
ContentType::Text { charset: c, .. } => c,
_ => Default::default(),
};
let bytes = match self.content_transfer_encoding {
ContentTransferEncoding::Base64 => match BASE64_MIME.decode(&self.raw) {
Ok(v) => v,
_ => self.raw.to_vec(),
},
ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_bytes(&self.raw)
.to_full_result()
.unwrap(),
ContentTransferEncoding::_7Bit
| ContentTransferEncoding::_8Bit
| ContentTransferEncoding::Other { .. } => 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 {
Attachment {
content_type: self.content_type,
content_transfer_encoding: self.content_transfer_encoding,
raw: self.raw,
}
}
pub fn subattachments(raw: &[u8], boundary: &[u8]) -> Vec<Attachment> {
match parser::attachments(raw, boundary).to_full_result() {
Ok(attachments) => {
let mut vec = Vec::with_capacity(attachments.len());
for a in attachments {
let mut builder = AttachmentBuilder::default();
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;
}
};
let body_slice = {
let offset = (body.as_ptr() as usize).wrapping_sub(a.as_ptr() as usize);
SliceBuild::new(offset, body.len())
};
builder.raw = body_slice.get(a).ltrim().into();
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);
}
}
vec.push(builder.build());
}
vec
}
a => {
eprintln!(
"error {:?}\n\traw: {:?}\n\tboundary: {:?}",
a,
str::from_utf8(raw).unwrap(),
boundary
);
Vec::new()
}
}
}
}
impl fmt::Display for Attachment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.content_type {
ContentType::MessageRfc822 => match EnvelopeWrapper::new(self.bytes().to_vec()) {
Ok(wrapper) => write!(
f,
"message/rfc822: {} - {} - {}",
wrapper.date(),
wrapper.field_from_to_string(),
wrapper.subject()
),
Err(e) => write!(f, "{}", e),
},
ContentType::Unsupported { .. } => {
write!(f, "Data attachment of type {}", self.mime_type())
}
ContentType::Text { .. } => write!(f, "Text attachment of type {}", self.mime_type()),
ContentType::Multipart {
subattachments: ref sub_att_vec,
..
} => write!(
f,
"{} attachment with {} subs",
self.mime_type(),
sub_att_vec.len()
),
}
}
}
impl Attachment {
pub fn new(
content_type: ContentType,
content_transfer_encoding: ContentTransferEncoding,
raw: Vec<u8>,
) -> Self {
Attachment {
content_type,
content_transfer_encoding,
raw,
}
}
pub fn bytes(&self) -> &[u8] {
&self.raw
}
fn get_text_recursive(&self, text: &mut Vec<u8>) {
match self.content_type {
ContentType::Text { .. } => {
text.extend(decode(self, None));
}
ContentType::Multipart {
kind: ref multipart_type,
subattachments: ref sub_att_vec,
..
} => 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;
}
}
}
MultipartType::Mixed | MultipartType::Digest => {
for a in sub_att_vec {
a.get_text_recursive(text);
}
}
},
_ => {}
}
}
pub fn text(&self) -> String {
let mut text = Vec::with_capacity(self.raw.len());
self.get_text_recursive(&mut text);
String::from_utf8_lossy(text.as_slice().trim()).into()
}
pub fn description(&self) -> Vec<String> {
self.attachments().iter().map(|a| a.text()).collect()
}
pub fn mime_type(&self) -> 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.content_type {
ContentType::Multipart {
subattachments: ref sub_att_vec,
..
} => {
ret.push(att.clone());
// FIXME: Wrong count
for a in sub_att_vec {
count_recursive(a, ret);
}
}
_ => ret.push(att.clone()),
}
}
count_recursive(&self, &mut ret);
ret
}
pub fn count_attachments(&self) -> usize {
self.attachments().len()
}
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 {
match self.content_type {
ContentType::Text {
kind: Text::Html, ..
} => true,
_ => false,
}
}
}
pub fn interpret_format_flowed(_t: &str) -> String {
//let mut n = String::with_capacity(t.len());
unimplemented!()
}
fn decode_rfc822(_raw: &[u8]) -> Attachment {
let builder = AttachmentBuilder::new(b"");
builder.build()
/*
eprintln!("raw is\n{:?}", str::from_utf8(raw).unwrap());
let e = match Envelope::from_bytes(raw) {
Some(e) => e,
None => {
eprintln!("error in parsing mail");
let error_msg = b"Mail cannot be shown because of errors.";
let mut builder = AttachmentBuilder::new(error_msg);
return builder.build();
}
};
e.body(None)
*/
}
type Filter = Box<Fn(&Attachment, &mut Vec<u8>) -> ()>;
fn decode_rec_helper(a: &Attachment, filter: &Option<Filter>) -> Vec<u8> {
let mut ret = match a.content_type {
ContentType::Unsupported { .. } => Vec::new(),
ContentType::Text { .. } => decode_helper(a, filter),
ContentType::MessageRfc822 => decode_rec(&decode_rfc822(&a.raw), None),
ContentType::Multipart {
kind: ref multipart_type,
subattachments: ref sub_att_vec,
..
} => {
if *multipart_type == MultipartType::Alternative {
for a in sub_att_vec {
if let ContentType::Text {
kind: Text::Plain, ..
} = a.content_type
{
return decode_helper(a, filter);
}
}
decode_helper(a, filter)
} else {
let mut vec = Vec::new();
for a in sub_att_vec {
vec.extend(decode_rec_helper(a, filter));
}
vec
}
}
};
if let Some(filter) = filter {
filter(a, &mut ret);
}
ret
}
pub fn decode_rec(a: &Attachment, filter: Option<Filter>) -> Vec<u8> {
decode_rec_helper(a, &filter)
}
fn decode_helper(a: &Attachment, filter: &Option<Filter>) -> Vec<u8> {
let charset = match a.content_type {
ContentType::Text { charset: c, .. } => c,
_ => Default::default(),
};
let bytes = match a.content_transfer_encoding {
ContentTransferEncoding::Base64 => match BASE64_MIME.decode(a.bytes()) {
Ok(v) => v,
_ => a.bytes().to_vec(),
},
ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_bytes(a.bytes())
.to_full_result()
.unwrap(),
ContentTransferEncoding::_7Bit
| ContentTransferEncoding::_8Bit
| ContentTransferEncoding::Other { .. } => a.bytes().to_vec(),
};
let mut ret = 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()
};
if let Some(filter) = filter {
filter(a, &mut ret);
}
ret
}
pub fn decode(a: &Attachment, filter: Option<Filter>) -> Vec<u8> {
decode_helper(a, &filter)
}