Fix multipart/related with main text/html part not displayed correctly

sieve
Manos Pitsidianakis 2022-12-09 14:06:20 +02:00
parent f63ce388f7
commit 40c6647db8
7 changed files with 147 additions and 43 deletions

View File

@ -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,
}
}

View File

@ -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 => {

View File

@ -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)

View File

@ -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!(

View File

@ -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();
}

View File

@ -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())
})

View File

@ -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",