/* * 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 . */ 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 } * Multipart */ // TODO: Add example. // #[derive(Default, PartialEq)] pub struct AttachmentBuilder { content_type: ContentType, content_transfer_encoding: ContentTransferEncoding, raw: Vec, } #[derive(Clone, Serialize, Deserialize, PartialEq)] pub struct Attachment { content_type: ContentType, content_transfer_encoding: ContentTransferEncoding, raw: Vec, } 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 = 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 { // 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 { 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, ) -> Self { Attachment { content_type, content_transfer_encoding, raw, } } pub fn bytes(&self) -> &[u8] { &self.raw } fn get_text_recursive(&self, text: &mut Vec) { 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 { 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 { let mut ret = Vec::new(); fn count_recursive(att: &Attachment, ret: &mut Vec) { 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 decode_rec_helper(a: &Attachment, filter: &Option) -> Vec { 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) -> Vec { decode_rec_helper(a, &filter) } fn decode_helper(a: &Attachment, filter: &Option) -> Vec { 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) -> Vec { decode_helper(a, &filter) }