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 struct AttachmentBuilder {
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub content_transfer_encoding: ContentTransferEncoding,
|
pub content_transfer_encoding: ContentTransferEncoding,
|
||||||
|
pub content_disposition: ContentDisposition,
|
||||||
|
|
||||||
pub raw: Vec<u8>,
|
pub raw: Vec<u8>,
|
||||||
pub body: StrBuilder,
|
pub body: StrBuilder,
|
||||||
|
@ -50,6 +51,7 @@ impl AttachmentBuilder {
|
||||||
return AttachmentBuilder {
|
return AttachmentBuilder {
|
||||||
content_type: Default::default(),
|
content_type: Default::default(),
|
||||||
content_transfer_encoding: ContentTransferEncoding::_7Bit,
|
content_transfer_encoding: ContentTransferEncoding::_7Bit,
|
||||||
|
content_disposition: ContentDisposition::default(),
|
||||||
raw: content.to_vec(),
|
raw: content.to_vec(),
|
||||||
body: StrBuilder {
|
body: StrBuilder {
|
||||||
length: content.len(),
|
length: content.len(),
|
||||||
|
@ -74,6 +76,8 @@ impl AttachmentBuilder {
|
||||||
builder.set_content_type_from_bytes(value);
|
builder.set_content_type_from_bytes(value);
|
||||||
} else if name.eq_ignore_ascii_case(b"content-transfer-encoding") {
|
} else if name.eq_ignore_ascii_case(b"content-transfer-encoding") {
|
||||||
builder.set_content_transfer_encoding(ContentTransferEncoding::from(value));
|
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
|
builder
|
||||||
|
@ -116,6 +120,15 @@ impl AttachmentBuilder {
|
||||||
self
|
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 {
|
pub fn content_transfer_encoding(&self) -> &ContentTransferEncoding {
|
||||||
&self.content_transfer_encoding
|
&self.content_transfer_encoding
|
||||||
}
|
}
|
||||||
|
@ -218,6 +231,7 @@ impl AttachmentBuilder {
|
||||||
Attachment {
|
Attachment {
|
||||||
content_type: self.content_type,
|
content_type: self.content_type,
|
||||||
content_transfer_encoding: self.content_transfer_encoding,
|
content_transfer_encoding: self.content_transfer_encoding,
|
||||||
|
content_disposition: self.content_disposition,
|
||||||
raw: self.raw,
|
raw: self.raw,
|
||||||
body: self.body,
|
body: self.body,
|
||||||
}
|
}
|
||||||
|
@ -257,6 +271,8 @@ impl AttachmentBuilder {
|
||||||
builder.set_content_transfer_encoding(ContentTransferEncoding::from(
|
builder.set_content_transfer_encoding(ContentTransferEncoding::from(
|
||||||
value,
|
value,
|
||||||
));
|
));
|
||||||
|
} else if name.eq_ignore_ascii_case(b"content-disposition") {
|
||||||
|
builder.set_content_disposition(ContentDisposition::from(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vec.push(builder.build());
|
vec.push(builder.build());
|
||||||
|
@ -280,12 +296,14 @@ impl From<Attachment> for AttachmentBuilder {
|
||||||
fn from(val: Attachment) -> Self {
|
fn from(val: Attachment) -> Self {
|
||||||
let Attachment {
|
let Attachment {
|
||||||
content_type,
|
content_type,
|
||||||
|
content_disposition,
|
||||||
content_transfer_encoding,
|
content_transfer_encoding,
|
||||||
raw,
|
raw,
|
||||||
body,
|
body,
|
||||||
} = val;
|
} = val;
|
||||||
AttachmentBuilder {
|
AttachmentBuilder {
|
||||||
content_type,
|
content_type,
|
||||||
|
content_disposition,
|
||||||
content_transfer_encoding,
|
content_transfer_encoding,
|
||||||
raw,
|
raw,
|
||||||
body,
|
body,
|
||||||
|
@ -298,12 +316,14 @@ impl From<AttachmentBuilder> for Attachment {
|
||||||
let AttachmentBuilder {
|
let AttachmentBuilder {
|
||||||
content_type,
|
content_type,
|
||||||
content_transfer_encoding,
|
content_transfer_encoding,
|
||||||
|
content_disposition,
|
||||||
raw,
|
raw,
|
||||||
body,
|
body,
|
||||||
} = val;
|
} = val;
|
||||||
Attachment {
|
Attachment {
|
||||||
content_type,
|
content_type,
|
||||||
content_transfer_encoding,
|
content_transfer_encoding,
|
||||||
|
content_disposition,
|
||||||
raw,
|
raw,
|
||||||
body,
|
body,
|
||||||
}
|
}
|
||||||
|
@ -315,6 +335,7 @@ impl From<AttachmentBuilder> for Attachment {
|
||||||
pub struct Attachment {
|
pub struct Attachment {
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub content_transfer_encoding: ContentTransferEncoding,
|
pub content_transfer_encoding: ContentTransferEncoding,
|
||||||
|
pub content_disposition: ContentDisposition,
|
||||||
|
|
||||||
pub raw: Vec<u8>,
|
pub raw: Vec<u8>,
|
||||||
pub body: StrBuilder,
|
pub body: StrBuilder,
|
||||||
|
@ -395,6 +416,7 @@ impl Attachment {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Attachment {
|
Attachment {
|
||||||
content_type,
|
content_type,
|
||||||
|
content_disposition: ContentDisposition::default(),
|
||||||
content_transfer_encoding,
|
content_transfer_encoding,
|
||||||
body: StrBuilder {
|
body: StrBuilder {
|
||||||
length: raw.len(),
|
length: raw.len(),
|
||||||
|
@ -478,18 +500,22 @@ impl Attachment {
|
||||||
} => match kind {
|
} => match kind {
|
||||||
MultipartType::Alternative => {
|
MultipartType::Alternative => {
|
||||||
for a in parts {
|
for a in parts {
|
||||||
if let ContentType::Text {
|
if a.content_disposition.kind.is_inline() {
|
||||||
kind: Text::Plain, ..
|
if let ContentType::Text {
|
||||||
} = a.content_type
|
kind: Text::Plain, ..
|
||||||
{
|
} = a.content_type
|
||||||
a.get_text_recursive(text);
|
{
|
||||||
break;
|
a.get_text_recursive(text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
for a in parts {
|
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 {
|
pub fn text(&self) -> String {
|
||||||
let mut text = Vec::with_capacity(self.body.length);
|
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()).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mime_type(&self) -> String {
|
pub fn mime_type(&self) -> String {
|
||||||
|
@ -674,6 +700,25 @@ impl Attachment {
|
||||||
|
|
||||||
ret
|
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 {
|
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();
|
let mut vec = Vec::new();
|
||||||
for a in parts {
|
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
|
vec
|
||||||
}
|
}
|
||||||
|
|
|
@ -643,6 +643,7 @@ pub mod headers {
|
||||||
pub mod attachments {
|
pub mod attachments {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::email::address::*;
|
use crate::email::address::*;
|
||||||
|
use crate::email::attachment_types::{ContentDisposition, ContentDispositionKind};
|
||||||
pub fn attachment(input: &[u8]) -> IResult<&[u8], (std::vec::Vec<(&[u8], &[u8])>, &[u8])> {
|
pub fn attachment(input: &[u8]) -> IResult<&[u8], (std::vec::Vec<(&[u8], &[u8])>, &[u8])> {
|
||||||
separated_pair(
|
separated_pair(
|
||||||
many0(headers::header),
|
many0(headers::header),
|
||||||
|
@ -810,7 +811,7 @@ pub mod attachments {
|
||||||
|
|
||||||
/* Caution: values should be passed through phrase() */
|
/* Caution: values should be passed through phrase() */
|
||||||
pub fn content_type_parameter(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
|
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, name) = terminated(take_until("="), tag("="))(input.ltrim())?;
|
||||||
let (input, value) = alt((
|
let (input, value) = alt((
|
||||||
delimited(tag("\""), take_until("\""), tag("\"")),
|
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])>)> {
|
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, _) = tag("/")(input)?;
|
||||||
let (input, _subtype) = is_not(";")(input)?;
|
let (input, _subtype) = is_not(";")(input)?;
|
||||||
let (input, parameters) = many0(content_type_parameter)(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 {
|
pub mod encodings {
|
||||||
|
|
Loading…
Reference in New Issue