Skip to content

Commit bb82076

Browse files
committed
Sanitize filenames during zip extraction
1 parent 8d3408f commit bb82076

File tree

4 files changed

+27
-2
lines changed

4 files changed

+27
-2
lines changed

Cargo.lock

Lines changed: 11 additions & 0 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
@@ -68,6 +68,7 @@ uv-virtualenv = { path = "crates/uv-virtualenv" }
6868
uv-warnings = { path = "crates/uv-warnings" }
6969
uv-workspace = { path = "crates/uv-workspace" }
7070

71+
sanitize-filename = { version = "0.5.0" }
7172
anstream = { version = "0.6.15" }
7273
anyhow = { version = "1.0.89" }
7374
async-channel = { version = "2.3.1" }

crates/uv-extract/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ md-5 = { workspace = true }
2828
rayon = { workspace = true }
2929
reqwest = { workspace = true }
3030
rustc-hash = { workspace = true }
31+
sanitize-filename = { workspace = true }
3132
sha2 = { workspace = true }
3233
thiserror = { workspace = true }
3334
tokio = { workspace = true }

crates/uv-extract/src/stream.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ pub async fn unzip<R: tokio::io::AsyncRead + Unpin>(
1919
reader: R,
2020
target: impl AsRef<Path>,
2121
) -> Result<(), Error> {
22+
/// Sanitize a filename for use on Windows.
23+
fn sanitize(filename: impl AsRef<str>) -> String {
24+
sanitize_filename::sanitize_with_options(
25+
filename,
26+
sanitize_filename::Options {
27+
windows: cfg!(windows),
28+
truncate: false,
29+
replacement: "",
30+
},
31+
)
32+
}
33+
2234
let target = target.as_ref();
2335
let mut reader = futures::io::BufReader::with_capacity(DEFAULT_BUF_SIZE, reader.compat());
2436
let mut zip = async_zip::base::read::stream::ZipFileReader::new(&mut reader);
@@ -28,7 +40,7 @@ pub async fn unzip<R: tokio::io::AsyncRead + Unpin>(
2840
while let Some(mut entry) = zip.next_with_entry().await? {
2941
// Construct the (expected) path to the file on-disk.
3042
let path = entry.reader().entry().filename().as_str()?;
31-
let path = target.join(path);
43+
let path = target.join(sanitize(path));
3244
let is_dir = entry.reader().entry().dir()?;
3345

3446
// Either create the directory or write the file to disk.
@@ -84,7 +96,7 @@ pub async fn unzip<R: tokio::io::AsyncRead + Unpin>(
8496
if has_any_executable_bit != 0 {
8597
// Construct the (expected) path to the file on-disk.
8698
let path = entry.filename().as_str()?;
87-
let path = target.join(path);
99+
let path = target.join(sanitize(path));
88100

89101
let permissions = fs_err::tokio::metadata(&path).await?.permissions();
90102
if permissions.mode() & 0o111 != 0o111 {

0 commit comments

Comments
 (0)