melib: refactor attachments and attachment_types interfaces

embed
Manos Pitsidianakis 2019-07-30 21:33:15 +03:00
parent 7592e66d6a
commit c17bb24f0d
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
5 changed files with 121 additions and 156 deletions

View File

@ -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()

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<u8>,
kind: MultipartType,
subattachments: Vec<Attachment>,
},
@ -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(),
}
}
}
}

View File

@ -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<u8> }
* Multipart
*/
// TODO: Add example.
//
#[derive(Default, PartialEq)]
pub struct AttachmentBuilder {
content_type: ContentType,
@ -42,30 +34,6 @@ pub struct AttachmentBuilder {
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(),
{
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<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,
@ -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<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(),
{
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<u8>) {
@ -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<FnMut(&'a Attachment, &mut Vec<u8>) -> () + 'a>;
@ -514,23 +448,23 @@ fn decode_helper<'a>(a: &'a Attachment, filter: &mut Option<Filter<'a>>) -> 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()

View File

@ -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,

View File

@ -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() {