mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
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:
136
src/html.rs
136
src/html.rs
@@ -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 < > and & but also " 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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user