git.delta.rocks / jrsonnet / refs/heads / master

difftreelog

source

crates/jrsonnet-pkg/src/install/accessor.rs3.8 KiBsourcehistory
1use std::{2	fs::File,3	io::{self, Read},4	result,5	str::FromStr as _,6	sync::Mutex,7};89use tracing::warn;10use zip::{ZipArchive, result::ZipError};1112use crate::jsonnet_bundler::{LocalSource, SubDir, SubDirEscapeError};1314#[derive(thiserror::Error, Debug)]15pub enum Error {16	#[error(transparent)]17	Zip(#[from] ZipError),18	#[error("invalid prefixed archive")]19	ZipInvalidPrefix,20	#[error("zip io: {0}")]21	ZipIo(io::Error),22	#[error("subdir not found: {0}")]23	SubDirNotFound(SubDir),24	#[error(transparent)]25	SubdirEscape(#[from] SubDirEscapeError),26}27type Result<T, E = Error> = result::Result<T, E>;2829pub trait SourceAccessor {}3031pub struct ZipFileAccessor {32	archive: Mutex<ZipArchive<File>>,33	// Github archives have top-level directory with repo name34	prefix: SubDir,35}3637impl ZipFileAccessor {38	pub fn new_prefixed(file: File) -> Result<Self> {39		let archive = ZipArchive::new(file)?;40		let prefix = archive.name_for_index(0).ok_or(Error::ZipInvalidPrefix)?;4142		Ok(Self {43			prefix: SubDir::from_str(prefix)?,44			archive: Mutex::new(archive),45		})46	}47	/// Read a file from inside the archive's logical root (after stripping the48	/// github-style `<repo>-<sha>/` prefix).49	#[allow(clippy::significant_drop_tightening, reason = "false-positive")]50	pub fn read(&self, name: &SubDir) -> Result<Option<Vec<u8>>> {51		let prefixed = self52			.prefix53			.join(name)54			.expect("prefix and name are both subdirs");55		let mut archive = self.archive.lock().expect("not poisoned");56		let mut v = match archive.by_name(prefixed.as_str()) {57			Ok(v) => v,58			Err(ZipError::FileNotFound) => return Ok(None),59			Err(e) => return Err(e.into()),60		};61		if !v.is_file() {62			return Ok(None);63		}64		let mut out = Vec::new();65		v.read_to_end(&mut out).map_err(Error::ZipIo)?;66		Ok(Some(out))67	}68	#[allow(clippy::significant_drop_tightening, reason = "false-positive")]69	#[allow(70		clippy::iter_not_returning_iterator,71		reason = "idk for a better name, it is still inner iteration"72	)]73	pub fn iter<E>(74		&self,75		subdir: &SubDir,76		cb: &mut dyn FnMut(SubDir, AccessorEntry) -> Result<(), E>,77	) -> Result<(), E>78	where79		E: From<Error>,80	{81		let mut archive = self.archive.lock().expect("not poisoned");82		let len = archive.len();8384		let mut found = false;85		for i in 0..len {86			let mut entry = archive.by_index(i).map_err(Error::from)?;87			let raw = entry.name();88			let Ok(full_name) = SubDir::from_str(raw) else {89				warn!("invalid zip entry name: {raw}");90				continue;91			};92			// Peel off the github-archive top-level `<repo>-<sha>/` prefix.93			let Some(in_repo) = full_name.strip_prefix(&self.prefix) else {94				continue;95			};96			let Some(name) = in_repo.strip_prefix(subdir) else {97				continue;98			};99			found = true;100			if name.is_empty() && entry.is_dir() {101				continue;102			}103104			cb(105				name.clone(),106				if entry.is_dir() {107					AccessorEntry::Dir108				} else if entry.is_symlink() {109					let mut target = Vec::new();110					entry.read_to_end(&mut target).map_err(Error::ZipIo)?;111					let Ok(target_str) = std::str::from_utf8(&target) else {112						warn!("non-utf8 symlink target in zip entry: {name:?}");113						continue;114					};115					let Ok(target) = LocalSource::from_str(target_str) else {116						warn!(117							"symlink target {target_str:?} at {name:?} escapes sandbox; skipping"118						);119						continue;120					};121					AccessorEntry::Symlink(target)122				} else if entry.is_file() {123					let mut data = Vec::new();124					entry.read_to_end(&mut data).map_err(Error::ZipIo)?;125					AccessorEntry::File(data)126				} else {127					warn!("unknown accessor entry type: {name:?}");128					continue;129				},130			)?;131		}132133		if !found {134			return Err(Error::SubDirNotFound(subdir.clone()).into());135		}136137		Ok(())138	}139	pub fn len(&self) -> usize {140		self.archive.lock().expect("not poisoned").len()141	}142	pub fn is_empty(&self) -> bool {143		self.len() == 0144	}145}146147pub enum AccessorEntry {148	Dir,149	File(Vec<u8>),150	Symlink(LocalSource),151}