diff --git a/CHANGELOG.md b/CHANGELOG.md index 60ebbbb03..45d4c3006 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ - keep track of securejoin joiner status in database to survive restarts #2920 - remove never used `SentboxMove` option #3111 +### Fixes +- Fix a bug where sometimes the file extension of a long filename containing a dot was cropped #3098 ## 1.76.0 diff --git a/src/blob.rs b/src/blob.rs index 0051327e0..9a42edf9f 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -345,13 +345,30 @@ impl<'a> BlobObject<'a> { }; let clean = sanitize_filename::sanitize_with_options(name, opts); + // Let's take the tricky filename + // "file.with_lots_of_characters_behind_point_and_double_ending.tar.gz" as an example. + // Split it into "file" and "with_lots_of_characters_behind_point_and_double_ending.tar.gz": let mut iter = clean.splitn(2, '.'); + let stem: String = iter.next().unwrap_or_default().chars().take(64).collect(); - let ext: String = iter.next().unwrap_or_default().chars().take(32).collect(); + // stem == "file" + + let ext_chars = iter.next().unwrap_or_default().chars(); + let ext: String = ext_chars + .rev() + .take(32) + .collect::>() + .iter() + .rev() + .collect(); + // ext == "d_point_and_double_ending.tar.gz" + if ext.is_empty() { (stem, "".to_string()) } else { (stem, format!(".{}", ext).to_lowercase()) + // Return ("file", ".d_point_and_double_ending.tar.gz") + // which is not perfect but acceptable. } } diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 8b359812e..c1ac86893 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -2333,6 +2333,9 @@ async fn add_or_lookup_contact_by_addr( #[cfg(test)] mod tests { + use async_std::fs::{self, File}; + use async_std::io::WriteExt; + use super::*; use crate::chat::get_chat_contacts; @@ -2340,7 +2343,7 @@ mod tests { use crate::chatlist::Chatlist; use crate::constants::{DC_CONTACT_ID_INFO, DC_GCL_NO_SPECIALS}; use crate::message::Message; - use crate::test_utils::{get_chat_msg, TestContext}; + use crate::test_utils::{get_chat_msg, TestContext, TestContextManager}; #[test] fn test_hex_hash() { @@ -4968,4 +4971,59 @@ Reply from different address Ok(()) } + + #[async_std::test] + async fn test_long_filenames() -> Result<()> { + let mut tcm = TestContextManager::new().await; + let alice = tcm.alice().await; + let bob = tcm.bob().await; + + for filename_sent in &[ + "foo.bar very long file name test baz.tar.gz", + "foobarabababababababbababababverylongfilenametestbaz.tar.gz", + "fooo...tar.gz", + "foo. .tar.gz", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.tar.gz", + "a.tar.gz", + "a.a..a.a.a.a.tar.gz", + ] { + let attachment = alice.blobdir.join(filename_sent); + let content = format!("File content of {}", filename_sent); + File::create(&attachment) + .await? + .write_all(content.as_bytes()) + .await?; + + let mut msg_alice = Message::new(Viewtype::File); + msg_alice.set_file(attachment.to_str().unwrap(), None); + let alice_chat = alice.create_chat(&bob).await; + let sent = alice.send_msg(alice_chat.id, &mut msg_alice).await; + println!("{}", sent.payload()); + + bob.recv_msg(&sent).await; + let msg_bob = bob.get_last_msg().await; + + async fn check_message(msg: &Message, t: &TestContext, content: &str) { + assert_eq!(msg.get_viewtype(), Viewtype::File); + let resulting_filename = msg.get_filename().unwrap(); + let path = msg.get_file(t).unwrap(); + assert!( + resulting_filename.ends_with(".tar.gz"), + "{:?} doesn't end with .tar.gz, path: {:?}", + resulting_filename, + path + ); + assert!( + path.to_str().unwrap().ends_with(".tar.gz"), + "path {:?} doesn't end with .tar.gz", + path + ); + assert_eq!(fs::read_to_string(path).await.unwrap(), content); + } + check_message(&msg_alice, &alice, &content).await; + check_message(&msg_bob, &bob, &content).await; + } + + Ok(()) + } }