Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
33 changes: 25 additions & 8 deletions src/archive/zip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,12 @@ where
let is_symlink = mode.is_some_and(|mode| mode & 0o170000 == 0o120000);

if is_symlink {
let mut target = String::new();
file.read_to_string(&mut target)?;
// Symlink targets are arbitrary bytes on Unix, not guaranteed UTF-8; read as bytes.
let mut target_bytes = Vec::new();
file.read_to_end(&mut target_bytes)?;
let target = symlink_target_from_bytes(&target_bytes);

validate_symlink_target(&relpath, Path::new(&target))?;
validate_symlink_target(&relpath, &target)?;
#[cfg(unix)]
std::os::unix::fs::symlink(&target, &file_path)?;
#[cfg(windows)]
Expand All @@ -86,13 +88,15 @@ where
let is_symlink = mode.is_some_and(|mode| mode & 0o170000 == 0o120000);

if is_symlink {
let mut target = String::new();
file.read_to_string(&mut target)?;
// Symlink targets are arbitrary bytes on Unix, not guaranteed UTF-8; read as bytes.
let mut target_bytes = Vec::new();
file.read_to_end(&mut target_bytes)?;
let target = symlink_target_from_bytes(&target_bytes);

validate_symlink_target(&relpath, Path::new(&target))?;
info!("linking {} -> \"{}\"", PathFmt(file_path), target);
validate_symlink_target(&relpath, &target)?;
info!("linking {} -> \"{}\"", PathFmt(file_path), target.display());

create_symlink(Path::new(&target), file_path)?;
create_symlink(&target, file_path)?;
} else {
#[cfg(unix)]
let mut output_file = {
Expand Down Expand Up @@ -286,6 +290,19 @@ where
Ok(bytes)
}

/// Decode a zip symlink target's raw bytes into a path (lossless on Unix, lossy elsewhere).
fn symlink_target_from_bytes(bytes: &[u8]) -> PathBuf {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
PathBuf::from(std::ffi::OsStr::from_bytes(bytes))
}
#[cfg(not(unix))]
{
PathBuf::from(String::from_utf8_lossy(bytes).into_owned())
}
}

fn display_zip_comment_if_exists<R: Read>(file: &ZipFile<'_, R>) {
let comment = file.comment();
if !comment.is_empty() {
Expand Down
13 changes: 9 additions & 4 deletions src/utils/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,15 @@ impl<R: Read> Read for LimitedReader<R> {
}

if self.remaining == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"decompression output exceeded configured limit (see OUCH_MAX_DECOMPRESSED_BYTES)",
));
// Probe one byte to tell exactly-at-limit (EOF) from over-limit; discarded on error.
let mut probe = [0u8; 1];
return match self.inner.read(&mut probe)? {
0 => Ok(0),
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
"decompression output exceeded configured limit (see OUCH_MAX_DECOMPRESSED_BYTES)",
)),
};
}

let bytes_to_ready = usize::try_from(self.remaining).unwrap_or(usize::MAX).min(buf.len());
Expand Down
Loading