mirror of
https://github.com/chatmail/core.git
synced 2026-05-14 20:36:30 +03:00
fix: Assume file extensions are 32 chars max and don't contain whitespace (#5338)
Before file extensions were also limited to 32 chars, but extra chars in the beginning were just cut off, e.g. "file.with_lots_of_characters_behind_point_and_double_ending.tar.gz" was considered to have an extension "d_point_and_double_ending.tar.gz". Better to take only "tar.gz" then. Also don't include whitespace-containing parts in extensions. File extensions generally don't contain whitespaces.
This commit is contained in:
29
src/blob.rs
29
src/blob.rs
@@ -232,41 +232,44 @@ impl<'a> BlobObject<'a> {
|
|||||||
|
|
||||||
/// Get a file extension if any, including the dot, in lower case, otherwise an empty string.
|
/// Get a file extension if any, including the dot, in lower case, otherwise an empty string.
|
||||||
fn get_extension(name: &str) -> String {
|
fn get_extension(name: &str) -> String {
|
||||||
let mut name = name.to_string();
|
let mut name = name;
|
||||||
for part in name.rsplit('/') {
|
for part in name.rsplit('/') {
|
||||||
if !part.is_empty() {
|
if !part.is_empty() {
|
||||||
name = part.to_string();
|
name = part;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for part in name.rsplit('\\') {
|
for part in name.rsplit('\\') {
|
||||||
if !part.is_empty() {
|
if !part.is_empty() {
|
||||||
name = part.to_string();
|
name = part;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's take the tricky filename
|
// Let's take the tricky filename
|
||||||
// "file.with_lots_of_characters_behind_point_and_double_ending.tar.gz" as an example.
|
// "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":
|
// Assume that the extension is 32 chars maximum.
|
||||||
let mut iter = name.splitn(2, '.');
|
let ext: String = name
|
||||||
iter.next();
|
.chars()
|
||||||
|
|
||||||
let ext_chars = iter.next().unwrap_or_default().chars();
|
|
||||||
let ext: String = ext_chars
|
|
||||||
.rev()
|
.rev()
|
||||||
.take(32)
|
.take_while(|c| !c.is_whitespace())
|
||||||
|
.take(33)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.collect();
|
.collect();
|
||||||
// ext == "d_point_and_double_ending.tar.gz"
|
// ext == "nd_point_and_double_ending.tar.gz"
|
||||||
|
|
||||||
|
// Split it into "nd_point_and_double_ending" and "tar.gz":
|
||||||
|
let mut iter = ext.splitn(2, '.');
|
||||||
|
iter.next();
|
||||||
|
|
||||||
|
let ext = iter.next().unwrap_or_default();
|
||||||
if ext.is_empty() {
|
if ext.is_empty() {
|
||||||
ext
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
format!(".{ext}").to_lowercase()
|
format!(".{ext}").to_lowercase()
|
||||||
// Return ".d_point_and_double_ending.tar.gz", which is not perfect but acceptable.
|
// Return ".tar.gz".
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -2960,20 +2961,27 @@ Reply from different address
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_long_and_duplicated_filenames() -> Result<()> {
|
async fn test_weird_and_duplicated_filenames() -> Result<()> {
|
||||||
let mut tcm = TestContextManager::new();
|
let mut tcm = TestContextManager::new();
|
||||||
let alice = tcm.alice().await;
|
let alice = tcm.alice().await;
|
||||||
let bob = tcm.bob().await;
|
let bob = tcm.bob().await;
|
||||||
|
|
||||||
for filename_sent in &[
|
for (filename_sent, expected_ext) in &[
|
||||||
"foo.bar very long file name test baz.tar.gz",
|
("foo.bar very long file name test baz.tar.gz", "tar.gz"),
|
||||||
"foobarabababababababbababababverylongfilenametestbaz.tar.gz",
|
(
|
||||||
"fooo...tar.gz",
|
"foo.barabababababababbababababverylongfilenametestbaz.tar.gz",
|
||||||
"foo. .tar.gz",
|
"tar.gz",
|
||||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.tar.gz",
|
),
|
||||||
"a.tar.gz",
|
("fooo...tar.gz", "..tar.gz"),
|
||||||
"a.tar.gz",
|
("foo. .tar.gz", "tar.gz"),
|
||||||
"a.a..a.a.a.a.tar.gz",
|
(
|
||||||
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.tar.gz",
|
||||||
|
"tar.gz",
|
||||||
|
),
|
||||||
|
("a.tar.gz", "tar.gz"),
|
||||||
|
("a.tar.gz", "tar.gz"),
|
||||||
|
("a.a..a.a.a.a.tar.gz", "a..a.a.a.a.tar.gz"),
|
||||||
|
("a. tar.tar.gz", "tar.gz"),
|
||||||
] {
|
] {
|
||||||
let attachment = alice.blobdir.join(filename_sent);
|
let attachment = alice.blobdir.join(filename_sent);
|
||||||
let content = format!("File content of {filename_sent}");
|
let content = format!("File content of {filename_sent}");
|
||||||
@@ -2987,23 +2995,33 @@ async fn test_long_and_duplicated_filenames() -> Result<()> {
|
|||||||
|
|
||||||
let msg_bob = bob.recv_msg(&sent).await;
|
let msg_bob = bob.recv_msg(&sent).await;
|
||||||
|
|
||||||
async fn check_message(msg: &Message, t: &TestContext, filename: &str, content: &str) {
|
async fn check_message(
|
||||||
|
msg: &Message,
|
||||||
|
t: &TestContext,
|
||||||
|
filename: &str,
|
||||||
|
expected_ext: &str,
|
||||||
|
content: &str,
|
||||||
|
) {
|
||||||
assert_eq!(msg.get_viewtype(), Viewtype::File);
|
assert_eq!(msg.get_viewtype(), Viewtype::File);
|
||||||
let resulting_filename = msg.get_filename().unwrap();
|
let resulting_filename = msg.get_filename().unwrap();
|
||||||
assert_eq!(resulting_filename, filename);
|
assert_eq!(resulting_filename, filename);
|
||||||
let path = msg.get_file(t).unwrap();
|
let path = msg.get_file(t).unwrap();
|
||||||
|
if !msg.get_state().is_outgoing() {
|
||||||
|
let re =
|
||||||
|
Regex::new(&("^[[:xdigit:]]{16}.".to_string() + expected_ext + "$")).unwrap();
|
||||||
|
assert!(
|
||||||
|
re.is_match(path.file_name().unwrap().to_str().unwrap()),
|
||||||
|
"invalid path {path:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
let path2 = path.with_file_name("saved.txt");
|
let path2 = path.with_file_name("saved.txt");
|
||||||
msg.save_file(t, &path2).await.unwrap();
|
msg.save_file(t, &path2).await.unwrap();
|
||||||
assert!(
|
|
||||||
path.to_str().unwrap().ends_with(".tar.gz"),
|
|
||||||
"path {path:?} doesn't end with .tar.gz"
|
|
||||||
);
|
|
||||||
assert_eq!(fs::read_to_string(&path).await.unwrap(), content);
|
assert_eq!(fs::read_to_string(&path).await.unwrap(), content);
|
||||||
assert_eq!(fs::read_to_string(&path2).await.unwrap(), content);
|
assert_eq!(fs::read_to_string(&path2).await.unwrap(), content);
|
||||||
fs::remove_file(path2).await.unwrap();
|
fs::remove_file(path2).await.unwrap();
|
||||||
}
|
}
|
||||||
check_message(&msg_alice, &alice, filename_sent, &content).await;
|
check_message(&msg_alice, &alice, filename_sent, expected_ext, &content).await;
|
||||||
check_message(&msg_bob, &bob, filename_sent, &content).await;
|
check_message(&msg_bob, &bob, filename_sent, expected_ext, &content).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user