Skip to content

Commit 57ec371

Browse files
committed
fix: Cross-profile forwarding of messages with has_html() returning true (#7791)
This includes forwarding of long messages. Also this fixes sending, but more likely resending of forwarded messages for which the original message was deleted, because now we save HTML to the db immediately when creating a forwarded message, not when actually sending it.
1 parent 2194f6a commit 57ec371

4 files changed

Lines changed: 73 additions & 36 deletions

File tree

src/chat.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::io::Cursor;
77
use std::marker::Sync;
88
use std::path::{Path, PathBuf};
99
use std::str::FromStr;
10+
use std::sync::Arc;
1011
use std::time::Duration;
1112

1213
use anyhow::{Context as _, Result, anyhow, bail, ensure};
@@ -1886,11 +1887,7 @@ impl Chat {
18861887

18871888
let (msg_text, was_truncated) = truncate_msg_text(context, msg.text.clone()).await?;
18881889
let new_mime_headers = if msg.has_html() {
1889-
if msg.param.exists(Param::Forwarded) {
1890-
msg.get_id().get_html(context).await?
1891-
} else {
1892-
msg.param.get(Param::SendHtml).map(|s| s.to_string())
1893-
}
1890+
msg.param.get(Param::SendHtml).map(|s| s.to_string())
18941891
} else {
18951892
None
18961893
};
@@ -4367,8 +4364,12 @@ pub async fn forward_msgs_2ctx(
43674364
msg.param = Params::new();
43684365

43694366
if msg.get_viewtype() != Viewtype::Sticker {
4367+
let forwarded_msg_id = match Arc::ptr_eq(&ctx_src.inner, &ctx_dst.inner) {
4368+
true => src_msg_id,
4369+
false => MsgId::new_unset(),
4370+
};
43704371
msg.param
4371-
.set_int(Param::Forwarded, src_msg_id.to_u32() as i32);
4372+
.set_int(Param::Forwarded, forwarded_msg_id.to_u32() as i32);
43724373
}
43734374

43744375
if msg.get_viewtype() == Viewtype::Call {
@@ -4395,6 +4396,9 @@ pub async fn forward_msgs_2ctx(
43954396
msg.param.steal(param, Param::ProtectQuote);
43964397
msg.param.steal(param, Param::Quote);
43974398
msg.param.steal(param, Param::Summary1);
4399+
if msg.has_html() {
4400+
msg.set_html(src_msg_id.get_html(ctx_src).await?);
4401+
}
43984402
msg.in_reply_to = None;
43994403

44004404
// do not leak data as group names; a default subject is generated by mimefactory

src/html.rs

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,14 @@ fn mimepart_to_data_url(mail: &mailparse::ParsedMail<'_>) -> Result<String> {
254254

255255
impl MsgId {
256256
/// Get HTML by database message id.
257-
/// This requires `mime_headers` field to be set for the message;
258-
/// this is the case at least when `Message.has_html()` returns true
259-
/// (we do not save raw mime unconditionally in the database to save space).
257+
/// Returns `Some` at least when `Message.has_html()` is true.
258+
/// NB: we do not save raw mime unconditionally in the database to save space.
260259
/// The corresponding ffi-function is `dc_get_msg_html()`.
261260
pub async fn get_html(self, context: &Context) -> Result<Option<String>> {
261+
let param = self.get_param(context).await?;
262+
if let Some(html) = param.get(SendHtml) {
263+
return Ok(Some(html.to_string()));
264+
}
262265
let rawmime = message::get_mime_headers(context, self).await?;
263266

264267
if !rawmime.is_empty() {
@@ -279,9 +282,9 @@ impl MsgId {
279282
#[cfg(test)]
280283
mod tests {
281284
use super::*;
282-
use crate::chat;
283-
use crate::chat::{forward_msgs, save_msgs};
285+
use crate::chat::{self, Chat, forward_msgs, save_msgs};
284286
use crate::config::Config;
287+
use crate::constants;
285288
use crate::contact::ContactId;
286289
use crate::message::{MessengerMessage, Viewtype};
287290
use crate::receive_imf::receive_imf;
@@ -440,7 +443,7 @@ test some special html-characters as &lt; &gt; and &amp; but also &quot; and &#x
440443
}
441444

442445
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
443-
async fn test_html_forwarding() {
446+
async fn test_html_forwarding() -> Result<()> {
444447
// alice receives a non-delta html-message
445448
let mut tcm = TestContextManager::new();
446449
let alice = &tcm.alice().await;
@@ -459,31 +462,57 @@ test some special html-characters as &lt; &gt; and &amp; but also &quot; and &#x
459462
assert!(html.contains("this is <b>html</b>"));
460463

461464
// alice: create chat with bob and forward received html-message there
462-
let chat = alice.create_chat_with_contact("", "bob@example.net").await;
463-
forward_msgs(alice, &[msg.get_id()], chat.get_id())
465+
let chat_alice = alice.create_chat_with_contact("", "bob@example.net").await;
466+
forward_msgs(alice, &[msg.get_id()], chat_alice.get_id())
464467
.await
465468
.unwrap();
466-
let msg = alice.get_last_msg_in(chat.get_id()).await;
467-
assert_eq!(msg.get_from_id(), ContactId::SELF);
468-
assert_eq!(msg.is_dc_message, MessengerMessage::Yes);
469-
assert!(msg.is_forwarded());
470-
assert!(msg.get_text().contains("this is plain"));
471-
assert!(msg.has_html());
472-
let html = msg.get_id().get_html(alice).await.unwrap().unwrap();
473-
assert!(html.contains("this is <b>html</b>"));
469+
async fn check_sender(ctx: &TestContext, chat: &Chat) {
470+
let msg = ctx.get_last_msg_in(chat.get_id()).await;
471+
assert_eq!(msg.get_from_id(), ContactId::SELF);
472+
assert_eq!(msg.is_dc_message, MessengerMessage::Yes);
473+
assert!(msg.is_forwarded());
474+
assert!(msg.get_text().contains("this is plain"));
475+
assert!(msg.has_html());
476+
let html = msg.get_id().get_html(ctx).await.unwrap().unwrap();
477+
assert!(html.contains("this is <b>html</b>"));
478+
}
479+
check_sender(alice, &chat_alice).await;
474480

475481
// bob: check that bob also got the html-part of the forwarded message
476482
let bob = &tcm.bob().await;
477-
let chat = bob.create_chat_with_contact("", "alice@example.org").await;
478-
let msg = bob.recv_msg(&alice.pop_sent_msg().await).await;
479-
assert_eq!(chat.id, msg.chat_id);
480-
assert_ne!(msg.get_from_id(), ContactId::SELF);
481-
assert_eq!(msg.is_dc_message, MessengerMessage::Yes);
482-
assert!(msg.is_forwarded());
483-
assert!(msg.get_text().contains("this is plain"));
483+
let chat_bob = bob.create_chat_with_contact("", "alice@example.org").await;
484+
async fn check_receiver(ctx: &TestContext, chat: &Chat, sender: &TestContext) {
485+
let msg = ctx.recv_msg(&sender.pop_sent_msg().await).await;
486+
assert_eq!(chat.id, msg.chat_id);
487+
assert_ne!(msg.get_from_id(), ContactId::SELF);
488+
assert_eq!(msg.is_dc_message, MessengerMessage::Yes);
489+
assert!(msg.is_forwarded());
490+
assert!(msg.get_text().contains("this is plain"));
491+
assert!(msg.has_html());
492+
let html = msg.get_id().get_html(ctx).await.unwrap().unwrap();
493+
assert!(html.contains("this is <b>html</b>"));
494+
}
495+
check_receiver(bob, &chat_bob, alice).await;
496+
497+
// Finally it appears that alice is bob, so alice can forward the message to herself via
498+
// "bob" profile!
499+
chat::forward_msgs_2ctx(alice, &[msg.get_id()], bob, chat_bob.get_id()).await?;
500+
check_sender(bob, &chat_bob).await;
501+
check_receiver(alice, &chat_alice, bob).await;
502+
503+
// Check cross-profile forwarding of long outgoing messages.
504+
let line = "this text with 42 chars is just repeated.\n";
505+
let long_txt = line.repeat(constants::DC_DESIRED_TEXT_LEN / line.len() + 2);
506+
let mut msg = Message::new_text(long_txt);
507+
alice.send_msg(chat_alice.id, &mut msg).await;
508+
let msg = alice.get_last_msg_in(chat_alice.id).await;
484509
assert!(msg.has_html());
485-
let html = msg.get_id().get_html(bob).await.unwrap().unwrap();
486-
assert!(html.contains("this is <b>html</b>"));
510+
let html = msg.id.get_html(alice).await?.unwrap();
511+
chat::forward_msgs_2ctx(alice, &[msg.get_id()], bob, chat_bob.get_id()).await?;
512+
let msg = bob.get_last_msg_in(chat_bob.id).await;
513+
assert!(msg.has_html());
514+
assert_eq!(msg.id.get_html(bob).await?.unwrap(), html);
515+
Ok(())
487516
}
488517

489518
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]

src/message.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,7 @@ impl Message {
980980

981981
/// Returns true if the message is a forwarded message.
982982
pub fn is_forwarded(&self) -> bool {
983-
0 != self.param.get_int(Param::Forwarded).unwrap_or_default()
983+
self.param.get_int(Param::Forwarded).is_some()
984984
}
985985

986986
/// Returns true if the message is edited.

src/mimefactory.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,15 +1934,19 @@ impl MimeFactory {
19341934

19351935
let mut parts = Vec::new();
19361936

1937-
// add HTML-part, this is needed only if a HTML-message from a non-delta-client is forwarded;
1938-
// for simplificity and to avoid conversion errors, we're generating the HTML-part from the original message.
19391937
if msg.has_html() {
1940-
let html = if let Some(orig_msg_id) = msg.param.get_int(Param::Forwarded) {
1938+
let html = if let Some(html) = msg.param.get(Param::SendHtml) {
1939+
Some(html.to_string())
1940+
} else if let Some(orig_msg_id) = msg.param.get_int(Param::Forwarded)
1941+
&& orig_msg_id != 0
1942+
{
1943+
// Legacy forwarded messages may not have `Param::SendHtml` set. Let's hope the
1944+
// original message exists.
19411945
MsgId::new(orig_msg_id.try_into()?)
19421946
.get_html(context)
19431947
.await?
19441948
} else {
1945-
msg.param.get(Param::SendHtml).map(|s| s.to_string())
1949+
None
19461950
};
19471951
if let Some(html) = html {
19481952
main_part = MimePart::new(

0 commit comments

Comments
 (0)