diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-15 00:19:50 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-15 02:37:37 +0900 |
| commit | ac46dc8e98629719370c98e0a4760f7cd6b25578 (patch) | |
| tree | 99bb1c319c02648e6248b6c307c1fd7394455d8f /crates | |
| parent | 06d2d2f869cd4c8c48898fae28fb91d6716e35c2 (diff) | |
| download | php-shirabe-ac46dc8e98629719370c98e0a4760f7cd6b25578.tar.gz php-shirabe-ac46dc8e98629719370c98e0a4760f7cd6b25578.tar.zst php-shirabe-ac46dc8e98629719370c98e0a4760f7cd6b25578.zip | |
feat(port): port Zip.php
Add zip crate dependency and ZipArchive stub to shirabe-php-shim.
Also add dirname and stream_get_contents shim functions.
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/shirabe-php-shim/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/shirabe-php-shim/src/lib.rs | 43 | ||||
| -rw-r--r-- | crates/shirabe/src/util/zip.rs | 109 |
3 files changed, 153 insertions, 0 deletions
diff --git a/crates/shirabe-php-shim/Cargo.toml b/crates/shirabe-php-shim/Cargo.toml index 4256d47..64bbec3 100644 --- a/crates/shirabe-php-shim/Cargo.toml +++ b/crates/shirabe-php-shim/Cargo.toml @@ -5,3 +5,4 @@ edition.workspace = true [dependencies] indexmap.workspace = true +zip.workspace = true diff --git a/crates/shirabe-php-shim/src/lib.rs b/crates/shirabe-php-shim/src/lib.rs index 27857a5..d129479 100644 --- a/crates/shirabe-php-shim/src/lib.rs +++ b/crates/shirabe-php-shim/src/lib.rs @@ -254,10 +254,53 @@ impl PharData { } } +#[derive(Debug)] +pub struct ZipArchive { + pub num_files: i64, +} + +impl ZipArchive { + pub fn new() -> Self { + todo!() + } + + pub fn open(&mut self, filename: &str) -> bool { + todo!() + } + + pub fn close(&self) { + todo!() + } + + pub fn locate_name(&self, name: &str) -> Option<i64> { + todo!() + } + + pub fn get_from_index(&self, index: i64) -> Option<String> { + todo!() + } + + pub fn get_name_index(&self, index: i64) -> String { + todo!() + } + + pub fn get_stream(&self, name: &str) -> Option<PhpMixed> { + todo!() + } +} + pub trait JsonSerializable { fn json_serialize(&self) -> PhpMixed; } +pub fn dirname(path: &str) -> String { + todo!() +} + +pub fn stream_get_contents(stream: PhpMixed) -> Option<String> { + todo!() +} + pub fn class_exists(name: &str) -> bool { todo!() } diff --git a/crates/shirabe/src/util/zip.rs b/crates/shirabe/src/util/zip.rs index 583d397..296345a 100644 --- a/crates/shirabe/src/util/zip.rs +++ b/crates/shirabe/src/util/zip.rs @@ -1 +1,110 @@ //! ref: composer/src/Composer/Util/Zip.php + +use anyhow::Result; +use indexmap::IndexMap; +use shirabe_php_shim::{dirname, extension_loaded, implode, stream_get_contents, RuntimeException, ZipArchive}; + +pub struct Zip; + +impl Zip { + pub fn get_composer_json(path_to_zip: &str) -> Result<Option<String>> { + if !extension_loaded("zip") { + return Err(RuntimeException { + message: "The Zip Util requires PHP's zip extension".to_string(), + code: 0, + } + .into()); + } + + let mut zip = ZipArchive::new(); + if !zip.open(path_to_zip) { + return Ok(None); + } + + if zip.num_files == 0 { + zip.close(); + return Ok(None); + } + + let found_file_index = Self::locate_file(&zip, "composer.json")?; + + let mut content: Option<String> = None; + let configuration_file_name = zip.get_name_index(found_file_index); + let stream = zip.get_stream(&configuration_file_name); + + if stream.is_some() { + content = stream_get_contents(stream.unwrap()); + } + + zip.close(); + + Ok(content) + } + + fn locate_file(zip: &ZipArchive, filename: &str) -> Result<i64> { + // return root composer.json if it is there and is a file + if let Some(index) = zip.locate_name(filename) { + if zip.get_from_index(index).is_some() { + return Ok(index); + } + } + + let mut top_level_paths: IndexMap<String, bool> = IndexMap::new(); + for i in 0..zip.num_files { + let name = zip.get_name_index(i); + let dir_name = dirname(&name); + + // ignore OSX specific resource fork folder + if name.contains("__MACOSX") { + continue; + } + + // handle archives with proper TOC + if dir_name == "." { + top_level_paths.insert(name, true); + if top_level_paths.len() > 1 { + return Err(RuntimeException { + message: format!( + "Archive has more than one top level directories, and no composer.json was found on the top level, so it's an invalid archive. Top level paths found were: {}", + implode(",", &top_level_paths.keys().cloned().collect::<Vec<_>>()) + ), + code: 0, + } + .into()); + } + continue; + } + + // handle archives which do not have a TOC record for the directory itself + if !dir_name.contains('\\') && !dir_name.contains('/') { + top_level_paths.insert(format!("{}/", dir_name), true); + if top_level_paths.len() > 1 { + return Err(RuntimeException { + message: format!( + "Archive has more than one top level directories, and no composer.json was found on the top level, so it's an invalid archive. Top level paths found were: {}", + implode(",", &top_level_paths.keys().cloned().collect::<Vec<_>>()) + ), + code: 0, + } + .into()); + } + } + } + + if !top_level_paths.is_empty() { + let first_key = top_level_paths.keys().next().unwrap().clone(); + if let Some(index) = zip.locate_name(&format!("{}{}", first_key, filename)) { + if zip.get_from_index(index).is_some() { + return Ok(index); + } + } + } + + Err(RuntimeException { + message: "No composer.json found either at the top level or within the topmost directory" + .to_string(), + code: 0, + } + .into()) + } +} |
