Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 28 additions & 10 deletions src/source/extract.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
//! Helpers to extract archives
use std::{ffi::OsStr, io::BufRead, path::Path};
use std::{
ffi::OsStr,
io::{self, BufRead, BufReader},
path::Path,
};

use crate::console_utils::LoggingOutputHandler;

Expand All @@ -13,7 +17,7 @@ enum TarCompression<'a> {
Gzip(flate2::read::GzDecoder<Box<dyn BufRead + 'a>>),
Bzip2(bzip2::read::BzDecoder<Box<dyn BufRead + 'a>>),
Xz2(xz2::read::XzDecoder<Box<dyn BufRead + 'a>>),
Zstd(zstd::stream::read::Decoder<'a, std::io::BufReader<Box<dyn BufRead + 'a>>>),
Zstd(zstd::stream::read::Decoder<'a, BufReader<Box<dyn BufRead + 'a>>>),
Compress,
Lzip,
Lzop,
Expand Down Expand Up @@ -79,8 +83,8 @@ fn ext_to_compression<'a>(ext: Option<&OsStr>, file: Box<dyn BufRead + 'a>) -> T
}
}

impl std::io::Read for TarCompression<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
impl io::Read for TarCompression<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
TarCompression::PlainTar(reader) => reader.read(buf),
TarCompression::Gzip(reader) => reader.read(buf),
Expand Down Expand Up @@ -132,7 +136,7 @@ pub(crate) fn extract_tar(
);

let file = File::open(archive)?;
let buf_reader = std::io::BufReader::with_capacity(1024 * 1024, file);
let buf_reader = BufReader::with_capacity(1024 * 1024, file);
let wrapped = progress_bar.wrap_read(buf_reader);

let mut archive = tar::Archive::new(ext_to_compression(archive.file_name(), Box::new(wrapped)));
Expand Down Expand Up @@ -170,15 +174,29 @@ pub(crate) fn extract_zip(
);

let file = File::open(archive)?;
let buf_reader = std::io::BufReader::with_capacity(1024 * 1024, file);
let buf_reader = BufReader::with_capacity(1024 * 1024, file);
let wrapped = progress_bar.wrap_read(buf_reader);
let mut archive =
zip::ZipArchive::new(wrapped).map_err(|e| SourceError::InvalidZip(e.to_string()))?;

let tmp_extraction_dir = tempfile::Builder::new().tempdir_in(target_directory)?;
archive
.extract(&tmp_extraction_dir)
.map_err(|e| SourceError::ZipExtractionError(e.to_string()))?;

// Extract using iterator - handles Windows-specific issues with manual extraction
(0..archive.len()).try_for_each(|i| -> Result<(), SourceError> {
let mut file = archive
.by_index(i)
.map_err(|e| SourceError::ZipExtractionError(e.to_string()))?;
let outpath = tmp_extraction_dir.path().join(file.mangled_name());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the mangled_name?

Copy link
Collaborator Author

@zelosleone zelosleone Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://docs.rs/zip/latest/zip/read/struct.ZipFile.html#method.mangled_name basically what we are doing is using zip crate's mangled_name to manually extract correcly the paths into files + folders with sanitized file names

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should use that function as the docs recommend: enclosed_name.

This will read well-formed ZIP files correctly, and is resistant to path-based exploits. It is recommended over ZipFile::mangled_name.

Why does mangled name fix anything?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enclosed_name causes windows file lock issues, which seems to give me problematic results on windows. I would love your input on that, retrying is an approach but i am not sure about it for zip extraction

if file.name().ends_with('/') {
fs::create_dir_all(&outpath)?;
} else {
if let Some(p) = outpath.parent() {
fs::create_dir_all(p)?;
}
io::copy(&mut file, &mut File::create(&outpath)?)?;
}
Ok(())
})?;

move_extracted_dir(tmp_extraction_dir.path(), target_directory)?;
progress_bar.finish_with_message("Extracted...");
Expand All @@ -204,7 +222,7 @@ pub(crate) fn extract_7z(
);

let file = File::open(archive)?;
let buf_reader = std::io::BufReader::with_capacity(1024 * 1024, file);
let buf_reader = BufReader::with_capacity(1024 * 1024, file);
let wrapped = progress_bar.wrap_read(buf_reader);

let tmp_extraction_dir = tempfile::Builder::new().tempdir_in(target_directory)?;
Expand Down
Loading