forward html-messages properly (#2151)

* add failing tests for forwarding html-mails

* let MsgId.get_html() return an option

* write html-part to local database on forwarding

* add html-part to forwarded non-dc messages

* read HTML-parts from encrypted messages

* avoid clone()

* Received:-header is no longer needed since #2152

* Update src/html.rs

Co-authored-by: Floris Bruynooghe <flub@devork.be>

* Update src/html.rs

Co-authored-by: Floris Bruynooghe <flub@devork.be>

* Update src/mimeparser.rs

Co-authored-by: Floris Bruynooghe <flub@devork.be>

* prefer 'orig' over 'org' as abbreviation for 'original'

* improve comment on tests

* prefer 'try_into()' over 'as u32' to avoid panics on bad data

* simplify ffi

Co-authored-by: Floris Bruynooghe <flub@devork.be>
This commit is contained in:
bjoern
2021-01-23 23:21:18 +01:00
committed by GitHub
parent 2a8c418d54
commit e9c582c4e4
8 changed files with 183 additions and 19 deletions

View File

@@ -18,6 +18,7 @@ use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::message::{Message, MsgId};
use crate::mimeparser::parse_message_id;
use crate::plaintext::PlainText;
use lettre_email::PartBuilder;
use mailparse::ParsedContentType;
impl Message {
@@ -221,7 +222,7 @@ impl MsgId {
/// this is the case at least when `Message.has_html()` returns true
/// (we do not save raw mime unconditionally in the database to save space).
/// The corresponding ffi-function is `dc_get_msg_html()`.
pub async fn get_html(self, context: &Context) -> String {
pub async fn get_html(self, context: &Context) -> Option<String> {
let rawmime: Option<String> = context
.sql
.query_get_value(
@@ -232,19 +233,52 @@ impl MsgId {
.await;
if let Some(rawmime) = rawmime {
match HtmlMsgParser::from_bytes(context, rawmime.as_bytes()).await {
Err(err) => format!("parser error: {}", err),
Ok(parser) => parser.html,
if !rawmime.is_empty() {
match HtmlMsgParser::from_bytes(context, rawmime.as_bytes()).await {
Err(err) => {
warn!(context, "get_html: parser error: {}", err);
None
}
Ok(parser) => Some(parser.html),
}
} else {
warn!(context, "get_html: empty mime for {}", self);
None
}
} else {
format!("parser error: no mime for {}", self)
warn!(context, "get_html: no mime for {}", self);
None
}
}
/// Wraps HTML generated by [`MsgId::get_html`] into a text/html mimepart structure.
///
/// Used on forwarding messages to avoid leaking the original mime structure
/// and also to avoid sending too much, maybe large data.
pub async fn get_html_as_mimepart(self, context: &Context) -> Option<PartBuilder> {
self.get_html(context).await.map(|s| {
PartBuilder::new()
.content_type(&"text/html; charset=utf-8".parse::<mime::Mime>().unwrap())
.body(s)
})
}
// As [`MsgId::get_html_as_mimepart`] but wraps [`MsgId::get_html`] into text/html mime raw string.
pub async fn get_html_as_rawmime(self, context: &Context) -> Option<String> {
self.get_html_as_mimepart(context)
.await
.map(|p| p.build().as_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chat::forward_msgs;
use crate::config::Config;
use crate::constants::DC_CONTACT_ID_SELF;
use crate::dc_receive_imf::dc_receive_imf;
use crate::message::MessengerMessage;
use crate::test_utils::TestContext;
#[async_std::test]
@@ -393,4 +427,96 @@ test some special html-characters as &lt; &gt; and &amp; but also &quot; and &#x
.is_some());
assert!(parser.html.find("cid:").is_none());
}
#[async_std::test]
async fn test_get_html_empty() {
let t = TestContext::new().await;
let msg_id = MsgId::new_unset();
assert!(msg_id.get_html(&t).await.is_none())
}
#[async_std::test]
async fn test_html_forwarding() {
// alice receives a non-delta html-message
let alice = TestContext::new_alice().await;
alice.set_config(Config::ShowEmails, Some("2")).await.ok();
let chat = alice.chat_with_contact("", "sender@testrun.org").await;
let raw = include_bytes!("../test-data/message/text_alt_plain_html.eml");
dc_receive_imf(&alice, raw, "INBOX", 1, false)
.await
.unwrap();
let msg = alice.get_last_msg_in(chat.get_id()).await;
assert_ne!(msg.get_from_id(), DC_CONTACT_ID_SELF);
assert_eq!(msg.is_dc_message, MessengerMessage::No);
assert!(!msg.is_forwarded());
assert!(msg.get_text().unwrap().find("this is plain").is_some());
assert!(msg.has_html());
let html = msg.get_id().get_html(&alice).await.unwrap();
assert!(html.find("this is <b>html</b>").is_some());
// alice: create chat with bob and forward received html-message there
let chat = alice.chat_with_contact("", "bob@example.net").await;
forward_msgs(&alice, &[msg.get_id()], chat.get_id())
.await
.unwrap();
let msg = alice.get_last_msg_in(chat.get_id()).await;
assert_eq!(msg.get_from_id(), DC_CONTACT_ID_SELF);
assert_eq!(msg.is_dc_message, MessengerMessage::Yes);
assert!(msg.is_forwarded());
assert!(msg.get_text().unwrap().find("this is plain").is_some());
assert!(msg.has_html());
let html = msg.get_id().get_html(&alice).await.unwrap();
assert!(html.find("this is <b>html</b>").is_some());
// bob: check that bob also got the html-part of the forwarded message
let bob = TestContext::new_bob().await;
let chat = bob.chat_with_contact("", "alice@example.com").await;
bob.recv_msg(&alice.pop_sent_msg().await).await;
let msg = bob.get_last_msg_in(chat.get_id()).await;
assert_ne!(msg.get_from_id(), DC_CONTACT_ID_SELF);
assert_eq!(msg.is_dc_message, MessengerMessage::Yes);
assert!(msg.is_forwarded());
assert!(msg.get_text().unwrap().find("this is plain").is_some());
assert!(msg.has_html());
let html = msg.get_id().get_html(&bob).await.unwrap();
assert!(html.find("this is <b>html</b>").is_some());
}
#[async_std::test]
async fn test_html_forwarding_encrypted() {
// Alice receives a non-delta html-message
// (`ShowEmails=1` lets Alice actually receive non-delta messages for known contacts,
// the contact is marked as known by creating a chat using `chat_with_contact()`)
let alice = TestContext::new_alice().await;
alice.set_config(Config::ShowEmails, Some("1")).await.ok();
let chat = alice.chat_with_contact("", "sender@testrun.org").await;
let raw = include_bytes!("../test-data/message/text_alt_plain_html.eml");
dc_receive_imf(&alice, raw, "INBOX", 1, false)
.await
.unwrap();
let msg = alice.get_last_msg_in(chat.get_id()).await;
// forward the message to saved-messages,
// this will encrypt the message as new_alice() has set up keys
let chat = alice.get_self_chat().await;
forward_msgs(&alice, &[msg.get_id()], chat.get_id())
.await
.unwrap();
let msg = alice.pop_sent_msg().await;
// receive the message on another device
let alice = TestContext::new_alice().await;
assert_eq!(alice.get_config_int(Config::ShowEmails).await, 0); // set to "1" above, make sure it is another db
alice.recv_msg(&msg).await;
let chat = alice.get_self_chat().await;
let msg = alice.get_last_msg_in(chat.get_id()).await;
assert_eq!(msg.get_from_id(), DC_CONTACT_ID_SELF);
assert_eq!(msg.is_dc_message, MessengerMessage::Yes);
assert!(msg.get_showpadlock());
assert!(msg.is_forwarded());
assert!(msg.get_text().unwrap().find("this is plain").is_some());
assert!(msg.has_html());
let html = msg.get_id().get_html(&alice).await.unwrap();
assert!(html.find("this is <b>html</b>").is_some());
}
}