Skip to content

Commit e017b3b

Browse files
noctuxmartinetd
authored andcommitted
Do not overwrite saved media files
If a file with the requested filename already exists in --media-dir, divert the write to a new, uniquely named file (based on the original filename) via the tempfile crate.
1 parent c59632e commit e017b3b

File tree

3 files changed

+56
-5
lines changed

3 files changed

+56
-5
lines changed

Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@ rand_core = { version = "0.9", features = ["os_rng"] }
3232
regex = "1.8"
3333
serde = "1.0"
3434
serde_json = "1.0"
35+
tempfile = "3.19.1"
3536
tokio = { version = "1.0.0", features = ["full"] }
3637
tokio-util = { version = "0.7", features = ["codec"] }

src/matrix/sync_room_message.rs

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ use matrix_sdk::{
1212
Client, RoomState,
1313
};
1414
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
15-
use std::path::PathBuf;
15+
use std::ffi::OsStr;
16+
use std::path::{Path, PathBuf};
17+
1618
use tokio::fs;
1719
use tokio::io::AsyncWriteExt;
1820

@@ -25,6 +27,37 @@ use crate::matrix::verification::handle_verification_request;
2527
/// https://url.spec.whatwg.org/#fragment-percent-encode-set
2628
const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
2729

30+
async fn generate_fresh_file(dir: PathBuf, filename: &str) -> Result<(tokio::fs::File, String)> {
31+
let filename = Path::new(filename);
32+
let prefix = filename
33+
.file_stem()
34+
.and_then(OsStr::to_str)
35+
.map(|s| format!("{}-", s))
36+
.unwrap_or_else(|| "noname-".to_string());
37+
let suffix = filename
38+
.extension()
39+
.and_then(OsStr::to_str)
40+
.map(|s| format!(".{}", s))
41+
.unwrap_or_default();
42+
43+
tokio::task::spawn_blocking(move || {
44+
let (filehandle, filepath) = tempfile::Builder::new()
45+
.prefix(&prefix)
46+
.suffix(&suffix)
47+
.tempfile_in(dir)?
48+
.keep()?;
49+
50+
let fresh_filename = filepath
51+
.file_name()
52+
.context("Internal error: No filename component")?
53+
.to_str()
54+
.context("Internal error: Non-utf8 filename")?
55+
.to_owned();
56+
Ok((tokio::fs::File::from(filehandle), fresh_filename))
57+
})
58+
.await?
59+
}
60+
2861
#[async_trait]
2962
pub trait SourceUri {
3063
async fn to_uri(&self, client: &Client, body: &str) -> Result<String>;
@@ -54,12 +87,28 @@ impl SourceUri for MediaSource {
5487
.await?
5588
}
5689
let file = dir.join(filename);
57-
fs::File::create(file).await?.write_all(&content).await?;
90+
let plainfh = fs::OpenOptions::new()
91+
.write(true)
92+
.create_new(true)
93+
.open(file)
94+
.await;
95+
let outfiletuple = match plainfh {
96+
Ok(fh) => Ok((fh, filename.to_owned())),
97+
Err(err) => {
98+
if err.kind() == std::io::ErrorKind::AlreadyExists {
99+
generate_fresh_file(dir, filename).await
100+
} else {
101+
Err(anyhow::Error::from(err))
102+
}
103+
}
104+
};
105+
let (mut fh, filename) = outfiletuple?;
106+
fh.write_all(&content).await?;
58107
let url = args().media_url.as_ref().unwrap_or(dir_path);
59108
Ok(format!(
60109
"{}/{}",
61110
url,
62-
utf8_percent_encode(filename, FRAGMENT)
111+
utf8_percent_encode(&filename, FRAGMENT)
63112
))
64113
}
65114
}

0 commit comments

Comments
 (0)