From c17bb24f0dd32fc789bf014434da0d50b0fd69af Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Tue, 30 Jul 2019 21:33:15 +0300 Subject: [PATCH] melib: refactor attachments and attachment_types interfaces --- melib/src/email.rs | 6 +- melib/src/email/attachment_types.rs | 77 +++++++--- melib/src/email/attachments.rs | 188 ++++++++---------------- ui/src/components/mail/view.rs | 4 +- ui/src/components/mail/view/envelope.rs | 2 +- 5 files changed, 121 insertions(+), 156 deletions(-) diff --git a/melib/src/email.rs b/melib/src/email.rs index b7b8690d7..29671724b 100644 --- a/melib/src/email.rs +++ b/melib/src/email.rs @@ -472,7 +472,7 @@ impl Envelope { && cst.eq_ignore_ascii_case(b"mixed") => { let mut builder = AttachmentBuilder::new(body); - builder.set_content_type(value); + builder.set_content_type_from_bytes(value); let b = builder.build(); let subs = b.attachments(); @@ -586,9 +586,9 @@ impl Envelope { continue; } if name.eq_ignore_ascii_case(b"content-transfer-encoding") { - builder.set_content_transfer_encoding(value); + builder.set_content_transfer_encoding(ContentTransferEncoding::from(value)); } else if name.eq_ignore_ascii_case(b"content-type") { - builder.set_content_type(value); + builder.set_content_type_from_bytes(value); } } builder.build() diff --git a/melib/src/email/attachment_types.rs b/melib/src/email/attachment_types.rs index 6cba27ea9..6e2802530 100644 --- a/melib/src/email/attachment_types.rs +++ b/melib/src/email/attachment_types.rs @@ -1,30 +1,28 @@ +/* + * meli + * + * Copyright 2017-2019 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 crate::email::attachments::Attachment; use crate::email::parser::BytesExt; 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, @@ -100,6 +98,22 @@ impl Display for MultipartType { } } +impl From<&[u8]> for MultipartType { + fn from(val: &[u8]) -> MultipartType { + if val.eq_ignore_ascii_case(b"mixed") { + MultipartType::Mixed + } else if val.eq_ignore_ascii_case(b"alternative") { + MultipartType::Alternative + } else if val.eq_ignore_ascii_case(b"digest") { + MultipartType::Digest + } else if val.eq_ignore_ascii_case(b"signed") { + MultipartType::Signed + } else { + Default::default() + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum ContentType { Text { @@ -107,7 +121,7 @@ pub enum ContentType { charset: Charset, }, Multipart { - boundary: SliceBuild, + boundary: Vec, kind: MultipartType, subattachments: Vec, }, @@ -216,3 +230,20 @@ impl Display for ContentTransferEncoding { } } } +impl From<&[u8]> for ContentTransferEncoding { + fn from(val: &[u8]) -> ContentTransferEncoding { + if val.eq_ignore_ascii_case(b"base64") { + ContentTransferEncoding::Base64 + } else if val.eq_ignore_ascii_case(b"7bit") { + ContentTransferEncoding::_7Bit + } else if val.eq_ignore_ascii_case(b"8bit") { + ContentTransferEncoding::_8Bit + } else if val.eq_ignore_ascii_case(b"quoted-printable") { + ContentTransferEncoding::QuotedPrintable + } else { + ContentTransferEncoding::Other { + tag: val.to_ascii_lowercase(), + } + } + } +} diff --git a/melib/src/email/attachments.rs b/melib/src/email/attachments.rs index 3621f1039..1a5352e06 100644 --- a/melib/src/email/attachments.rs +++ b/melib/src/email/attachments.rs @@ -21,20 +21,12 @@ use crate::email::parser; use crate::email::parser::BytesExt; use crate::email::EnvelopeWrapper; +use core::fmt; +use core::str; use data_encoding::BASE64_MIME; -use std::fmt; -use std::str; pub use crate::email::attachment_types::*; -/* - * - * Data - * Text { content: Vec } - * Multipart - */ -// TODO: Add example. -// #[derive(Default, PartialEq)] pub struct AttachmentBuilder { content_type: ContentType, @@ -42,30 +34,6 @@ pub struct AttachmentBuilder { 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(), - { - let mut text = Vec::with_capacity(4096); - self.get_text_recursive(&mut text); - std::str::from_utf8(&text).map(std::string::ToString::to_string).unwrap_or_else(|e| format!("Unicode error {}", e)) - } - ) - } -} - impl AttachmentBuilder { pub fn new(content: &[u8]) -> Self { AttachmentBuilder { @@ -74,11 +42,26 @@ impl AttachmentBuilder { raw: content.to_vec(), } } - pub fn content_type(&mut self) -> &ContentType { + + pub fn set_content_type(&mut self, val: ContentType) -> &mut Self { + self.content_type = val; + self + } + + pub fn content_type(&self) -> &ContentType { &self.content_type } - pub fn set_content_type(&mut self, value: &[u8]) -> &Self { + pub fn set_content_transfer_encoding(&mut self, val: ContentTransferEncoding) -> &mut Self { + self.content_transfer_encoding = val; + self + } + + pub fn content_transfer_encoding(&self) -> &ContentTransferEncoding { + &self.content_transfer_encoding + } + + pub fn set_content_type_from_bytes(&mut self, value: &[u8]) -> &mut Self { match parser::content_type(value).to_full_result() { Ok((ct, cst, params)) => { if ct.eq_ignore_ascii_case(b"multipart") { @@ -90,26 +73,12 @@ impl AttachmentBuilder { } } 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)); - // Invalid mail or wrong assumption? - // assert!(!subattachments.is_empty()); + let boundary = boundary.unwrap().to_vec(); + let subattachments = Self::subattachments(&self.raw, &boundary); + 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() - }, + kind: MultipartType::from(cst), subattachments, }; } else if ct.eq_ignore_ascii_case(b"text") { @@ -164,50 +133,7 @@ impl AttachmentBuilder { } self } - pub fn set_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, @@ -234,16 +160,14 @@ impl AttachmentBuilder { } }; - 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(); + builder.raw = body.ltrim().into(); for (name, value) in headers { if name.eq_ignore_ascii_case(b"content-type") { - builder.set_content_type(value); + builder.set_content_type_from_bytes(value); } else if name.eq_ignore_ascii_case(b"content-transfer-encoding") { - builder.set_content_transfer_encoding(value); + builder.set_content_transfer_encoding(ContentTransferEncoding::from( + value, + )); } } vec.push(builder.build()); @@ -263,10 +187,34 @@ impl AttachmentBuilder { } } +/// Immutable attachment type. +#[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(), + { + let mut text = Vec::with_capacity(4096); + self.get_text_recursive(&mut text); + std::str::from_utf8(&text).map(std::string::ToString::to_string).unwrap_or_else(|e| format!("Unicode error {}", e)) + } + ) + } +} + 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()) { + ContentType::MessageRfc822 => match EnvelopeWrapper::new(self.raw().to_vec()) { Ok(wrapper) => write!( f, "message/rfc822: {} - {} - {}", @@ -307,7 +255,7 @@ impl Attachment { } } - pub fn bytes(&self) -> &[u8] { + pub fn raw(&self) -> &[u8] { &self.raw } fn get_text_recursive(&self, text: &mut Vec) { @@ -442,27 +390,13 @@ impl Attachment { } 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""); + // FIXME + let builder = AttachmentBuilder::new(b"message/rfc822 cannot be displayed"); 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) -> () + 'a>; @@ -514,23 +448,23 @@ fn decode_helper<'a>(a: &'a Attachment, filter: &mut Option>) -> Vec< }; let bytes = match a.content_transfer_encoding { - ContentTransferEncoding::Base64 => match BASE64_MIME.decode(a.bytes()) { + ContentTransferEncoding::Base64 => match BASE64_MIME.decode(a.raw()) { Ok(v) => v, - _ => a.bytes().to_vec(), + _ => a.raw().to_vec(), }, - ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_bytes(a.bytes()) + ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_bytes(a.raw()) .to_full_result() .unwrap(), ContentTransferEncoding::_7Bit | ContentTransferEncoding::_8Bit - | ContentTransferEncoding::Other { .. } => a.bytes().to_vec(), + | ContentTransferEncoding::Other { .. } => a.raw().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() + a.raw().to_vec() } } else { bytes.to_vec() diff --git a/ui/src/components/mail/view.rs b/ui/src/components/mail/view.rs index f27c53c9b..621ca246f 100644 --- a/ui/src/components/mail/view.rs +++ b/ui/src/components/mail/view.rs @@ -200,7 +200,7 @@ impl MailView { } t } - ViewMode::Raw => String::from_utf8_lossy(body.bytes()).into_owned(), + ViewMode::Raw => String::from_utf8_lossy(body.raw()).into_owned(), ViewMode::Url => { let mut t = body_text.to_string(); for (lidx, l) in finder.links(&body.text()).enumerate() { @@ -720,7 +720,7 @@ impl Component for MailView { match u.content_type() { ContentType::MessageRfc822 => { self.mode = ViewMode::Subview; - match EnvelopeWrapper::new(u.bytes().to_vec()) { + match EnvelopeWrapper::new(u.raw().to_vec()) { Ok(wrapper) => { self.subview = Some(Box::new(EnvelopeView::new( wrapper, diff --git a/ui/src/components/mail/view/envelope.rs b/ui/src/components/mail/view/envelope.rs index 040c2f10e..532f2756d 100644 --- a/ui/src/components/mail/view/envelope.rs +++ b/ui/src/components/mail/view/envelope.rs @@ -150,7 +150,7 @@ impl EnvelopeView { } t } - ViewMode::Raw => String::from_utf8_lossy(body.bytes()).into_owned(), + ViewMode::Raw => String::from_utf8_lossy(body.raw()).into_owned(), ViewMode::Url => { let mut t = body_text.to_string(); for (lidx, l) in finder.links(&body.text()).enumerate() {