Skip to content

Commit d0b37ba

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 d0b37ba

File tree

3 files changed

+57
-4
lines changed

3 files changed

+57
-4
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: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,41 @@ use crate::matrix::verification::handle_verification_request;
2525
/// https://url.spec.whatwg.org/#fragment-percent-encode-set
2626
const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
2727

28+
async fn generate_fresh_file(dir: PathBuf, filename: &str) -> Result<(tokio::fs::File, String)> {
29+
let fresh_filepath = tokio::task::spawn_blocking({
30+
let filename = String::from(filename);
31+
move || -> Result<PathBuf> {
32+
let firstdotidx = filename.find('.');
33+
let (prefix, suffix) = match firstdotidx {
34+
Some(idx) => filename.split_at(idx),
35+
None => (filename.as_str(), ""),
36+
};
37+
let (_, tmpfile) = tempfile::Builder::new()
38+
.prefix(&(prefix.to_owned() + "-"))
39+
.suffix(&suffix)
40+
.tempfile_in(dir)?
41+
.keep()?;
42+
Ok(tmpfile)
43+
}
44+
})
45+
.await??;
46+
let fresh_filename = fresh_filepath
47+
.file_name()
48+
.context("Internal error: No filename component")?
49+
.to_str()
50+
.context("Internal error: Non-utf8 filename")?
51+
.to_owned();
52+
Ok((
53+
fs::OpenOptions::new()
54+
.write(true)
55+
.create(true)
56+
.truncate(true)
57+
.open(fresh_filepath)
58+
.await?,
59+
fresh_filename,
60+
))
61+
}
62+
2863
#[async_trait]
2964
pub trait SourceUri {
3065
async fn to_uri(&self, client: &Client, body: &str) -> Result<String>;
@@ -54,12 +89,28 @@ impl SourceUri for MediaSource {
5489
.await?
5590
}
5691
let file = dir.join(filename);
57-
fs::File::create(file).await?.write_all(&content).await?;
92+
let plainfh = fs::OpenOptions::new()
93+
.write(true)
94+
.create_new(true)
95+
.open(file)
96+
.await;
97+
let outfiletuple = match plainfh {
98+
Ok(fh) => Ok((fh, filename.to_owned())),
99+
Err(err) => {
100+
if err.kind() == std::io::ErrorKind::AlreadyExists {
101+
generate_fresh_file(dir, filename).await
102+
} else {
103+
Err(anyhow::Error::from(err))
104+
}
105+
}
106+
};
107+
let (mut fh, filename) = outfiletuple?;
108+
fh.write_all(&content).await?;
58109
let url = args().media_url.as_ref().unwrap_or(dir_path);
59110
Ok(format!(
60111
"{}/{}",
61112
url,
62-
utf8_percent_encode(filename, FRAGMENT)
113+
utf8_percent_encode(&filename, FRAGMENT)
63114
))
64115
}
65116
}

0 commit comments

Comments
 (0)