From 75b7bea78fd5eaff745a085db9bb915ec524d739 Mon Sep 17 00:00:00 2001 From: iequidoo Date: Tue, 8 Jul 2025 06:22:28 -0300 Subject: [PATCH] fix: Decide on filename used for sending depending on the original Viewtype If a user attaches an image as `File`, we should send the original filename. And vice versa, if it's `Image` originally, we mustn't reveal the filename. The filename used for sending is now also saved to the db, so all the sender's devices will display the same filename in the message info. --- src/blob.rs | 2 +- src/chat.rs | 81 ++++++++++++++++++++++++++++++------------ src/chat/chat_tests.rs | 9 ++--- src/mimefactory.rs | 52 +-------------------------- 4 files changed, 64 insertions(+), 80 deletions(-) diff --git a/src/blob.rs b/src/blob.rs index aedf5116c..7aedf9fba 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -499,7 +499,7 @@ impl<'a> BlobObject<'a> { if !is_avatar && no_exif { error!( context, - "Cannot recode image, using original data and file name: {err:#}.", + "Cannot recode image, using original data: {err:#}.", ); *viewtype = Viewtype::File; Ok(original_name) diff --git a/src/chat.rs b/src/chat.rs index 3c2a88d89..1cd53114f 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -10,6 +10,7 @@ use std::str::FromStr; use std::time::Duration; use anyhow::{Context as _, Result, anyhow, bail, ensure}; +use chrono::TimeZone; use deltachat_contact_tools::{ContactAddress, sanitize_bidi_characters, sanitize_single_line}; use deltachat_derive::{FromSql, ToSql}; use mail_builder::mime::MimePart; @@ -1958,7 +1959,6 @@ impl Chat { context: &Context, msg: &mut Message, update_msg_id: Option, - timestamp: i64, ) -> Result { let mut to_id = 0; let mut location_id = 0; @@ -1990,7 +1990,7 @@ impl Chat { msg.param.set_int(Param::AttachGroupImage, 1); self.param .remove(Param::Unpromoted) - .set_i64(Param::GroupNameTimestamp, timestamp); + .set_i64(Param::GroupNameTimestamp, msg.timestamp_sort); self.update_param(context).await?; // TODO: Remove this compat code needed because Core <= v1.143: // - doesn't accept synchronization of QR code tokens for unpromoted groups, so we also @@ -2085,7 +2085,7 @@ impl Chat { (timestamp,from_id,chat_id, latitude,longitude,independent)\ VALUES (?,?,?, ?,?,1);", ( - timestamp, + msg.timestamp_sort, ContactId::SELF, self.id, msg.param.get_float(Param::SetLatitude).unwrap_or_default(), @@ -2141,7 +2141,6 @@ impl Chat { msg.chat_id = self.id; msg.from_id = ContactId::SELF; - msg.timestamp_sort = timestamp; // add message to the database if let Some(update_msg_id) = update_msg_id { @@ -2682,6 +2681,7 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> { if msg.viewtype == Viewtype::Text || msg.viewtype == Viewtype::VideochatInvitation { // the caller should check if the message text is empty } else if msg.viewtype.has_file() { + let viewtype_orig = msg.viewtype; let mut blob = msg .param .get_file_blob(context)? @@ -2749,6 +2749,52 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> { msg.try_calc_and_set_dimensions(context).await?; + let filename = msg.get_filename().context("msg has no file")?; + let suffix = Path::new(&filename) + .extension() + .and_then(|e| e.to_str()) + .unwrap_or("dat"); + // Get file name to use for sending. For privacy purposes, we do not transfer the original + // filenames e.g. for images; these names are normally not needed and contain timestamps, + // running numbers, etc. + let filename: String = match viewtype_orig { + Viewtype::Voice => format!( + "voice-messsage_{}.{}", + chrono::Utc + .timestamp_opt(msg.timestamp_sort, 0) + .single() + .map_or_else( + || "YY-mm-dd_hh:mm:ss".to_string(), + |ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string() + ), + &suffix + ), + Viewtype::Image | Viewtype::Gif => format!( + "image_{}.{}", + chrono::Utc + .timestamp_opt(msg.timestamp_sort, 0) + .single() + .map_or_else( + || "YY-mm-dd_hh:mm:ss".to_string(), + |ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string(), + ), + &suffix, + ), + Viewtype::Video => format!( + "video_{}.{}", + chrono::Utc + .timestamp_opt(msg.timestamp_sort, 0) + .single() + .map_or_else( + || "YY-mm-dd_hh:mm:ss".to_string(), + |ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string() + ), + &suffix + ), + _ => filename, + }; + msg.param.set(Param::Filename, filename); + info!( context, "Attaching \"{}\" for message type #{}.", @@ -2901,18 +2947,12 @@ async fn prepare_send_msg( // ... then change the MessageState in the message object msg.state = MessageState::OutPending; + msg.timestamp_sort = create_smeared_timestamp(context); prepare_msg_blob(context, msg).await?; if !msg.hidden { chat_id.unarchive_if_not_muted(context, msg.state).await?; } - msg.id = chat - .prepare_msg_raw( - context, - msg, - update_msg_id, - create_smeared_timestamp(context), - ) - .await?; + msg.id = chat.prepare_msg_raw(context, msg, update_msg_id).await?; msg.chat_id = chat_id; let row_ids = create_send_msg_jobs(context, msg) @@ -4307,9 +4347,8 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) msg.state = MessageState::OutPending; msg.rfc724_mid = create_outgoing_rfc724_mid(); - let new_msg_id = chat - .prepare_msg_raw(context, &mut msg, None, curr_timestamp) - .await?; + msg.timestamp_sort = curr_timestamp; + let new_msg_id = chat.prepare_msg_raw(context, &mut msg, None).await?; curr_timestamp += 1; if !create_send_msg_jobs(context, &mut msg).await?.is_empty() { @@ -4559,19 +4598,17 @@ pub async fn add_device_msg_with_importance( chat_id = ChatId::get_for_contact(context, ContactId::DEVICE).await?; let rfc724_mid = create_outgoing_rfc724_mid(); - prepare_msg_blob(context, msg).await?; - let timestamp_sent = create_smeared_timestamp(context); // makes sure, the added message is the last one, // even if the date is wrong (useful esp. when warning about bad dates) - let mut timestamp_sort = timestamp_sent; + msg.timestamp_sort = timestamp_sent; if let Some(last_msg_time) = chat_id.get_timestamp(context).await? { - if timestamp_sort <= last_msg_time { - timestamp_sort = last_msg_time + 1; + if msg.timestamp_sort <= last_msg_time { + msg.timestamp_sort = last_msg_time + 1; } } - + prepare_msg_blob(context, msg).await?; let state = MessageState::InFresh; let row_id = context .sql @@ -4593,7 +4630,7 @@ pub async fn add_device_msg_with_importance( chat_id, ContactId::DEVICE, ContactId::SELF, - timestamp_sort, + msg.timestamp_sort, timestamp_sent, timestamp_sent, // timestamp_sent equals timestamp_rcvd msg.viewtype, diff --git a/src/chat/chat_tests.rs b/src/chat/chat_tests.rs index f21168dde..63688a2a9 100644 --- a/src/chat/chat_tests.rs +++ b/src/chat/chat_tests.rs @@ -1962,12 +1962,7 @@ async fn test_sticker( let msg = bob.recv_msg(&sent_msg).await; assert_eq!(msg.chat_id, bob_chat.id); assert_eq!(msg.get_viewtype(), res_viewtype); - let msg_filename = msg.get_filename().unwrap(); - match res_viewtype { - Viewtype::Sticker => assert_eq!(msg_filename, filename), - Viewtype::Image => assert!(msg_filename.starts_with("image_")), - _ => panic!("Not implemented"), - } + assert_eq!(msg.get_filename().unwrap(), filename); assert_eq!(msg.get_width(), w); assert_eq!(msg.get_height(), h); assert!(msg.get_filebytes(&bob).await?.unwrap() > 250); @@ -3739,9 +3734,11 @@ async fn test_nonimage_with_png_ext() -> Result<()> { let sent_msg = alice.send_msg(alice_chat.get_id(), &mut msg).await; assert_eq!(msg.viewtype, Viewtype::File); assert_eq!(msg.get_filemime().unwrap(), "application/octet-stream"); + assert!(!msg.get_filename().unwrap().contains("screenshot")); let msg_bob = bob.recv_msg(&sent_msg).await; assert_eq!(msg_bob.viewtype, Viewtype::File); assert_eq!(msg_bob.get_filemime().unwrap(), "application/octet-stream"); + assert!(!msg_bob.get_filename().unwrap().contains("screenshot")); Ok(()) } diff --git a/src/mimefactory.rs b/src/mimefactory.rs index bdc5f49ab..8baa0ded9 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -2,11 +2,9 @@ use std::collections::{BTreeSet, HashSet}; use std::io::Cursor; -use std::path::Path; use anyhow::{Context as _, Result, bail, ensure}; use base64::Engine as _; -use chrono::TimeZone; use deltachat_contact_tools::sanitize_bidi_characters; use mail_builder::headers::HeaderType; use mail_builder::headers::address::{Address, EmailAddress}; @@ -1770,57 +1768,10 @@ fn hidden_recipients() -> Address<'static> { async fn build_body_file(context: &Context, msg: &Message) -> Result> { let file_name = msg.get_filename().context("msg has no file")?; - let suffix = Path::new(&file_name) - .extension() - .and_then(|e| e.to_str()) - .unwrap_or("dat"); - let blob = msg .param .get_file_blob(context)? .context("msg has no file")?; - - // Get file name to use for sending. For privacy purposes, we do - // not transfer the original filenames eg. for images; these names - // are normally not needed and contain timestamps, running numbers - // etc. - let filename_to_send: String = match msg.viewtype { - Viewtype::Voice => format!( - "voice-messsage_{}.{}", - chrono::Utc - .timestamp_opt(msg.timestamp_sort, 0) - .single() - .map_or_else( - || "YY-mm-dd_hh:mm:ss".to_string(), - |ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string() - ), - &suffix - ), - Viewtype::Image | Viewtype::Gif => format!( - "image_{}.{}", - chrono::Utc - .timestamp_opt(msg.timestamp_sort, 0) - .single() - .map_or_else( - || "YY-mm-dd_hh:mm:ss".to_string(), - |ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string(), - ), - &suffix, - ), - Viewtype::Video => format!( - "video_{}.{}", - chrono::Utc - .timestamp_opt(msg.timestamp_sort, 0) - .single() - .map_or_else( - || "YY-mm-dd_hh:mm:ss".to_string(), - |ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string() - ), - &suffix - ), - _ => file_name, - }; - let mimetype = msg .param .get(Param::MimeType) @@ -1833,8 +1784,7 @@ async fn build_body_file(context: &Context, msg: &Message) -> Result