melib/email/attachments: add Content-Disposition
parent
b2c14abd6e
commit
c0f8bc1aed
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue