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`.
embed
Manos Pitsidianakis 2019-09-25 22:00:30 +03:00
parent 5a53020f3d
commit 9305e543cf
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
8 changed files with 320 additions and 106 deletions

View File

@ -274,16 +274,25 @@ impl Envelope {
}
} else if name.eq_ignore_ascii_case(b"content-type") {
match parser::content_type(value).to_full_result() {
Ok((ct, cst, _))
Ok((ct, cst, ref params))
if ct.eq_ignore_ascii_case(b"multipart")
&& 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);
let b = builder.build();
let subs = b.attachments();
self.has_attachments = subs.iter().any(|sub| !sub.is_text());
let mut boundary = None;
for (n, v) in params {
if n == b"boundary" {
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())
}
pub fn body_bytes(&self, bytes: &[u8]) -> Attachment {
if bytes.is_empty() {
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);
}
}
let builder = AttachmentBuilder::new(bytes);
builder.build()
}
pub fn headers<'a>(&self, bytes: &'a [u8]) -> Result<Vec<(&'a str, &'a str)>> {

View File

@ -129,7 +129,7 @@ impl fmt::Debug for Address {
}
/// 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 offset: usize,
pub length: usize,
@ -146,12 +146,13 @@ pub trait StrBuild {
}
impl StrBuilder {
fn display<'a>(&self, s: &'a [u8]) -> String {
pub fn display<'a>(&self, s: &'a [u8]) -> String {
let offset = self.offset;
let length = self.length;
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)]
}
}

View File

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

View File

@ -18,6 +18,7 @@
* 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::address::StrBuilder;
use crate::email::parser;
use crate::email::parser::BytesExt;
use crate::email::EnvelopeWrapper;
@ -33,21 +34,59 @@ pub struct AttachmentBuilder {
pub content_transfer_encoding: ContentTransferEncoding,
pub raw: Vec<u8>,
pub body: StrBuilder,
}
impl AttachmentBuilder {
pub fn new(content: &[u8]) -> Self {
AttachmentBuilder {
content_type: Default::default(),
content_transfer_encoding: ContentTransferEncoding::_7Bit,
raw: content.to_vec(),
let (headers, body) = match parser::attachment(content).to_full_result() {
Ok(v) => v,
Err(_) => {
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] {
&self.raw
}
pub fn body(&self) -> &[u8] {
self.body.display_bytes(&self.raw)
}
pub fn set_raw(&mut self, raw: Vec<u8>) -> &mut Self {
self.raw = raw;
self
@ -84,12 +123,12 @@ impl AttachmentBuilder {
}
assert!(boundary.is_some());
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 {
boundary,
kind: MultipartType::from(cst),
subattachments,
parts,
};
} else if ct.eq_ignore_ascii_case(b"text") {
self.content_type = ContentType::Text {
@ -160,15 +199,16 @@ impl AttachmentBuilder {
content_type: self.content_type,
content_transfer_encoding: self.content_transfer_encoding,
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() {
return Vec::new();
}
match parser::attachments(raw, boundary).to_full_result() {
match parser::parts(raw, boundary).to_full_result() {
Ok(attachments) => {
let mut vec = Vec::with_capacity(attachments.len());
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 {
if name.eq_ignore_ascii_case(b"content-type") {
builder.set_content_type_from_bytes(value);
@ -218,11 +262,13 @@ impl From<Attachment> for AttachmentBuilder {
content_type,
content_transfer_encoding,
raw,
body,
} = val;
AttachmentBuilder {
content_type,
content_transfer_encoding,
raw,
body,
}
}
}
@ -230,10 +276,11 @@ impl From<Attachment> for AttachmentBuilder {
/// Immutable attachment type.
#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub struct Attachment {
pub(in crate::email) content_type: ContentType,
pub(in crate::email) content_transfer_encoding: ContentTransferEncoding,
pub content_type: ContentType,
pub content_transfer_encoding: ContentTransferEncoding,
pub(in crate::email) raw: Vec<u8>,
pub raw: Vec<u8>,
pub body: StrBuilder,
}
impl fmt::Debug for Attachment {
@ -254,16 +301,18 @@ impl fmt::Debug for Attachment {
impl fmt::Display for Attachment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.content_type {
ContentType::MessageRfc822 => match EnvelopeWrapper::new(self.raw().to_vec()) {
Ok(wrapper) => write!(
f,
"message/rfc822: {} - {} - {}",
wrapper.date(),
wrapper.field_from_to_string(),
wrapper.subject()
),
Err(e) => write!(f, "{}", e),
},
ContentType::MessageRfc822 => {
match EnvelopeWrapper::new(self.body.display_bytes(&self.raw).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::OctetStream { ref name } => {
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::Text { .. } => write!(f, "Text attachment of type {}", self.mime_type()),
ContentType::Multipart {
subattachments: ref sub_att_vec,
parts: ref sub_att_vec,
..
} => write!(
f,
@ -292,6 +341,10 @@ impl Attachment {
Attachment {
content_type,
content_transfer_encoding,
body: StrBuilder {
length: raw.len(),
offset: 0,
},
raw,
}
}
@ -300,6 +353,62 @@ impl Attachment {
&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>) {
match self.content_type {
ContentType::Text { .. } => {
@ -307,11 +416,11 @@ impl Attachment {
}
ContentType::Multipart {
ref kind,
ref subattachments,
ref parts,
..
} => match kind {
MultipartType::Alternative => {
for a in subattachments {
for a in parts {
if let ContentType::Text {
kind: Text::Plain, ..
} = a.content_type
@ -322,7 +431,7 @@ impl Attachment {
}
}
_ => {
for a in subattachments {
for a in parts {
a.get_text_recursive(text)
}
}
@ -331,7 +440,7 @@ impl Attachment {
}
}
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);
String::from_utf8_lossy(text.as_slice().trim()).into()
}
@ -346,7 +455,7 @@ impl Attachment {
fn count_recursive(att: &Attachment, ret: &mut Vec<Attachment>) {
match att.content_type {
ContentType::Multipart {
subattachments: ref sub_att_vec,
parts: ref sub_att_vec,
..
} => {
ret.push(att.clone());
@ -387,10 +496,10 @@ impl Attachment {
} => false,
ContentType::Multipart {
kind: MultipartType::Alternative,
ref subattachments,
ref parts,
..
} => {
for a in subattachments.iter() {
for a in parts.iter() {
if let ContentType::Text {
kind: Text::Plain, ..
} = a.content_type
@ -402,18 +511,15 @@ impl Attachment {
}
ContentType::Multipart {
kind: MultipartType::Signed,
ref subattachments,
ref parts,
..
} => subattachments
} => parts
.iter()
.find(|s| s.content_type != ContentType::PGPSignature)
.map(Attachment::is_html)
.unwrap_or(false),
ContentType::Multipart {
ref subattachments, ..
} => subattachments
.iter()
.fold(true, |acc, a| match &a.content_type {
ContentType::Multipart { ref parts, .. } => {
parts.iter().fold(true, |acc, a| match &a.content_type {
ContentType::Text {
kind: Text::Plain, ..
} => false,
@ -425,7 +531,8 @@ impl Attachment {
..
} => a.is_html(),
_ => acc,
}),
})
}
_ => false,
}
}
@ -454,16 +561,16 @@ fn decode_rec_helper<'a>(a: &'a Attachment, filter: &mut Option<Filter<'a>>) ->
.into_bytes(),
ContentType::PGPSignature => a.content_type.to_string().into_bytes(),
ContentType::MessageRfc822 => {
let temp = decode_rfc822(&a.raw);
let temp = decode_rfc822(a.body());
decode_rec(&temp, None)
}
ContentType::Multipart {
ref kind,
ref subattachments,
ref parts,
..
} => match kind {
MultipartType::Alternative => {
for a in subattachments {
for a in parts {
if let ContentType::Text {
kind: Text::Plain, ..
} = 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();
for a in subattachments {
for a in parts {
vec.extend(decode_rec_helper(a, filter));
}
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 {
ContentTransferEncoding::Base64 => match BASE64_MIME.decode(a.raw()) {
ContentTransferEncoding::Base64 => match BASE64_MIME.decode(a.body()) {
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()
.unwrap(),
ContentTransferEncoding::_7Bit
| ContentTransferEncoding::_8Bit
| ContentTransferEncoding::Other { .. } => a.raw().to_vec(),
| ContentTransferEncoding::Other { .. } => a.body().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.raw().to_vec()
a.body().to_vec()
}
} else {
bytes.to_vec()

View File

@ -259,13 +259,13 @@ impl Draft {
ret.push_str("MIME-Version: 1.0\n");
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 mut body_attachment = AttachmentBuilder::default();
body_attachment.set_raw(self.body.as_bytes().to_vec());
subattachments.push(body_attachment);
subattachments.extend(attachments.into_iter());
build_multipart(&mut ret, MultipartType::Mixed, subattachments);
parts.push(body_attachment);
parts.extend(attachments.into_iter());
build_multipart(&mut ret, MultipartType::Mixed, parts);
} else {
if self.body.is_ascii() {
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::*;
let boundary = ContentType::make_boundary(&subattachments);
let boundary = ContentType::make_boundary(&parts);
ret.extend(
format!(
"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');
/* rfc1341 */
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.extend(boundary.chars());
ret.push('\n');
@ -347,12 +347,12 @@ fn build_multipart(ret: &mut String, kind: MultipartType, subattachments: Vec<At
Multipart {
boundary: _boundary,
kind,
subattachments: subsubattachments,
parts: subparts,
} => {
build_multipart(
ret,
kind,
subsubattachments
subparts
.into_iter()
.map(|s| s.into())
.collect::<Vec<AttachmentBuilder>>(),

View File

@ -28,6 +28,17 @@ pub(super) use nom::{ErrorKind, IResult, Needed};
use encoding::all::*;
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 {
($var:ident) => {
$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 name: &[u8] = &input[0..0];
/* field-name = 1*<any CHAR, excluding CTLs, SPACE, and ":"> */
for (i, x) in input.iter().enumerate() {
if *x == b':' {
name = &input[0..i];
ptr = i + 1;
break;
} else if is_ctl_or_space!(*x) {
return IResult::Error(error_code!(ErrorKind::Custom(43)));
}
}
if name.is_empty() {
@ -184,6 +198,8 @@ fn header_without_val(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
}
let mut ptr = 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() {
if input[i..].starts_with(b"\r\n") {
name = &input[0..i];
@ -191,8 +207,11 @@ fn header_without_val(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
break;
} else if *x == b':' || *x == b'\n' {
name = &input[0..i];
has_colon = true;
ptr = i;
break;
} else if is_ctl_or_space!(*x) {
return IResult::Error(error_code!(ErrorKind::Custom(43)));
}
}
if name.is_empty() {
@ -200,10 +219,16 @@ fn header_without_val(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
}
if input[ptr] == b':' {
ptr += 1;
has_colon = true;
if ptr >= input.len() {
return IResult::Incomplete(Needed::Unknown);
}
}
if !has_colon {
return IResult::Incomplete(Needed::Unknown);
}
while input[ptr] == b' ' {
ptr += 1;
if ptr >= input.len() {
@ -265,10 +290,10 @@ named!(pub body_raw<&[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))));
named!(pub attachment<(std::vec::Vec<(&[u8], &[u8])>, &[u8])>,
do_parse!(
opt!(is_a!(" \n\t\r")) >>
pair: pair!(many0!(complete!(header)), take_while!(call!(|_| true))) >>
pair: separated_pair!(many0!(complete!(header)), alt_complete!(tag!(b"\n") | tag!(b"\r\n")), take_while!(call!(|_| true))) >>
( { pair } )));
/* 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));
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 input = input.ltrim();
let mut input = input;
loop {
let b_start = if let Some(v) = input.find(boundary) {
v
@ -652,10 +741,13 @@ fn attachments_f<'a>(input: &'a [u8], boundary: &[u8]) -> IResult<&'a [u8], Vec<
input = &input[b_start - 2..];
if &input[0..2] == b"--" {
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;
}
input = &input[1..];
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"--" {
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;
} else {
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)
}
named_args!(pub attachments<'a>(boundary: &'a [u8]) < Vec<&'this_is_probably_unique_i_hope_please [u8]> >,
alt_complete!(call!(attachments_f, boundary) | do_parse!(
named_args!(pub parts<'a>(boundary: &'a [u8]) < Vec<&'this_is_probably_unique_i_hope_please [u8]> >,
alt_complete!(call!(parts_f, boundary) | do_parse!(
take_until_and_consume!(&b"--"[..]) >>
take_until_and_consume!(boundary) >>
( { 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)]
mod tests {
@ -1009,7 +1129,7 @@ mod tests {
Ok(v) => v,
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);
let v: Vec<&str> = attachments
.iter()

View File

@ -213,7 +213,7 @@ impl MailView {
}
t
}
ViewMode::Raw => String::from_utf8_lossy(body.raw()).into_owned(),
ViewMode::Raw => String::from_utf8_lossy(body.body()).into_owned(),
ViewMode::Url => {
let mut t = body_text.to_string();
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) {
match u.content_type() {
ContentType::MessageRfc822 => {
match EnvelopeWrapper::new(u.raw().to_vec()) {
match EnvelopeWrapper::new(u.body().to_vec()) {
Ok(wrapper) => {
context.replies.push_back(UIEvent::Action(Tab(New(Some(
Box::new(EnvelopeView::new(

View File

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