summaryrefslogtreecommitdiffstats
path: root/melib/src/email/attachments.rs
diff options
context:
space:
mode:
Diffstat (limited to 'melib/src/email/attachments.rs')
-rw-r--r--melib/src/email/attachments.rs529
1 files changed, 529 insertions, 0 deletions
diff --git a/melib/src/email/attachments.rs b/melib/src/email/attachments.rs
new file mode 100644
index 00000000..bfbedd3a
--- /dev/null
+++ b/melib/src/email/attachments.rs
@@ -0,0 +1,529 @@
+/*
+ * 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 crate::email::parser;
+use crate::email::parser::BytesExt;
+use crate::email::EnvelopeWrapper;
+use data_encoding::BASE64_MIME;
+use std::fmt;
+use std::str;
+
+pub use crate::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 if cst.eq_ignore_ascii_case(b"signed") {
+ MultipartType::Signed
+ } 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 if ct.eq_ignore_ascii_case(b"application")
+ && cst.eq_ignore_ascii_case(b"pgp-signature")
+ {
+ self.content_type = ContentType::PGPSignature;
+ } 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) => {
+ debug!("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(_) => {
+ debug!("error in parsing attachment");
+ debug!("\n-------------------------------");
+ debug!("{}\n", ::std::string::String::from_utf8_lossy(a));
+ debug!("-------------------------------\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 => {
+ debug!(
+ "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::PGPSignature => write!(f, "pgp signature {}", self.mime_type()),
+ 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 {
+ ref kind,
+ ref subattachments,
+ ..
+ } => match kind {
+ MultipartType::Alternative => {
+ for a in subattachments {
+ if let ContentType::Text {
+ kind: Text::Plain, ..
+ } = a.content_type
+ {
+ a.get_text_recursive(text);
+ break;
+ }
+ }
+ }
+ _ => {
+ for a in subattachments {
+ 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,
+ ContentType::Text {
+ kind: Text::Plain, ..
+ } => false,
+ ContentType::Multipart {
+ kind: MultipartType::Alternative,
+ ref subattachments,
+ ..
+ } => {
+ for a in subattachments.iter() {
+ if let ContentType::Text {
+ kind: Text::Plain, ..
+ } = a.content_type
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ ContentType::Multipart {
+ kind: MultipartType::Signed,
+ ref subattachments,
+ ..
+ } => subattachments
+ .iter()
+ .find(|s| s.content_type != ContentType::PGPSignature)
+ .map(|s| s.is_html())
+ .unwrap_or(false),
+ ContentType::Multipart {
+ ref subattachments, ..
+ } => subattachments
+ .iter()
+ .fold(true, |acc, a| match &a.content_type {
+ ContentType::Text {
+ kind: Text::Plain, ..
+ } => false,
+ ContentType::Text {
+ kind: Text::Html, ..
+ } => acc,
+ ContentType::Multipart {
+ kind: MultipartType::Alternative,
+ ..
+ } => a.is_html(),
+ _ => acc,
+ }),
+ _ => 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()
+
+ /*
+ debug!("raw is\n{:?}", str::from_utf8(raw).unwrap());
+ let e = match Envelope::from_bytes(raw) {
+ Some(e) => e,
+ None => {
+ debug!("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<'a> = Box<FnMut(&'a Attachment, &mut Vec<u8>) -> () + 'a>;
+
+fn decode_rec_helper<'a>(a: &'a Attachment, filter: &mut Option<Filter<'a>>) -> Vec<u8> {
+ let ret = match a.content_type {
+ ContentType::Unsupported { .. } => Vec::new(),
+ ContentType::Text { .. } => decode_helper(a, filter),
+ ContentType::PGPSignature => a.content_type.to_string().into_bytes(),
+ ContentType::MessageRfc822 => decode_rec(&decode_rfc822(&a.raw), None),
+ ContentType::Multipart {
+ ref kind,
+ ref subattachments,
+ ..
+ } => match kind {
+ MultipartType::Alternative => {
+ for a in subattachments {
+ if let ContentType::Text {
+ kind: Text::Plain, ..
+ } = a.content_type
+ {
+ return decode_helper(a, filter);
+ }
+ }
+ decode_helper(a, filter)
+ }
+ _ => {
+ let mut vec = Vec::new();
+ for a in subattachments {
+ vec.extend(decode_rec_helper(a, filter));
+ }
+ vec
+ }
+ },
+ };
+ ret
+}
+
+pub fn decode_rec<'a>(a: &'a Attachment, mut filter: Option<Filter<'a>>) -> Vec<u8> {
+ decode_rec_helper(a, &mut filter)
+}
+
+fn decode_helper<'a>(a: &'a Attachment, filter: &mut Option<Filter<'a>>) -> 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>(a: &'a Attachment, mut filter: Option<Filter<'a>>) -> Vec<u8> {
+ decode_helper(a, &mut filter)
+}