git.delta.rocks / jrsonnet / refs/commits / baeb3670f725

difftreelog

source

crates/jrsonnet-pkg/src/install/accessor.rs3.2 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::{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	pub fn iter<E>(70		&self,71		subdir: &SubDir,72		cb: &mut dyn FnMut(SubDir, AccessorEntry) -> Result<(), E>,73	) -> Result<(), E>74	where75		E: From<Error>,76	{77		let mut archive = self.archive.lock().expect("not poisoned");78		let len = archive.len();7980		let mut found = false;81		for i in 0..len {82			let mut entry = archive.by_index(i).map_err(Error::from)?;83			let raw = entry.name();84			let Ok(full_name) = SubDir::from_str(raw) else {85				warn!("invalid zip entry name: {raw}");86				continue;87			};88			// Peel off the github-archive top-level `<repo>-<sha>/` prefix.89			let Some(in_repo) = full_name.strip_prefix(&self.prefix) else {90				continue;91			};92			let Some(name) = in_repo.strip_prefix(subdir) else {93				continue;94			};95			found = true;96			if name.is_empty() && entry.is_dir() {97				continue;98			}99100			cb(101				name.clone(),102				if entry.is_dir() {103					AccessorEntry::Dir104				} else if entry.is_file() {105					let mut data = Vec::new();106					entry.read_to_end(&mut data).map_err(Error::ZipIo)?;107					AccessorEntry::File(data)108				} else {109					// TODO: Symlinks?110					panic!("unknown accessor entry type: {name:?}")111				},112			)?;113		}114115		if !found {116			return Err(Error::SubDirNotFound(subdir.clone()).into());117		}118119		Ok(())120	}121	pub fn len(&self) -> usize {122		self.archive.lock().expect("not poisoned").len()123	}124	pub fn is_empty(&self) -> bool {125		self.len() == 0126	}127}128129pub enum AccessorEntry {130	Dir,131	File(Vec<u8>),132}