From fcf38ae6c910ed1d28d7f7c3adfb7cc95d8c23c1 Mon Sep 17 00:00:00 2001 From: tommady Date: Sun, 13 Apr 2025 02:49:17 +0000 Subject: [PATCH] let zip can extract symlink Signed-off-by: tommady --- src/archive/tar.rs | 7 +++---- src/archive/zip.rs | 30 ++++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/archive/tar.rs b/src/archive/tar.rs index d101bf6..06f9280 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -30,12 +30,11 @@ pub fn unpack_archive(reader: Box, output_folder: &Path, quiet: bool) let mut files_unpacked = 0; for file in archive.entries()? { let mut file = file?; - let entry_type = file.header().entry_type(); - let relative_path = file.path()?.to_path_buf(); - let full_path = output_folder.join(&relative_path); - match entry_type { + match file.header().entry_type() { tar::EntryType::Symlink => { + let relative_path = file.path()?.to_path_buf(); + let full_path = output_folder.join(&relative_path); let target = file .link_name()? .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "Missing symlink target"))?; diff --git a/src/archive/zip.rs b/src/archive/zip.rs index f525d3b..9dda3ff 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -85,8 +85,23 @@ where )); } - let mut output_file = fs::File::create(file_path)?; - io::copy(&mut file, &mut output_file)?; + let mode = file.unix_mode().ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "Cannot extract file's mode") + })?; + let is_symlink = (mode & 0o170000) == 0o120000; + + if is_symlink { + let mut target = String::new(); + file.read_to_string(&mut target)?; + + #[cfg(unix)] + std::os::unix::fs::symlink(&target, &file_path)?; + #[cfg(windows)] + std::os::windows::fs::symlink_file(&target, &file_path)?; + } else { + let mut output_file = fs::File::create(file_path)?; + io::copy(&mut file, &mut output_file)?; + } set_last_modified_time(&file, file_path)?; } @@ -232,6 +247,17 @@ where if metadata.is_dir() { writer.add_directory(entry_name, options)?; + } else if path.is_symlink() { + let target_path = path.read_link()?; + let target_name = target_path.to_str().ok_or_else(|| { + FinalError::with_title("Zip requires that all directories names are valid UTF-8") + .detail(format!("File at '{target_path:?}' has a non-UTF-8 name")) + })?; + + // This approach writes the symlink target path as the content of the symlink entry. + // We detect symlinks during extraction by checking for the Unix symlink mode (0o120000) in the entry's permissions. + let symlink_options = options.unix_permissions(0o120777); + writer.add_symlink(entry_name, target_name, symlink_options)?; } else { #[cfg(not(unix))] let options = if is_executable::is_executable(path) {