git.delta.rocks / jrsonnet / refs/commits / 97dec27e23cc

difftreelog

feat(jrb) symlinks

kmklkzzmYaroslav Bolyukin2026-05-07parent: #6710888.patch.diff
in: master

2 files changed

modifiedcrates/jrsonnet-pkg/src/install/accessor.rsdiffbeforeafterboth
--- a/crates/jrsonnet-pkg/src/install/accessor.rs
+++ b/crates/jrsonnet-pkg/src/install/accessor.rs
@@ -9,7 +9,7 @@
 use tracing::warn;
 use zip::{ZipArchive, result::ZipError};
 
-use crate::jsonnet_bundler::{SubDir, SubDirEscapeError};
+use crate::jsonnet_bundler::{LocalSource, SubDir, SubDirEscapeError};
 
 #[derive(thiserror::Error, Debug)]
 pub enum Error {
@@ -105,13 +105,25 @@
 				name.clone(),
 				if entry.is_dir() {
 					AccessorEntry::Dir
+				} else if entry.is_symlink() {
+					let mut target = Vec::new();
+					entry.read_to_end(&mut target).map_err(Error::ZipIo)?;
+					let Ok(target_str) = std::str::from_utf8(&target) else {
+						warn!("non-utf8 symlink target in zip entry: {name:?}");
+						continue;
+					};
+					let Ok(target) = LocalSource::from_str(target_str) else {
+						warn!("symlink target {target_str:?} at {name:?} escapes sandbox; skipping");
+						continue;
+					};
+					AccessorEntry::Symlink(target)
 				} else if entry.is_file() {
 					let mut data = Vec::new();
 					entry.read_to_end(&mut data).map_err(Error::ZipIo)?;
 					AccessorEntry::File(data)
 				} else {
-					// TODO: Symlinks?
-					panic!("unknown accessor entry type: {name:?}")
+					warn!("unknown accessor entry type: {name:?}");
+					continue;
 				},
 			)?;
 		}
@@ -133,4 +145,5 @@
 pub enum AccessorEntry {
 	Dir,
 	File(Vec<u8>),
+	Symlink(LocalSource),
 }
modifiedcrates/jrsonnet-pkg/src/install/github.rsdiffbeforeafterboth
7 path::{Path, PathBuf},7 path::{Path, PathBuf},
8};8};
99
10use camino::Utf8PathBuf;
10use reqwest::{blocking::Response, header};11use reqwest::{blocking::Response, header};
11use tracing::{debug, info};12use tracing::{debug, info, warn};
1213
13use super::{14use super::{
14 Error, LocalExtraction, ResolveResult, Result, VendorSource,15 Error, LocalExtraction, ResolveResult, Result, VendorSource,
85 )?)86 )?)
86}87}
88
89#[cfg(unix)]
90fn make_symlink(target: &str, link: &Path) -> std::io::Result<()> {
91 std::os::unix::fs::symlink(target, link)
92}
93
94#[cfg(windows)]
95fn make_symlink(target: &str, link: &Path) -> std::io::Result<()> {
96 std::os::windows::fs::symlink_file(target, link)
97}
98
99#[cfg(not(any(unix, windows)))]
100fn make_symlink(_target: &str, _link: &Path) -> std::io::Result<()> {
101 Err(std::io::Error::new(
102 std::io::ErrorKind::Unsupported,
103 "symlinks are not supported on this platform",
104 ))
105}
87106
88fn extract_subdir(archive: &ZipFileAccessor, subdir: &SubDir, dest: &Path) -> Result<()> {107fn extract_subdir(archive: &ZipFileAccessor, subdir: &SubDir, dest: &Path) -> Result<()> {
89 archive.iter(subdir, &mut |name, entry| {108 archive.iter(subdir, &mut |name, entry| {
90 let target = dest.join(name);109 let target = dest.join(&name);
91 match entry {110 match entry {
92 AccessorEntry::Dir => {111 AccessorEntry::Dir => {
93 fs::create_dir_all(&target).map_err(|e| Error::Io(target, e))?;112 fs::create_dir_all(&target).map_err(|e| Error::Io(target, e))?;
98 }117 }
99 fs::write(&target, &data).map_err(|e| Error::Io(target, e))?;118 fs::write(&target, &data).map_err(|e| Error::Io(target, e))?;
100 }119 }
120 AccessorEntry::Symlink(link_target) => {
121 let symlink_parent = name
122 .as_path()
123 .parent()
124 .map(|p| SubDir::try_from(Utf8PathBuf::from(p)))
125 .transpose()
126 .expect("parent of a SubDir is a SubDir")
127 .unwrap_or_else(SubDir::empty);
128 if link_target.resolve_under(&symlink_parent).is_err() {
129 warn!("symlink {name} -> {link_target} escapes extraction; skipping");
130 return Ok(());
131 }
132 if let Some(parent) = target.parent() {
133 fs::create_dir_all(parent).map_err(|e| Error::Io(parent.to_owned(), e))?;
134 }
135 make_symlink(&link_target.to_string(), &target)
136 .map_err(|e| Error::Io(target, e))?;
137 }
101 }138 }
102 Ok(())139 Ok(())
103 })140 })