melib/email/attachments: add Content-Disposition

memfd
Manos Pitsidianakis 2020-08-09 09:49:32 +03:00
parent b2c14abd6e
commit c0f8bc1aed
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
3 changed files with 158 additions and 11 deletions

View File

@ -336,3 +336,52 @@ impl From<&[u8]> for ContentTransferEncoding {
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ContentDisposition {
pub kind: ContentDispositionKind,
pub filename: Option<String>,
pub creation_date: Option<String>,
pub modification_date: Option<String>,
pub read_date: Option<String>,
pub size: Option<String>,
pub parameter: Vec<String>,
}
#[derive(Clone, Debug, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ContentDispositionKind {
Inline,
Attachment,
}
impl ContentDispositionKind {
pub fn is_inline(&self) -> bool {
*self == ContentDispositionKind::Inline
}
pub fn is_attachment(&self) -> bool {
*self == ContentDispositionKind::Attachment
}
}
impl Default for ContentDispositionKind {
fn default() -> Self {
ContentDispositionKind::Inline
}
}
impl Display for ContentDispositionKind {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match *self {
ContentDispositionKind::Inline => write!(f, "inline"),
ContentDispositionKind::Attachment => write!(f, "attachment"),
}
}
}
impl From<&[u8]> for ContentDisposition {
fn from(val: &[u8]) -> ContentDisposition {
crate::email::parser::attachments::content_disposition(val)
.map(|(_, v)| v)
.unwrap_or_default()
}
}

View File

@ -32,6 +32,7 @@ pub use crate::email::attachment_types::*;
pub struct AttachmentBuilder {
pub content_type: ContentType,
pub content_transfer_encoding: ContentTransferEncoding,
pub content_disposition: ContentDisposition,
pub raw: Vec<u8>,
pub body: StrBuilder,
@ -50,6 +51,7 @@ impl AttachmentBuilder {
return AttachmentBuilder {
content_type: Default::default(),
content_transfer_encoding: ContentTransferEncoding::_7Bit,
content_disposition: ContentDisposition::default(),
raw: content.to_vec(),
body: StrBuilder {
length: content.len(),
@ -74,6 +76,8 @@ impl AttachmentBuilder {
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));
} else if name.eq_ignore_ascii_case(b"content-disposition") {
builder.set_content_disposition(ContentDisposition::from(value));
}
}
builder
@ -116,6 +120,15 @@ impl AttachmentBuilder {
self
}
pub fn set_content_disposition(&mut self, val: ContentDisposition) -> &mut Self {
self.content_disposition = val;
self
}
pub fn content_disposition(&self) -> &ContentDisposition {
&self.content_disposition
}
pub fn content_transfer_encoding(&self) -> &ContentTransferEncoding {
&self.content_transfer_encoding
}
@ -218,6 +231,7 @@ impl AttachmentBuilder {
Attachment {
content_type: self.content_type,
content_transfer_encoding: self.content_transfer_encoding,
content_disposition: self.content_disposition,
raw: self.raw,
body: self.body,
}
@ -257,6 +271,8 @@ impl AttachmentBuilder {
builder.set_content_transfer_encoding(ContentTransferEncoding::from(
value,
));
} else if name.eq_ignore_ascii_case(b"content-disposition") {
builder.set_content_disposition(ContentDisposition::from(value));
}
}
vec.push(builder.build());
@ -280,12 +296,14 @@ impl From<Attachment> for AttachmentBuilder {
fn from(val: Attachment) -> Self {
let Attachment {
content_type,
content_disposition,
content_transfer_encoding,
raw,
body,
} = val;
AttachmentBuilder {
content_type,
content_disposition,
content_transfer_encoding,
raw,
body,
@ -298,12 +316,14 @@ impl From<AttachmentBuilder> for Attachment {
let AttachmentBuilder {
content_type,
content_transfer_encoding,
content_disposition,
raw,
body,
} = val;
Attachment {
content_type,
content_transfer_encoding,
content_disposition,
raw,
body,
}
@ -315,6 +335,7 @@ impl From<AttachmentBuilder> for Attachment {
pub struct Attachment {
pub content_type: ContentType,
pub content_transfer_encoding: ContentTransferEncoding,
pub content_disposition: ContentDisposition,
pub raw: Vec<u8>,
pub body: StrBuilder,
@ -395,6 +416,7 @@ impl Attachment {
) -> Self {
Attachment {
content_type,
content_disposition: ContentDisposition::default(),
content_transfer_encoding,
body: StrBuilder {
length: raw.len(),
@ -478,18 +500,22 @@ impl Attachment {
} => match kind {
MultipartType::Alternative => {
for a in parts {
if let ContentType::Text {
kind: Text::Plain, ..
} = a.content_type
{
a.get_text_recursive(text);
break;
if a.content_disposition.kind.is_inline() {
if let ContentType::Text {
kind: Text::Plain, ..
} = a.content_type
{
a.get_text_recursive(text);
break;
}
}
}
}
_ => {
for a in parts {
a.get_text_recursive(text)
if a.content_disposition.kind.is_inline() {
a.get_text_recursive(text);
}
}
}
},
@ -499,7 +525,7 @@ impl Attachment {
pub fn text(&self) -> String {
let mut text = Vec::with_capacity(self.body.length);
self.get_text_recursive(&mut text);
String::from_utf8_lossy(text.as_slice().trim()).into()
String::from_utf8_lossy(text.as_slice()).into()
}
pub fn mime_type(&self) -> String {
@ -674,6 +700,25 @@ impl Attachment {
ret
}
pub fn filename(&self) -> Option<String> {
if self.content_disposition.kind.is_attachment() {
self.content_disposition.filename.clone()
} else {
None
}
.or_else(|| match &self.content_type {
ContentType::Text { parameters, .. } => parameters
.iter()
.find(|(h, _)| {
h.eq_ignore_ascii_case(b"name") | h.eq_ignore_ascii_case(b"filename")
})
.map(|(_, v)| String::from_utf8_lossy(v).to_string()),
ContentType::Other { name, .. } | ContentType::OctetStream { name, .. } => name.clone(),
_ => None,
})
.map(|n| n.replace(|c| std::path::is_separator(c) || c.is_ascii_control(), "_"))
}
}
pub fn interpret_format_flowed(_t: &str) -> String {
@ -729,7 +774,9 @@ fn decode_rec_helper<'a>(a: &'a Attachment, filter: &mut Option<Filter<'a>>) ->
_ => {
let mut vec = Vec::new();
for a in parts {
vec.extend(decode_rec_helper(a, filter));
if a.content_disposition.kind.is_inline() {
vec.extend(decode_rec_helper(a, filter));
}
}
vec
}

View File

@ -643,6 +643,7 @@ pub mod headers {
pub mod attachments {
use super::*;
use crate::email::address::*;
use crate::email::attachment_types::{ContentDisposition, ContentDispositionKind};
pub fn attachment(input: &[u8]) -> IResult<&[u8], (std::vec::Vec<(&[u8], &[u8])>, &[u8])> {
separated_pair(
many0(headers::header),
@ -810,7 +811,7 @@ pub mod attachments {
/* Caution: values should be passed through phrase() */
pub fn content_type_parameter(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
let (input, _) = tag(";")(input)?;
let (input, _) = tag(";")(input.ltrim())?;
let (input, name) = terminated(take_until("="), tag("="))(input.ltrim())?;
let (input, value) = alt((
delimited(tag("\""), take_until("\""), tag("\"")),
@ -821,7 +822,7 @@ pub mod attachments {
}
pub fn content_type(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8], Vec<(&[u8], &[u8])>)> {
let (input, _type) = take_until("/")(input)?;
let (input, _type) = take_until("/")(input.ltrim())?;
let (input, _) = tag("/")(input)?;
let (input, _subtype) = is_not(";")(input)?;
let (input, parameters) = many0(content_type_parameter)(input)?;
@ -838,6 +839,56 @@ pub mod attachments {
));
*/
}
/* Caution: values should be passed through phrase() */
pub fn content_disposition_parameter(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
let (input, _) = tag(";")(input.ltrim())?;
let (input, name) = terminated(take_until("="), tag("="))(input.ltrim())?;
let (input, value) = alt((
delimited(tag("\""), take_until("\""), tag("\"")),
is_not(";"),
))(input.ltrim())?;
Ok((input, (name, value)))
}
pub fn content_disposition(input: &[u8]) -> IResult<&[u8], ContentDisposition> {
let (input, kind) = alt((take_until(";"), take_while(|_| true)))(input.trim())?;
let mut ret = ContentDisposition {
kind: if kind.trim().eq_ignore_ascii_case(b"attachment") {
ContentDispositionKind::Attachment
} else {
ContentDispositionKind::Inline
},
..ContentDisposition::default()
};
if input.is_empty() {
return Ok((input, ret));
}
let (input, parameters) = many0(content_disposition_parameter)(input.ltrim())?;
for (k, v) in parameters {
if k.eq_ignore_ascii_case(b"filename") {
ret.filename =
Some(String::from_utf8_lossy(&super::encodings::phrase(v, false)?.1).into());
} else if k.eq_ignore_ascii_case(b"size") {
ret.size =
Some(String::from_utf8_lossy(&super::encodings::phrase(v, false)?.1).into());
} else if k.eq_ignore_ascii_case(b"creation-date") {
ret.creation_date =
Some(String::from_utf8_lossy(&super::encodings::phrase(v, false)?.1).into());
} else if k.eq_ignore_ascii_case(b"modification-date") {
ret.modification_date =
Some(String::from_utf8_lossy(&super::encodings::phrase(v, false)?.1).into());
} else if k.eq_ignore_ascii_case(b"read-date") {
ret.read_date =
Some(String::from_utf8_lossy(&super::encodings::phrase(v, false)?.1).into());
} else {
ret.parameter
.push(String::from_utf8_lossy(&super::encodings::phrase(v, false)?.1).into());
}
}
Ok((input, ret))
}
}
pub mod encodings {