melib: add a `body` field to Attachment

Attachment needs to know the range of bytes where the body part of the
attachment is located. The Attachment.raw field contains the entire
attachment, headers and body. The new Attachment.body fields contains a
`StrBuilder` which contains the offset and length of the body part inside
`raw`.
pull/234/head
Manos Pitsidianakis 5 years ago
parent 5a53020f3d
commit 9305e543cf
No known key found for this signature in database
GPG Key ID: 73627C2F690DF710

@ -274,16 +274,25 @@ impl Envelope {
} }
} else if name.eq_ignore_ascii_case(b"content-type") { } else if name.eq_ignore_ascii_case(b"content-type") {
match parser::content_type(value).to_full_result() { match parser::content_type(value).to_full_result() {
Ok((ct, cst, _)) Ok((ct, cst, ref params))
if ct.eq_ignore_ascii_case(b"multipart") if ct.eq_ignore_ascii_case(b"multipart")
&& cst.eq_ignore_ascii_case(b"mixed") => && cst.eq_ignore_ascii_case(b"mixed") =>
{ {
let mut builder = AttachmentBuilder::new(body); let mut builder = AttachmentBuilder::default();
builder.set_content_type_from_bytes(value); builder.set_content_type_from_bytes(value);
let b = builder.build(); let mut boundary = None;
let subs = b.attachments(); for (n, v) in params {
if n == b"boundary" {
self.has_attachments = subs.iter().any(|sub| !sub.is_text()); boundary = Some(v);
break;
}
}
if let Some(boundary) = boundary {
self.has_attachments =
Attachment::check_if_has_attachments_quick(body, boundary);
} else {
debug!("{:?} has no boundary field set in multipart/mixed content-type field.", &self);
}
} }
_ => {} _ => {}
} }
@ -373,31 +382,7 @@ impl Envelope {
.unwrap_or_else(|_| Vec::new()) .unwrap_or_else(|_| Vec::new())
} }
pub fn body_bytes(&self, bytes: &[u8]) -> Attachment { pub fn body_bytes(&self, bytes: &[u8]) -> Attachment {
if bytes.is_empty() { let builder = AttachmentBuilder::new(bytes);
let builder = AttachmentBuilder::new(bytes);
return builder.build();
}
let (headers, body) = match parser::mail(bytes).to_full_result() {
Ok(v) => v,
Err(_) => {
debug!("error in parsing mail\n");
let error_msg = b"Mail cannot be shown because of errors.";
let builder = AttachmentBuilder::new(error_msg);
return builder.build();
}
};
let mut builder = AttachmentBuilder::new(body);
for (name, value) in headers {
if value.len() == 1 && value.is_empty() {
continue;
}
if name.eq_ignore_ascii_case(b"content-transfer-encoding") {
builder.set_content_transfer_encoding(ContentTransferEncoding::from(value));
} else if name.eq_ignore_ascii_case(b"content-type") {
builder.set_content_type_from_bytes(value);
}
}
builder.build() builder.build()
} }
pub fn headers<'a>(&self, bytes: &'a [u8]) -> Result<Vec<(&'a str, &'a str)>> { pub fn headers<'a>(&self, bytes: &'a [u8]) -> Result<Vec<(&'a str, &'a str)>> {

@ -129,7 +129,7 @@ impl fmt::Debug for Address {
} }
/// Helper struct to return slices from a struct field on demand. /// Helper struct to return slices from a struct field on demand.
#[derive(Clone, Debug, Serialize, Deserialize, Default)] #[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Copy)]
pub struct StrBuilder { pub struct StrBuilder {
pub offset: usize, pub offset: usize,
pub length: usize, pub length: usize,
@ -146,12 +146,13 @@ pub trait StrBuild {
} }
impl StrBuilder { impl StrBuilder {
fn display<'a>(&self, s: &'a [u8]) -> String { pub fn display<'a>(&self, s: &'a [u8]) -> String {
let offset = self.offset; let offset = self.offset;
let length = self.length; let length = self.length;
String::from_utf8(s[offset..offset + length].to_vec()).unwrap() String::from_utf8(s[offset..offset + length].to_vec()).unwrap()
} }
fn display_bytes<'a>(&self, b: &'a [u8]) -> &'a [u8] {
pub fn display_bytes<'a>(&self, b: &'a [u8]) -> &'a [u8] {
&b[self.offset..(self.offset + self.length)] &b[self.offset..(self.offset + self.length)]
} }
} }

@ -20,6 +20,7 @@
*/ */
use crate::email::attachments::{Attachment, AttachmentBuilder}; use crate::email::attachments::{Attachment, AttachmentBuilder};
use crate::email::parser::BytesExt; use crate::email::parser::BytesExt;
use std::fmt::{Display, Formatter, Result as FmtResult}; use std::fmt::{Display, Formatter, Result as FmtResult};
use std::str; use std::str;
@ -123,7 +124,7 @@ pub enum ContentType {
Multipart { Multipart {
boundary: Vec<u8>, boundary: Vec<u8>,
kind: MultipartType, kind: MultipartType,
subattachments: Vec<Attachment>, parts: Vec<Attachment>,
}, },
MessageRfc822, MessageRfc822,
PGPSignature, PGPSignature,
@ -178,7 +179,7 @@ impl ContentType {
} }
} }
pub fn make_boundary(subattachments: &Vec<AttachmentBuilder>) -> String { pub fn make_boundary(parts: &Vec<AttachmentBuilder>) -> String {
use crate::email::compose::random::gen_boundary; use crate::email::compose::random::gen_boundary;
let mut boundary = "bzz_bzz__bzz__".to_string(); let mut boundary = "bzz_bzz__bzz__".to_string();
let mut random_boundary = gen_boundary(); let mut random_boundary = gen_boundary();
@ -186,7 +187,7 @@ impl ContentType {
let mut loop_counter = 4096; let mut loop_counter = 4096;
'loo: loop { 'loo: loop {
let mut flag = true; let mut flag = true;
for sub in subattachments { for sub in parts {
'sub_loop: loop { 'sub_loop: loop {
if sub.raw().find(random_boundary.as_bytes()).is_some() { if sub.raw().find(random_boundary.as_bytes()).is_some() {
random_boundary = gen_boundary(); random_boundary = gen_boundary();

@ -18,6 +18,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use crate::email::address::StrBuilder;
use crate::email::parser; use crate::email::parser;
use crate::email::parser::BytesExt; use crate::email::parser::BytesExt;
use crate::email::EnvelopeWrapper; use crate::email::EnvelopeWrapper;
@ -33,21 +34,59 @@ pub struct AttachmentBuilder {
pub content_transfer_encoding: ContentTransferEncoding, pub content_transfer_encoding: ContentTransferEncoding,
pub raw: Vec<u8>, pub raw: Vec<u8>,
pub body: StrBuilder,
} }
impl AttachmentBuilder { impl AttachmentBuilder {
pub fn new(content: &[u8]) -> Self { pub fn new(content: &[u8]) -> Self {
AttachmentBuilder { let (headers, body) = match parser::attachment(content).to_full_result() {
content_type: Default::default(), Ok(v) => v,
content_transfer_encoding: ContentTransferEncoding::_7Bit, Err(_) => {
raw: content.to_vec(), debug!("error in parsing attachment");
debug!("\n-------------------------------");
debug!("{}\n", ::std::string::String::from_utf8_lossy(content));
debug!("-------------------------------\n");
return AttachmentBuilder {
content_type: Default::default(),
content_transfer_encoding: ContentTransferEncoding::_7Bit,
raw: content.to_vec(),
body: StrBuilder {
length: content.len(),
offset: 0,
},
};
}
};
let raw = content.into();
let body = StrBuilder {
offset: content.len() - body.len(),
length: body.len(),
};
let mut builder = AttachmentBuilder {
raw,
body,
..Default::default()
};
for (name, value) in headers {
if name.eq_ignore_ascii_case(b"content-type") {
builder.set_content_type_from_bytes(value);
} else if name.eq_ignore_ascii_case(b"content-transfer-encoding") {
builder.set_content_transfer_encoding(ContentTransferEncoding::from(value));
}
} }
builder
} }
pub fn raw(&self) -> &[u8] { pub fn raw(&self) -> &[u8] {
&self.raw &self.raw
} }
pub fn body(&self) -> &[u8] {
self.body.display_bytes(&self.raw)
}
pub fn set_raw(&mut self, raw: Vec<u8>) -> &mut Self { pub fn set_raw(&mut self, raw: Vec<u8>) -> &mut Self {
self.raw = raw; self.raw = raw;
self self
@ -84,12 +123,12 @@ impl AttachmentBuilder {
} }
assert!(boundary.is_some()); assert!(boundary.is_some());
let boundary = boundary.unwrap().to_vec(); let boundary = boundary.unwrap().to_vec();
let subattachments = Self::subattachments(&self.raw, &boundary); let parts = Self::parts(self.body(), &boundary);
self.content_type = ContentType::Multipart { self.content_type = ContentType::Multipart {
boundary, boundary,
kind: MultipartType::from(cst), kind: MultipartType::from(cst),
subattachments, parts,
}; };
} else if ct.eq_ignore_ascii_case(b"text") { } else if ct.eq_ignore_ascii_case(b"text") {
self.content_type = ContentType::Text { self.content_type = ContentType::Text {
@ -160,15 +199,16 @@ impl AttachmentBuilder {
content_type: self.content_type, content_type: self.content_type,
content_transfer_encoding: self.content_transfer_encoding, content_transfer_encoding: self.content_transfer_encoding,
raw: self.raw, raw: self.raw,
body: self.body,
} }
} }
pub fn subattachments(raw: &[u8], boundary: &[u8]) -> Vec<Attachment> { pub fn parts(raw: &[u8], boundary: &[u8]) -> Vec<Attachment> {
if raw.is_empty() { if raw.is_empty() {
return Vec::new(); return Vec::new();
} }
match parser::attachments(raw, boundary).to_full_result() { match parser::parts(raw, boundary).to_full_result() {
Ok(attachments) => { Ok(attachments) => {
let mut vec = Vec::with_capacity(attachments.len()); let mut vec = Vec::with_capacity(attachments.len());
for a in attachments { for a in attachments {
@ -185,7 +225,11 @@ impl AttachmentBuilder {
} }
}; };
builder.raw = body.ltrim().into(); builder.raw = a.into();
builder.body = StrBuilder {
offset: a.len() - body.len(),
length: body.len(),
};
for (name, value) in headers { for (name, value) in headers {
if name.eq_ignore_ascii_case(b"content-type") { if name.eq_ignore_ascii_case(b"content-type") {
builder.set_content_type_from_bytes(value); builder.set_content_type_from_bytes(value);
@ -218,11 +262,13 @@ impl From<Attachment> for AttachmentBuilder {
content_type, content_type,
content_transfer_encoding, content_transfer_encoding,
raw, raw,
body,
} = val; } = val;
AttachmentBuilder { AttachmentBuilder {
content_type, content_type,
content_transfer_encoding, content_transfer_encoding,
raw, raw,
body,
} }
} }
} }
@ -230,10 +276,11 @@ impl From<Attachment> for AttachmentBuilder {
/// Immutable attachment type. /// Immutable attachment type.
#[derive(Clone, Serialize, Deserialize, PartialEq)] #[derive(Clone, Serialize, Deserialize, PartialEq)]
pub struct Attachment { pub struct Attachment {
pub(in crate::email) content_type: ContentType, pub content_type: ContentType,
pub(in crate::email) content_transfer_encoding: ContentTransferEncoding, pub content_transfer_encoding: ContentTransferEncoding,
pub(in crate::email) raw: Vec<u8>, pub raw: Vec<u8>,
pub body: StrBuilder,
} }
impl fmt::Debug for Attachment { impl fmt::Debug for Attachment {
@ -254,16 +301,18 @@ impl fmt::Debug for Attachment {
impl fmt::Display for Attachment { impl fmt::Display for Attachment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.content_type { match self.content_type {
ContentType::MessageRfc822 => match EnvelopeWrapper::new(self.raw().to_vec()) { ContentType::MessageRfc822 => {
Ok(wrapper) => write!( match EnvelopeWrapper::new(self.body.display_bytes(&self.raw).to_vec()) {
f, Ok(wrapper) => write!(
"message/rfc822: {} - {} - {}", f,
wrapper.date(), "message/rfc822: {} - {} - {}",
wrapper.field_from_to_string(), wrapper.date(),
wrapper.subject() wrapper.field_from_to_string(),
), wrapper.subject()
Err(e) => write!(f, "{}", e), ),
}, Err(e) => write!(f, "{}", e),
}
}
ContentType::PGPSignature => write!(f, "pgp signature {}", self.mime_type()), ContentType::PGPSignature => write!(f, "pgp signature {}", self.mime_type()),
ContentType::OctetStream { ref name } => { ContentType::OctetStream { ref name } => {
write!(f, "{}", name.clone().unwrap_or_else(|| self.mime_type())) write!(f, "{}", name.clone().unwrap_or_else(|| self.mime_type()))
@ -271,7 +320,7 @@ impl fmt::Display for Attachment {
ContentType::Other { .. } => write!(f, "Data attachment of type {}", self.mime_type()), ContentType::Other { .. } => write!(f, "Data attachment of type {}", self.mime_type()),
ContentType::Text { .. } => write!(f, "Text attachment of type {}", self.mime_type()), ContentType::Text { .. } => write!(f, "Text attachment of type {}", self.mime_type()),
ContentType::Multipart { ContentType::Multipart {
subattachments: ref sub_att_vec, parts: ref sub_att_vec,
.. ..
} => write!( } => write!(
f, f,
@ -292,6 +341,10 @@ impl Attachment {
Attachment { Attachment {
content_type, content_type,
content_transfer_encoding, content_transfer_encoding,
body: StrBuilder {
length: raw.len(),
offset: 0,
},
raw, raw,
} }
} }
@ -300,6 +353,62 @@ impl Attachment {
&self.raw &self.raw
} }
pub fn body(&self) -> &[u8] {
self.body.display_bytes(&self.raw)
}
pub fn part_boundaries(&self) -> Vec<StrBuilder> {
if self.raw.is_empty() {
return Vec::new();
}
match self.content_type {
ContentType::Multipart { ref boundary, .. } => {
match parser::multipart_parts(self.body(), boundary).to_full_result() {
Ok(v) => v,
Err(e) => {
debug!("error in parsing attachment");
debug!("\n-------------------------------");
debug!("{}\n", ::std::string::String::from_utf8_lossy(&self.raw));
debug!("-------------------------------\n");
debug!("{:?}\n", e);
Vec::new()
}
}
}
_ => Vec::new(),
}
}
/* Call on the body of a multipart/mixed Envelope to check if there are attachments without
* completely parsing them */
pub fn check_if_has_attachments_quick(bytes: &[u8], boundary: &[u8]) -> bool {
if bytes.is_empty() {
return false;
}
// FIXME: check if any part is multipart/mixed as well
match parser::multipart_parts(bytes, boundary).to_full_result() {
Ok(parts) => {
for p in parts {
for (n, v) in crate::email::parser::HeaderIterator(p.display_bytes(bytes)) {
if !n.eq_ignore_ascii_case(b"content-type") && !v.starts_with(b"text/") {
return true;
}
}
}
}
Err(e) => {
debug!("error in parsing multipart_parts");
debug!("\n-------------------------------");
debug!("{}\n", ::std::string::String::from_utf8_lossy(bytes));
debug!("-------------------------------\n");
debug!("{:?}\n", e);
}
}
false
}
fn get_text_recursive(&self, text: &mut Vec<u8>) { fn get_text_recursive(&self, text: &mut Vec<u8>) {
match self.content_type { match self.content_type {
ContentType::Text { .. } => { ContentType::Text { .. } => {
@ -307,11 +416,11 @@ impl Attachment {
} }
ContentType::Multipart { ContentType::Multipart {
ref kind, ref kind,
ref subattachments, ref parts,
.. ..
} => match kind { } => match kind {
MultipartType::Alternative => { MultipartType::Alternative => {
for a in subattachments { for a in parts {
if let ContentType::Text { if let ContentType::Text {
kind: Text::Plain, .. kind: Text::Plain, ..
} = a.content_type } = a.content_type
@ -322,7 +431,7 @@ impl Attachment {
} }
} }
_ => { _ => {
for a in subattachments { for a in parts {
a.get_text_recursive(text) a.get_text_recursive(text)
} }
} }
@ -331,7 +440,7 @@ impl Attachment {
} }
} }
pub fn text(&self) -> String { pub fn text(&self) -> String {
let mut text = Vec::with_capacity(self.raw.len()); let mut text = Vec::with_capacity(self.body.length);
self.get_text_recursive(&mut text); self.get_text_recursive(&mut text);
String::from_utf8_lossy(text.as_slice().trim()).into() String::from_utf8_lossy(text.as_slice().trim()).into()
} }
@ -346,7 +455,7 @@ impl Attachment {
fn count_recursive(att: &Attachment, ret: &mut Vec<Attachment>) { fn count_recursive(att: &Attachment, ret: &mut Vec<Attachment>) {
match att.content_type { match att.content_type {
ContentType::Multipart { ContentType::Multipart {
subattachments: ref sub_att_vec, parts: ref sub_att_vec,
.. ..
} => { } => {
ret.push(att.clone()); ret.push(att.clone());
@ -387,10 +496,10 @@ impl Attachment {
} => false, } => false,
ContentType::Multipart { ContentType::Multipart {
kind: MultipartType::Alternative, kind: MultipartType::Alternative,
ref subattachments, ref parts,
.. ..
} => { } => {
for a in subattachments.iter() { for a in parts.iter() {
if let ContentType::Text { if let ContentType::Text {
kind: Text::Plain, .. kind: Text::Plain, ..
} = a.content_type } = a.content_type
@ -402,18 +511,15 @@ impl Attachment {
} }
ContentType::Multipart { ContentType::Multipart {
kind: MultipartType::Signed, kind: MultipartType::Signed,
ref subattachments, ref parts,
.. ..
} => subattachments } => parts
.iter() .iter()
.find(|s| s.content_type != ContentType::PGPSignature) .find(|s| s.content_type != ContentType::PGPSignature)
.map(Attachment::is_html) .map(Attachment::is_html)
.unwrap_or(false), .unwrap_or(false),
ContentType::Multipart { ContentType::Multipart { ref parts, .. } => {
ref subattachments, .. parts.iter().fold(true, |acc, a| match &a.content_type {
} => subattachments
.iter()
.fold(true, |acc, a| match &a.content_type {
ContentType::Text { ContentType::Text {
kind: Text::Plain, .. kind: Text::Plain, ..
} => false, } => false,
@ -425,7 +531,8 @@ impl Attachment {
.. ..
} => a.is_html(), } => a.is_html(),
_ => acc, _ => acc,
}), })
}
_ => false, _ => false,
} }
} }
@ -454,16 +561,16 @@ fn decode_rec_helper<'a>(a: &'a Attachment, filter: &mut Option<Filter<'a>>) ->
.into_bytes(), .into_bytes(),
ContentType::PGPSignature => a.content_type.to_string().into_bytes(), ContentType::PGPSignature => a.content_type.to_string().into_bytes(),
ContentType::MessageRfc822 => { ContentType::MessageRfc822 => {
let temp = decode_rfc822(&a.raw); let temp = decode_rfc822(a.body());
decode_rec(&temp, None) decode_rec(&temp, None)
} }
ContentType::Multipart { ContentType::Multipart {
ref kind, ref kind,
ref subattachments, ref parts,
.. ..
} => match kind { } => match kind {
MultipartType::Alternative => { MultipartType::Alternative => {
for a in subattachments { for a in parts {
if let ContentType::Text { if let ContentType::Text {
kind: Text::Plain, .. kind: Text::Plain, ..
} = a.content_type } = a.content_type
@ -475,7 +582,7 @@ fn decode_rec_helper<'a>(a: &'a Attachment, filter: &mut Option<Filter<'a>>) ->
} }
_ => { _ => {
let mut vec = Vec::new(); let mut vec = Vec::new();
for a in subattachments { for a in parts {
vec.extend(decode_rec_helper(a, filter)); vec.extend(decode_rec_helper(a, filter));
} }
vec vec
@ -495,23 +602,23 @@ fn decode_helper<'a>(a: &'a Attachment, filter: &mut Option<Filter<'a>>) -> Vec<
}; };
let bytes = match a.content_transfer_encoding { let bytes = match a.content_transfer_encoding {
ContentTransferEncoding::Base64 => match BASE64_MIME.decode(a.raw()) { ContentTransferEncoding::Base64 => match BASE64_MIME.decode(a.body()) {
Ok(v) => v, Ok(v) => v,
_ => a.raw().to_vec(), _ => a.body().to_vec(),
}, },
ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_bytes(a.raw()) ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_bytes(a.body())
.to_full_result() .to_full_result()
.unwrap(), .unwrap(),
ContentTransferEncoding::_7Bit ContentTransferEncoding::_7Bit
| ContentTransferEncoding::_8Bit | ContentTransferEncoding::_8Bit
| ContentTransferEncoding::Other { .. } => a.raw().to_vec(), | ContentTransferEncoding::Other { .. } => a.body().to_vec(),
}; };
let mut ret = if a.content_type.is_text() { let mut ret = if a.content_type.is_text() {
if let Ok(v) = parser::decode_charset(&bytes, charset) { if let Ok(v) = parser::decode_charset(&bytes, charset) {
v.into_bytes() v.into_bytes()
} else { } else {
a.raw().to_vec() a.body().to_vec()
} }
} else { } else {
bytes.to_vec() bytes.to_vec()

@ -259,13 +259,13 @@ impl Draft {
ret.push_str("MIME-Version: 1.0\n"); ret.push_str("MIME-Version: 1.0\n");
if !self.attachments.is_empty() { if !self.attachments.is_empty() {
let mut subattachments = Vec::with_capacity(self.attachments.len() + 1); let mut parts = Vec::with_capacity(self.attachments.len() + 1);
let attachments = std::mem::replace(&mut self.attachments, Vec::new()); let attachments = std::mem::replace(&mut self.attachments, Vec::new());
let mut body_attachment = AttachmentBuilder::default(); let mut body_attachment = AttachmentBuilder::default();
body_attachment.set_raw(self.body.as_bytes().to_vec()); body_attachment.set_raw(self.body.as_bytes().to_vec());
subattachments.push(body_attachment); parts.push(body_attachment);
subattachments.extend(attachments.into_iter()); parts.extend(attachments.into_iter());
build_multipart(&mut ret, MultipartType::Mixed, subattachments); build_multipart(&mut ret, MultipartType::Mixed, parts);
} else { } else {
if self.body.is_ascii() { if self.body.is_ascii() {
ret.push('\n'); ret.push('\n');
@ -309,9 +309,9 @@ fn ignore_header(header: &[u8]) -> bool {
} }
} }
fn build_multipart(ret: &mut String, kind: MultipartType, subattachments: Vec<AttachmentBuilder>) { fn build_multipart(ret: &mut String, kind: MultipartType, parts: Vec<AttachmentBuilder>) {
use ContentType::*; use ContentType::*;
let boundary = ContentType::make_boundary(&subattachments); let boundary = ContentType::make_boundary(&parts);
ret.extend( ret.extend(
format!( format!(
"Content-Type: {}; charset=\"utf-8\"; boundary=\"{}\"\n", "Content-Type: {}; charset=\"utf-8\"; boundary=\"{}\"\n",
@ -322,7 +322,7 @@ fn build_multipart(ret: &mut String, kind: MultipartType, subattachments: Vec<At
ret.push('\n'); ret.push('\n');
/* rfc1341 */ /* rfc1341 */
ret.extend("This is a MIME formatted message with attachments. Use a MIME-compliant client to view it properly.\n".chars()); ret.extend("This is a MIME formatted message with attachments. Use a MIME-compliant client to view it properly.\n".chars());
for sub in subattachments { for sub in parts {
ret.push_str("--"); ret.push_str("--");
ret.extend(boundary.chars()); ret.extend(boundary.chars());
ret.push('\n'); ret.push('\n');
@ -347,12 +347,12 @@ fn build_multipart(ret: &mut String, kind: MultipartType, subattachments: Vec<At
Multipart { Multipart {
boundary: _boundary, boundary: _boundary,
kind, kind,
subattachments: subsubattachments, parts: subparts,
} => { } => {
build_multipart( build_multipart(
ret, ret,
kind, kind,
subsubattachments subparts
.into_iter() .into_iter()
.map(|s| s.into()) .map(|s| s.into())
.collect::<Vec<AttachmentBuilder>>(), .collect::<Vec<AttachmentBuilder>>(),

@ -28,6 +28,17 @@ pub(super) use nom::{ErrorKind, IResult, Needed};
use encoding::all::*; use encoding::all::*;
use std; use std;
macro_rules! is_ctl_or_space {
($var:ident) => {
/* <any ASCII control character and DEL> */
$var < 33 || $var == 127
};
($var:expr) => {
/* <any ASCII control character and DEL> */
$var < 33 || $var == 127
};
}
macro_rules! is_whitespace { macro_rules! is_whitespace {
($var:ident) => { ($var:ident) => {
$var == b' ' || $var == b'\t' || $var == b'\n' || $var == b'\r' $var == b' ' || $var == b'\t' || $var == b'\n' || $var == b'\r'
@ -138,11 +149,14 @@ fn header_with_val(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
} }
let mut ptr = 0; let mut ptr = 0;
let mut name: &[u8] = &input[0..0]; let mut name: &[u8] = &input[0..0];
/* field-name = 1*<any CHAR, excluding CTLs, SPACE, and ":"> */
for (i, x) in input.iter().enumerate() { for (i, x) in input.iter().enumerate() {
if *x == b':' { if *x == b':' {
name = &input[0..i]; name = &input[0..i];
ptr = i + 1; ptr = i + 1;
break; break;
} else if is_ctl_or_space!(*x) {
return IResult::Error(error_code!(ErrorKind::Custom(43)));
} }
} }
if name.is_empty() { if name.is_empty() {
@ -184,6 +198,8 @@ fn header_without_val(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
} }
let mut ptr = 0; let mut ptr = 0;
let mut name: &[u8] = &input[0..0]; let mut name: &[u8] = &input[0..0];
let mut has_colon = false;
/* field-name = 1*<any CHAR, excluding CTLs, SPACE, and ":"> */
for (i, x) in input.iter().enumerate() { for (i, x) in input.iter().enumerate() {
if input[i..].starts_with(b"\r\n") { if input[i..].starts_with(b"\r\n") {
name = &input[0..i]; name = &input[0..i];
@ -191,8 +207,11 @@ fn header_without_val(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
break; break;
} else if *x == b':' || *x == b'\n' { } else if *x == b':' || *x == b'\n' {
name = &input[0..i]; name = &input[0..i];
has_colon = true;
ptr = i; ptr = i;
break; break;
} else if is_ctl_or_space!(*x) {
return IResult::Error(error_code!(ErrorKind::Custom(43)));
} }
} }
if name.is_empty() { if name.is_empty() {
@ -200,10 +219,16 @@ fn header_without_val(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
} }
if input[ptr] == b':' { if input[ptr] == b':' {
ptr += 1; ptr += 1;
has_colon = true;
if ptr >= input.len() { if ptr >= input.len() {
return IResult::Incomplete(Needed::Unknown); return IResult::Incomplete(Needed::Unknown);
} }
} }
if !has_colon {
return IResult::Incomplete(Needed::Unknown);
}
while input[ptr] == b' ' { while input[ptr] == b' ' {
ptr += 1; ptr += 1;
if ptr >= input.len() { if ptr >= input.len() {
@ -265,10 +290,10 @@ named!(pub body_raw<&[u8]>,
named!(pub mail<(std::vec::Vec<(&[u8], &[u8])>, &[u8])>, named!(pub mail<(std::vec::Vec<(&[u8], &[u8])>, &[u8])>,
separated_pair!(headers, alt_complete!(tag!(b"\n") | tag!(b"\r\n")), take_while!(call!(|_| true)))); separated_pair!(headers, alt_complete!(tag!(b"\n") | tag!(b"\r\n")), take_while!(call!(|_| true))));
named!(pub attachment<(std::vec::Vec<(&[u8], &[u8])>, &[u8])>, named!(pub attachment<(std::vec::Vec<(&[u8], &[u8])>, &[u8])>,
do_parse!( do_parse!(
opt!(is_a!(" \n\t\r")) >> pair: separated_pair!(many0!(complete!(header)), alt_complete!(tag!(b"\n") | tag!(b"\r\n")), take_while!(call!(|_| true))) >>
pair: pair!(many0!(complete!(header)), take_while!(call!(|_| true))) >>
( { pair } ))); ( { pair } )));
/* Header parsers */ /* Header parsers */
@ -636,9 +661,73 @@ fn message_id_peek(input: &[u8]) -> IResult<&[u8], &[u8]> {
named!(pub references<Vec<&[u8]>>, separated_list!(complete!(is_a!(" \n\t\r")), message_id_peek)); named!(pub references<Vec<&[u8]>>, separated_list!(complete!(is_a!(" \n\t\r")), message_id_peek));
fn attachments_f<'a>(input: &'a [u8], boundary: &[u8]) -> IResult<&'a [u8], Vec<&'a [u8]>> { pub fn multipart_parts<'a>(input: &'a [u8], boundary: &[u8]) -> IResult<&'a [u8], Vec<StrBuilder>> {
let mut ret: Vec<_> = Vec::new();
let mut input = input;
let mut offset = 0;
loop {
let b_start = if let Some(v) = input.find(boundary) {
v
} else {
return IResult::Error(error_code!(ErrorKind::Custom(39)));
};
if b_start < 2 {
return IResult::Error(error_code!(ErrorKind::Custom(40)));
}
offset += b_start - 2;
input = &input[b_start - 2..];
if &input[0..2] == b"--" {
offset += 2 + boundary.len();
input = &input[2 + boundary.len()..];
if input[0] == b'\n' {
offset += 1;
input = &input[1..];
} else if input[0..].starts_with(b"\r\n") {
offset += 2;
input = &input[2..];
} else {
continue;
}
break;
}
}
loop {
if input.len() < boundary.len() + 4 {
return IResult::Error(error_code!(ErrorKind::Custom(41)));
}
if let Some(end) = input.find(boundary) {
if &input[end - 2..end] != b"--" {
return IResult::Error(error_code!(ErrorKind::Custom(42)));
}
ret.push(StrBuilder {
offset,
length: end - 2,
});
offset += end + boundary.len();
input = &input[end + boundary.len()..];
if input.len() < 2 || input[0] != b'\n' || &input[0..2] == b"--" {
break;
}
if input[0] == b'\n' {
offset += 1;
input = &input[1..];
} else if input[0..].starts_with(b"\r\n") {
offset += 2;
input = &input[2..];
}
continue;
} else {
return IResult::Error(error_code!(ErrorKind::Custom(43)));
}
}
IResult::Done(input, ret)
}
fn parts_f<'a>(input: &'a [u8], boundary: &[u8]) -> IResult<&'a [u8], Vec<&'a [u8]>> {
let mut ret: Vec<&[u8]> = Vec::new(); let mut ret: Vec<&[u8]> = Vec::new();
let mut input = input.ltrim(); let mut input = input;
loop { loop {
let b_start = if let Some(v) = input.find(boundary) { let b_start = if let Some(v) = input.find(boundary) {
v v
@ -652,10 +741,13 @@ fn attachments_f<'a>(input: &'a [u8], boundary: &[u8]) -> IResult<&'a [u8], Vec<
input = &input[b_start - 2..]; input = &input[b_start - 2..];
if &input[0..2] == b"--" { if &input[0..2] == b"--" {
input = &input[2 + boundary.len()..]; input = &input[2 + boundary.len()..];
if input[0] != b'\n' && !input[0..].starts_with(b"\r\n") { if input[0] == b'\n' {
input = &input[1..];
} else if input[0..].starts_with(b"\r\n") {
input = &input[2..];
} else {
continue; continue;
} }
input = &input[1..];
break; break;
} }
} }
@ -672,7 +764,11 @@ fn attachments_f<'a>(input: &'a [u8], boundary: &[u8]) -> IResult<&'a [u8], Vec<
if input.len() < 2 || input[0] != b'\n' || &input[0..2] == b"--" { if input.len() < 2 || input[0] != b'\n' || &input[0..2] == b"--" {
break; break;
} }
input = &input[1..]; if input[0] == b'\n' {
input = &input[1..];
} else if input[0..].starts_with(b"\r\n") {
input = &input[2..];
}
continue; continue;
} else { } else {
return IResult::Error(error_code!(ErrorKind::Custom(43))); return IResult::Error(error_code!(ErrorKind::Custom(43)));
@ -681,8 +777,8 @@ fn attachments_f<'a>(input: &'a [u8], boundary: &[u8]) -> IResult<&'a [u8], Vec<
IResult::Done(input, ret) IResult::Done(input, ret)
} }
named_args!(pub attachments<'a>(boundary: &'a [u8]) < Vec<&'this_is_probably_unique_i_hope_please [u8]> >, named_args!(pub parts<'a>(boundary: &'a [u8]) < Vec<&'this_is_probably_unique_i_hope_please [u8]> >,
alt_complete!(call!(attachments_f, boundary) | do_parse!( alt_complete!(call!(parts_f, boundary) | do_parse!(
take_until_and_consume!(&b"--"[..]) >> take_until_and_consume!(&b"--"[..]) >>
take_until_and_consume!(boundary) >> take_until_and_consume!(boundary) >>
( { Vec::<&[u8]>::new() } )) ( { Vec::<&[u8]>::new() } ))
@ -890,6 +986,30 @@ pub fn mailto(mut input: &[u8]) -> IResult<&[u8], Mailto> {
) )
} }
//alt_complete!(call!(header_without_val) | call!(header_with_val))
pub struct HeaderIterator<'a>(pub &'a [u8]);
impl<'a> Iterator for HeaderIterator<'a> {
type Item = (&'a [u8], &'a [u8]);
fn next(&mut self) -> Option<(&'a [u8], &'a [u8])> {
if self.0.is_empty() {
return None;
}
match header(self.0) {
IResult::Done(rest, value) => {
self.0 = rest;
Some(value)
}
_ => {
self.0 = &[];
None
}
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -1009,7 +1129,7 @@ mod tests {
Ok(v) => v, Ok(v) => v,
Err(_) => panic!(), Err(_) => panic!(),
}; };
let attachments = attachments(body, boundary).to_full_result().unwrap(); let attachments = parts(body, boundary).to_full_result().unwrap();
assert_eq!(attachments.len(), 4); assert_eq!(attachments.len(), 4);
let v: Vec<&str> = attachments let v: Vec<&str> = attachments
.iter() .iter()

@ -213,7 +213,7 @@ impl MailView {
} }
t t
} }
ViewMode::Raw => String::from_utf8_lossy(body.raw()).into_owned(), ViewMode::Raw => String::from_utf8_lossy(body.body()).into_owned(),
ViewMode::Url => { ViewMode::Url => {
let mut t = body_text.to_string(); let mut t = body_text.to_string();
for (lidx, l) in finder.links(&body.text()).enumerate() { for (lidx, l) in finder.links(&body.text()).enumerate() {
@ -745,7 +745,7 @@ impl Component for MailView {
if let Some(u) = envelope.body(op).attachments().get(lidx) { if let Some(u) = envelope.body(op).attachments().get(lidx) {
match u.content_type() { match u.content_type() {
ContentType::MessageRfc822 => { ContentType::MessageRfc822 => {
match EnvelopeWrapper::new(u.raw().to_vec()) { match EnvelopeWrapper::new(u.body().to_vec()) {
Ok(wrapper) => { Ok(wrapper) => {
context.replies.push_back(UIEvent::Action(Tab(New(Some( context.replies.push_back(UIEvent::Action(Tab(New(Some(
Box::new(EnvelopeView::new( Box::new(EnvelopeView::new(

@ -150,7 +150,7 @@ impl EnvelopeView {
} }
t t
} }
ViewMode::Raw => String::from_utf8_lossy(body.raw()).into_owned(), ViewMode::Raw => String::from_utf8_lossy(body.body()).into_owned(),
ViewMode::Url => { ViewMode::Url => {
let mut t = body_text.to_string(); let mut t = body_text.to_string();
for (lidx, l) in finder.links(&body.text()).enumerate() { for (lidx, l) in finder.links(&body.text()).enumerate() {

Loading…
Cancel
Save