add dc_msg_set_html() api (#2153)

* draft dc_msg_set_html() api

* implement setting 'html to be send'

* test sending html-parts

* more flexible html-partbuilder

* write html-parts to database and also send them

* add 'sendhtml' command to repl tool
This commit is contained in:
bjoern
2021-01-27 12:56:22 +01:00
committed by GitHub
parent c8c2724c28
commit 1e6d8063c8
8 changed files with 146 additions and 30 deletions

View File

@@ -30,6 +30,7 @@ use crate::dc_tools::{
};
use crate::ephemeral::{delete_expired_messages, schedule_ephemeral_task, Timer as EphemeralTimer};
use crate::events::EventType;
use crate::html::new_html_mimepart;
use crate::job::{self, Action};
use crate::message::{self, InvalidMsgId, Message, MessageState, MsgId};
use crate::mimeparser::SystemMessage;
@@ -1060,8 +1061,17 @@ impl Chat {
EphemeralTimer::Enabled { duration } => time() + i64::from(duration),
};
let new_mime_headers = if msg.param.exists(Param::Forwarded) && msg.mime_modified {
msg.get_id().get_html_as_rawmime(context).await
let new_mime_headers = if msg.has_html() {
let html = if msg.param.exists(Param::Forwarded) {
msg.get_id().get_html(context).await
} else {
msg.param.get(Param::SendHtml).map(|s| s.to_string())
};
if let Some(html) = html {
Some(new_html_mimepart(html).await.build().as_string())
} else {
None
}
} else {
None
};

View File

@@ -17,6 +17,7 @@ use crate::context::Context;
use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::message::{Message, MsgId};
use crate::mimeparser::parse_message_id;
use crate::param::Param::SendHtml;
use crate::plaintext::PlainText;
use lettre_email::PartBuilder;
use mailparse::ParsedContentType;
@@ -31,6 +32,24 @@ impl Message {
pub fn has_html(&self) -> bool {
self.mime_modified
}
/// Set HTML-part part of a message that is about to be sent.
/// The HTML-part is written to the database before sending and
/// used as the `text/html` part in the MIME-structure.
///
/// Received HTML parts are handled differently,
/// they are saved together with the whole MIME-structure
/// in `mime_headers` and the HTML-part is extracted using `MsgId::get_html()`.
/// (To underline this asynchronicity, we are using the wording "SendHtml")
pub fn set_html(&mut self, html: Option<String>) {
if let Some(html) = html {
self.param.set(SendHtml, html);
self.mime_modified = true;
} else {
self.param.remove(SendHtml);
self.mime_modified = false;
}
}
}
/// Type defining a rough mime-type.
@@ -250,33 +269,25 @@ impl MsgId {
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())
}
/// Wraps HTML text into a new 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 new_html_mimepart(html: String) -> PartBuilder {
PartBuilder::new()
.content_type(&"text/html; charset=utf-8".parse::<mime::Mime>().unwrap())
.body(html)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chat;
use crate::chat::forward_msgs;
use crate::config::Config;
use crate::constants::DC_CONTACT_ID_SELF;
use crate::constants::{Viewtype, DC_CONTACT_ID_SELF};
use crate::dc_receive_imf::dc_receive_imf;
use crate::message::MessengerMessage;
use crate::test_utils::TestContext;
@@ -519,4 +530,36 @@ test some special html-characters as &lt; &gt; and &amp; but also &quot; and &#x
let html = msg.get_id().get_html(&alice).await.unwrap();
assert!(html.find("this is <b>html</b>").is_some());
}
#[async_std::test]
async fn test_set_html() {
let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await;
// alice sends a message with html-part to bob
let chat_id = alice.create_chat(&bob).await.id;
let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some("plain text".to_string()));
msg.set_html(Some("<b>html</b> text".to_string()));
assert!(msg.mime_modified);
chat::send_msg(&alice, chat_id, &mut msg).await.unwrap();
// check the message is written correctly to alice's db
let msg = alice.get_last_msg_in(chat_id).await;
assert_eq!(msg.get_text(), Some("plain text".to_string()));
assert!(!msg.is_forwarded());
assert!(msg.mime_modified);
let html = msg.get_id().get_html(&alice).await.unwrap();
assert!(html.find("<b>html</b> text").is_some());
// let bob receive the message
let chat_id = bob.create_chat(&alice).await.id;
bob.recv_msg(&alice.pop_sent_msg().await).await;
let msg = bob.get_last_msg_in(chat_id).await;
assert_eq!(msg.get_text(), Some("plain text".to_string()));
assert!(!msg.is_forwarded());
assert!(msg.mime_modified);
let html = msg.get_id().get_html(&bob).await.unwrap();
assert!(html.find("<b>html</b> text").is_some());
}
}

View File

@@ -14,6 +14,7 @@ use crate::dc_tools::{
use crate::e2ee::EncryptHelper;
use crate::ephemeral::Timer as EphemeralTimer;
use crate::format_flowed::{format_flowed, format_flowed_quote};
use crate::html::new_html_mimepart;
use crate::location;
use crate::message::{self, Message, MsgId};
use crate::mimeparser::SystemMessage;
@@ -966,14 +967,16 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
// add HTML-part, this is needed only if a HTML-message from a non-delta-client is forwarded;
// for simplificity and to avoid conversion errors, we're generating the HTML-part from the original message.
if self.msg.has_html() {
if let Some(orig_msg_id) = self.msg.param.get_int(Param::Forwarded) {
let orig_msg_id = MsgId::new(orig_msg_id.try_into()?);
if let Some(html_part) = orig_msg_id.get_html_as_mimepart(context).await {
main_part = PartBuilder::new()
.message_type(MimeMultipartType::Alternative)
.child(main_part.build())
.child(html_part.build());
}
let html = if let Some(orig_msg_id) = self.msg.param.get_int(Param::Forwarded) {
MsgId::new(orig_msg_id.try_into()?).get_html(context).await
} else {
self.msg.param.get(Param::SendHtml).map(|s| s.to_string())
};
if let Some(html) = html {
main_part = PartBuilder::new()
.message_type(MimeMultipartType::Alternative)
.child(main_part.build())
.child(new_html_mimepart(html).await.build());
}
}

View File

@@ -34,6 +34,11 @@ pub enum Param {
/// For Messages
MimeType = b'm',
/// For Messages: HTML to be written to the database and to be send.
/// `SendHtml` param is not used for received messages.
/// Use `MsgId::get_html()` to get HTML of received messages.
SendHtml = b'T',
/// For Messages: message is encrypted, outgoing: guarantee E2EE or the message is not send
GuaranteeE2ee = b'c',