summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2022-12-09 14:06:20 +0200
committerManos Pitsidianakis <el13635@mail.ntua.gr>2022-12-09 14:06:20 +0200
commit40c6647db83c5137b79c9bec233972a8a78aeb76 (patch)
tree178afa922b0a985140817eb3f792d284afef262a
parentf63ce388f7774ea015fdaa2362202c33f3ddacd4 (diff)
downloadmeli-40c6647db83c5137b79c9bec233972a8a78aeb76.zip
Fix multipart/related with main text/html part not displayed correctly
-rw-r--r--melib/src/email/attachment_types.rs46
-rw-r--r--melib/src/email/attachments.rs103
-rw-r--r--melib/src/email/compose.rs24
-rw-r--r--melib/src/email/pgp.rs1
-rw-r--r--src/components/mail/compose.rs3
-rw-r--r--src/components/mail/pgp.rs8
-rw-r--r--src/components/mail/view.rs5
7 files changed, 147 insertions, 43 deletions
diff --git a/melib/src/email/attachment_types.rs b/melib/src/email/attachment_types.rs
index fbd303f5..b00f5a34 100644
--- a/melib/src/email/attachment_types.rs
+++ b/melib/src/email/attachment_types.rs
@@ -262,6 +262,7 @@ pub enum ContentType {
},
Multipart {
boundary: Vec<u8>,
+ parameters: Vec<(Vec<u8>, Vec<u8>)>,
kind: MultipartType,
parts: Vec<Attachment>,
},
@@ -271,9 +272,11 @@ pub enum ContentType {
Other {
tag: Vec<u8>,
name: Option<String>,
+ parameters: Vec<(Vec<u8>, Vec<u8>)>,
},
OctetStream {
name: Option<String>,
+ parameters: Vec<(Vec<u8>, Vec<u8>)>,
},
}
@@ -287,75 +290,79 @@ impl Default for ContentType {
}
}
-impl PartialEq<&str> for ContentType {
- fn eq(&self, other: &&str) -> bool {
+impl PartialEq<&[u8]> for ContentType {
+ fn eq(&self, other: &&[u8]) -> bool {
match (self, *other) {
(
ContentType::Text {
kind: Text::Plain, ..
},
- "text/plain",
+ b"text/plain",
) => true,
(
ContentType::Text {
kind: Text::Html, ..
},
- "text/html",
+ b"text/html",
) => true,
(
ContentType::Multipart {
kind: MultipartType::Alternative,
..
},
- "multipart/alternative",
+ b"multipart/alternative",
) => true,
(
ContentType::Multipart {
kind: MultipartType::Digest,
..
},
- "multipart/digest",
+ b"multipart/digest",
) => true,
(
ContentType::Multipart {
kind: MultipartType::Encrypted,
..
},
- "multipart/encrypted",
+ b"multipart/encrypted",
) => true,
(
ContentType::Multipart {
kind: MultipartType::Mixed,
..
},
- "multipart/mixed",
+ b"multipart/mixed",
) => true,
(
ContentType::Multipart {
kind: MultipartType::Related,
..
},
- "multipart/related",
+ b"multipart/related",
) => true,
(
ContentType::Multipart {
kind: MultipartType::Signed,
..
},
- "multipart/signed",
+ b"multipart/signed",
) => true,
- (ContentType::PGPSignature, "application/pgp-signature") => true,
- (ContentType::CMSSignature, "application/pkcs7-signature") => true,
- (ContentType::MessageRfc822, "message/rfc822") => true,
- (ContentType::Other { tag, .. }, _) => {
- other.eq_ignore_ascii_case(&String::from_utf8_lossy(tag))
- }
- (ContentType::OctetStream { .. }, "application/octet-stream") => true,
+ (ContentType::PGPSignature, b"application/pgp-signature") => true,
+ (ContentType::CMSSignature, b"application/pkcs7-signature") => true,
+ (ContentType::MessageRfc822, b"message/rfc822") => true,
+ (ContentType::Other { tag, .. }, _) => other.eq_ignore_ascii_case(tag),
+ (ContentType::OctetStream { .. }, b"application/octet-stream") => true,
_ => false,
}
}
}
+impl PartialEq<&str> for ContentType {
+ fn eq(&self, other: &&str) -> bool {
+ self.eq(&other.as_bytes())
+ }
+}
+
impl Display for ContentType {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
@@ -424,7 +431,10 @@ impl ContentType {
pub fn name(&self) -> Option<&str> {
match self {
ContentType::Other { ref name, .. } => name.as_ref().map(|n| n.as_ref()),
- ContentType::OctetStream { ref name } => name.as_ref().map(|n| n.as_ref()),
+ ContentType::OctetStream {
+ ref name,
+ parameters: _,
+ } => name.as_ref().map(|n| n.as_ref()),
_ => None,
}
}
diff --git a/melib/src/email/attachments.rs b/melib/src/email/attachments.rs
index 723d4ed3..c02d1a60 100644
--- a/melib/src/email/attachments.rs
+++ b/melib/src/email/attachments.rs
@@ -142,7 +142,7 @@ impl AttachmentBuilder {
Ok((_, (ct, cst, params))) => {
if ct.eq_ignore_ascii_case(b"multipart") {
let mut boundary = None;
- for (n, v) in params {
+ for (n, v) in &params {
if n.eq_ignore_ascii_case(b"boundary") {
boundary = Some(v);
break;
@@ -155,6 +155,10 @@ impl AttachmentBuilder {
self.content_type = ContentType::Multipart {
boundary,
kind: MultipartType::from(cst),
+ parameters: params
+ .into_iter()
+ .map(|(kb, vb)| (kb.to_vec(), vb.to_vec()))
+ .collect::<Vec<(Vec<u8>, Vec<u8>)>>(),
parts,
};
} else {
@@ -208,7 +212,7 @@ impl AttachmentBuilder {
self.content_type = ContentType::CMSSignature;
} else {
let mut name: Option<String> = None;
- for (n, v) in params {
+ for (n, v) in &params {
if n.eq_ignore_ascii_case(b"name") {
if let Ok(v) = crate::email::parser::encodings::phrase(v.trim(), false)
.as_ref()
@@ -225,7 +229,14 @@ impl AttachmentBuilder {
tag.extend(ct);
tag.push(b'/');
tag.extend(cst);
- self.content_type = ContentType::Other { tag, name };
+ self.content_type = ContentType::Other {
+ tag,
+ name,
+ parameters: params
+ .into_iter()
+ .map(|(kb, vb)| (kb.to_vec(), vb.to_vec()))
+ .collect::<Vec<(Vec<u8>, Vec<u8>)>>(),
+ };
}
}
Err(e) => {
@@ -556,11 +567,18 @@ impl Attachment {
text.extend(self.decode(Default::default()));
}
ContentType::Multipart {
- ref kind,
+ kind: MultipartType::Related,
ref parts,
+ ref parameters,
..
- } => match kind {
- MultipartType::Alternative => {
+ } => {
+ if let Some(main_attachment) = parameters
+ .iter()
+ .find_map(|(k, v)| if k == b"type" { Some(v) } else { None })
+ .and_then(|t| parts.iter().find(|a| a.content_type == t.as_slice()))
+ {
+ main_attachment.get_text_recursive(text);
+ } else {
for a in parts {
if a.content_disposition.kind.is_inline() {
if let ContentType::Text {
@@ -573,14 +591,33 @@ impl Attachment {
}
}
}
- _ => {
- for a in parts {
- if a.content_disposition.kind.is_inline() {
+ }
+ ContentType::Multipart {
+ kind: MultipartType::Alternative,
+ ref parts,
+ ..
+ } => {
+ for a in parts {
+ if a.content_disposition.kind.is_inline() {
+ if let ContentType::Text {
+ kind: Text::Plain, ..
+ } = a.content_type
+ {
a.get_text_recursive(text);
+ break;
}
}
}
- },
+ }
+ ContentType::Multipart {
+ kind: _, ref parts, ..
+ } => {
+ for a in parts {
+ if a.content_disposition.kind.is_inline() {
+ a.get_text_recursive(text);
+ }
+ }
+ }
_ => {}
}
}
@@ -646,7 +683,10 @@ impl Attachment {
ref parts,
..
} => parts.iter().all(Attachment::is_html),
-
+ ContentType::Multipart {
+ kind: MultipartType::Related,
+ ..
+ } => false,
ContentType::Multipart { ref parts, .. } => parts.iter().any(Attachment::is_html),
_ => false,
}
@@ -709,12 +749,25 @@ impl Attachment {
boundary,
kind,
parts,
+ parameters,
} => {
let boundary = String::from_utf8_lossy(boundary);
ret.push_str(&format!("Content-Type: {}; boundary={}", kind, boundary));
if *kind == MultipartType::Signed {
ret.push_str("; micalg=pgp-sha512; protocol=\"application/pgp-signature\"");
}
+ for (n, v) in parameters {
+ ret.push_str("; ");
+ ret.push_str(&String::from_utf8_lossy(n));
+ ret.push('=');
+ if v.contains(&b' ') {
+ ret.push('"');
+ }
+ ret.push_str(&String::from_utf8_lossy(v));
+ if v.contains(&b' ') {
+ ret.push('"');
+ }
+ }
ret.push_str("\r\n");
let boundary_start = format!("\r\n--{}\r\n", boundary);
@@ -732,15 +785,25 @@ impl Attachment {
ret.push_str(&format!("Content-Type: {}\r\n\r\n", a.content_type));
ret.push_str(&String::from_utf8_lossy(a.body()));
}
- ContentType::OctetStream { ref name } => {
+ ContentType::OctetStream { name, parameters } => {
if let Some(name) = name {
- ret.push_str(&format!(
- "Content-Type: {}; name={}\r\n\r\n",
- a.content_type, name
- ));
+ ret.push_str(&format!("Content-Type: {}; name={}", a.content_type, name));
} else {
- ret.push_str(&format!("Content-Type: {}\r\n\r\n", a.content_type));
+ ret.push_str(&format!("Content-Type: {}", a.content_type));
+ }
+ for (n, v) in parameters {
+ ret.push_str("; ");
+ ret.push_str(&String::from_utf8_lossy(n));
+ ret.push('=');
+ if v.contains(&b' ') {
+ ret.push('"');
+ }
+ ret.push_str(&String::from_utf8_lossy(v));
+ if v.contains(&b' ') {
+ ret.push('"');
+ }
}
+ ret.push_str("\r\n\r\n");
ret.push_str(BASE64_MIME.encode(a.body()).trim());
}
_ => {
@@ -803,7 +866,10 @@ impl Attachment {
match self.content_type {
ContentType::Other { .. } => Vec::new(),
ContentType::Text { .. } => self.decode_helper(options),
- ContentType::OctetStream { ref name } => name
+ ContentType::OctetStream {
+ ref name,
+ parameters: _,
+ } => name
.clone()
.unwrap_or_else(|| self.mime_type())
.into_bytes(),
@@ -820,6 +886,7 @@ impl Attachment {
ContentType::Multipart {
ref kind,
ref parts,
+ parameters: _,
..
} => match kind {
MultipartType::Alternative => {
diff --git a/melib/src/email/compose.rs b/melib/src/email/compose.rs
index 41585515..924431b8 100644
--- a/melib/src/email/compose.rs
+++ b/melib/src/email/compose.rs
@@ -335,14 +335,19 @@ impl Draft {
parts.push(body_attachment);
}
parts.extend(attachments.into_iter());
- build_multipart(&mut ret, MultipartType::Mixed, parts);
+ build_multipart(&mut ret, MultipartType::Mixed, &[], parts);
}
Ok(ret)
}
}
-fn build_multipart(ret: &mut String, kind: MultipartType, parts: Vec<AttachmentBuilder>) {
+fn build_multipart(
+ ret: &mut String,
+ kind: MultipartType,
+ parameters: &[(Vec<u8>, Vec<u8>)],
+ parts: Vec<AttachmentBuilder>,
+) {
let boundary = ContentType::make_boundary(&parts);
ret.push_str(&format!(
r#"Content-Type: {}; charset="utf-8"; boundary="{}""#,
@@ -351,6 +356,18 @@ fn build_multipart(ret: &mut String, kind: MultipartType, parts: Vec<AttachmentB
if kind == MultipartType::Encrypted {
ret.push_str(r#"; protocol="application/pgp-encrypted""#);
}
+ for (n, v) in parameters {
+ ret.push_str("; ");
+ ret.push_str(&String::from_utf8_lossy(n));
+ ret.push('=');
+ if v.contains(&b' ') {
+ ret.push('"');
+ }
+ ret.push_str(&String::from_utf8_lossy(v));
+ if v.contains(&b' ') {
+ ret.push('"');
+ }
+ }
ret.push_str("\r\n\r\n");
/* rfc1341 */
ret.push_str("This is a MIME formatted message with attachments. Use a MIME-compliant client to view it properly.\r\n");
@@ -389,10 +406,12 @@ fn print_attachment(ret: &mut String, a: AttachmentBuilder) {
boundary: _,
kind,
parts,
+ parameters,
} => {
build_multipart(
ret,
kind,
+ &parameters,
parts
.into_iter()
.map(|s| s.into())
@@ -578,6 +597,7 @@ where
} else {
b"application/octet-stream".to_vec()
},
+ parameters: vec![],
});
Ok(attachment)
diff --git a/melib/src/email/pgp.rs b/melib/src/email/pgp.rs
index c28ac0b9..d17e7259 100644
--- a/melib/src/email/pgp.rs
+++ b/melib/src/email/pgp.rs
@@ -93,6 +93,7 @@ pub fn verify_signature(a: &Attachment) -> Result<(Vec<u8>, &Attachment)> {
kind: MultipartType::Signed,
ref parts,
boundary: _,
+ parameters: _,
} => {
if parts.len() != 2 {
return Err(Error::new(format!(
diff --git a/src/components/mail/compose.rs b/src/components/mail/compose.rs
index b2362964..331ce968 100644
--- a/src/components/mail/compose.rs
+++ b/src/components/mail/compose.rs
@@ -2305,9 +2305,10 @@ pub fn send_draft_async(
boundary: boundary.into_bytes(),
kind: MultipartType::Mixed,
parts: parts.into_iter().map(|a| a.into()).collect::<Vec<_>>(),
+ parameters: vec![],
},
Default::default(),
- Vec::new(),
+ vec![],
)
.into();
}
diff --git a/src/components/mail/pgp.rs b/src/components/mail/pgp.rs
index 7fce3f7f..ccdadff0 100644
--- a/src/components/mail/pgp.rs
+++ b/src/components/mail/pgp.rs
@@ -71,9 +71,10 @@ pub fn sign_filter(
boundary: boundary.into_bytes(),
kind: MultipartType::Signed,
parts: parts.into_iter().map(|a| a.into()).collect::<Vec<_>>(),
+ parameters: vec![],
},
Default::default(),
- Vec::new(),
+ vec![],
)
.into())
})
@@ -100,7 +101,7 @@ pub fn encrypt_filter(
let sig_attachment = {
let mut a = Attachment::new(
- ContentType::OctetStream { name: None },
+ ContentType::OctetStream { name: None, parameters: vec![] },
Default::default(),
ctx.encrypt(sign_keys, encrypt_keys, data)?.await?,
);
@@ -117,9 +118,10 @@ pub fn encrypt_filter(
boundary: boundary.into_bytes(),
kind: MultipartType::Encrypted,
parts: parts.into_iter().map(|a| a.into()).collect::<Vec<_>>(),
+ parameters: vec![],
},
Default::default(),
- Vec::new(),
+ vec![],
)
.into())
})
diff --git a/src/components/mail/view.rs b/src/components/mail/view.rs
index 84e56228..9efb2e4c 100644
--- a/src/components/mail/view.rs
+++ b/src/components/mail/view.rs
@@ -2263,7 +2263,10 @@ impl Component for MailView {
));
}
}
- ContentType::OctetStream { ref name } => {
+ ContentType::OctetStream {
+ ref name,
+ parameters: _,
+ } => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Failed to open {}. application/octet-stream isn't supported yet",