aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-15 00:58:38 +0900
committernsfisis <nsfisis@gmail.com>2026-05-15 19:51:17 +0900
commit38a4e30dee2c8c806afc67b77363da88c0f21c49 (patch)
tree8512bfa71e44b826e52caaa346658ab993fe16f3
parenta9d321f731b13b6cdbd3f8069d6bd60558038edf (diff)
downloadphp-shirabe-38a4e30dee2c8c806afc67b77363da88c0f21c49.tar.gz
php-shirabe-38a4e30dee2c8c806afc67b77363da88c0f21c49.tar.zst
php-shirabe-38a4e30dee2c8c806afc67b77363da88c0f21c49.zip
feat(port): port ZipArchiver.php
-rw-r--r--crates/shirabe-php-shim/src/lib.rs4
-rw-r--r--crates/shirabe/src/package/archiver/archiver_interface.rs2
-rw-r--r--crates/shirabe/src/package/archiver/zip_archiver.rs97
3 files changed, 102 insertions, 1 deletions
diff --git a/crates/shirabe-php-shim/src/lib.rs b/crates/shirabe-php-shim/src/lib.rs
index 393a485..68cf03a 100644
--- a/crates/shirabe-php-shim/src/lib.rs
+++ b/crates/shirabe-php-shim/src/lib.rs
@@ -134,6 +134,10 @@ pub fn hash_raw(algo: &str, data: &str) -> Vec<u8> {
todo!()
}
+pub fn pack(format: &str, values: &[PhpMixed]) -> Vec<u8> {
+ todo!()
+}
+
pub fn unpack(format: &str, data: &[u8]) -> Option<IndexMap<String, Box<PhpMixed>>> {
todo!()
}
diff --git a/crates/shirabe/src/package/archiver/archiver_interface.rs b/crates/shirabe/src/package/archiver/archiver_interface.rs
index 79299af..82e976d 100644
--- a/crates/shirabe/src/package/archiver/archiver_interface.rs
+++ b/crates/shirabe/src/package/archiver/archiver_interface.rs
@@ -8,7 +8,7 @@ pub trait ArchiverInterface {
format: String,
excludes: Vec<String>,
ignore_filters: bool,
- ) -> String;
+ ) -> anyhow::Result<String>;
fn supports(&self, format: String, source_type: Option<String>) -> bool;
}
diff --git a/crates/shirabe/src/package/archiver/zip_archiver.rs b/crates/shirabe/src/package/archiver/zip_archiver.rs
index bd15193..ab7c284 100644
--- a/crates/shirabe/src/package/archiver/zip_archiver.rs
+++ b/crates/shirabe/src/package/archiver/zip_archiver.rs
@@ -1 +1,98 @@
//! ref: composer/src/Composer/Package/Archiver/ZipArchiver.php
+
+use indexmap::IndexMap;
+use shirabe_php_shim::{class_exists, fileperms, method_exists, pack, realpath, PhpMixed, RuntimeException, ZipArchive};
+use crate::package::archiver::archivable_files_finder::ArchivableFilesFinder;
+use crate::package::archiver::archiver_interface::ArchiverInterface;
+use crate::util::filesystem::Filesystem;
+use crate::util::platform::Platform;
+
+#[derive(Debug)]
+pub struct ZipArchiver;
+
+impl ZipArchiver {
+ fn formats() -> IndexMap<String, bool> {
+ let mut map = IndexMap::new();
+ map.insert("zip".to_string(), true);
+ map
+ }
+
+ fn compression_available(&self) -> bool {
+ class_exists("ZipArchive")
+ }
+}
+
+impl ArchiverInterface for ZipArchiver {
+ fn archive(
+ &self,
+ sources: String,
+ target: String,
+ format: String,
+ excludes: Vec<String>,
+ ignore_filters: bool,
+ ) -> anyhow::Result<String> {
+ let fs = Filesystem::new();
+ let sources_realpath = realpath(&sources);
+ let sources = if let Some(p) = sources_realpath {
+ p
+ } else {
+ sources
+ };
+ let sources = fs.normalize_path(&sources);
+
+ let mut zip = ZipArchive::new();
+ let res = zip.open(&target, ZipArchive::CREATE);
+ if res == true {
+ let files = ArchivableFilesFinder::new(&sources, excludes, ignore_filters)?;
+ for file in files {
+ let filepath = file.get_pathname();
+ let mut relative_path = file.get_relative_pathname();
+
+ if Platform::is_windows() {
+ relative_path = shirabe_php_shim::strtr(&relative_path, "\\", "/");
+ }
+
+ if file.is_dir() {
+ zip.add_empty_dir(&relative_path);
+ } else {
+ zip.add_file(&filepath, &relative_path);
+ }
+
+ // setExternalAttributesName() is only available with libzip 0.11.2 or above
+ if method_exists(&PhpMixed::Null, "setExternalAttributesName") {
+ let perms = fileperms(&filepath);
+ zip.set_external_attributes_name(&relative_path, ZipArchive::OPSYS_UNIX, perms << 16);
+ }
+ }
+ if zip.close() {
+ if !std::path::Path::new(&target).exists() {
+ // create minimal valid ZIP file (Empty Central Directory + End of Central Directory record)
+ let eocd = pack("VvvvvVVv", &[
+ PhpMixed::Int(0x06054b50), // End of central directory signature
+ PhpMixed::Int(0), // Number of this disk
+ PhpMixed::Int(0), // Disk where central directory starts
+ PhpMixed::Int(0), // Number of central directory records on this disk
+ PhpMixed::Int(0), // Total number of central directory records
+ PhpMixed::Int(0), // Size of central directory (bytes)
+ PhpMixed::Int(0), // Offset of start of central directory
+ PhpMixed::Int(0), // Comment length
+ ]);
+ std::fs::write(&target, &eocd)?;
+ }
+
+ return Ok(target);
+ }
+ }
+ let message = format!(
+ "Could not create archive '{}' from '{}': {}",
+ target,
+ sources,
+ zip.get_status_string()
+ );
+ Err(RuntimeException { message, code: 0 }.into())
+ }
+
+ fn supports(&self, format: String, _source_type: Option<String>) -> bool {
+ Self::formats().contains_key(&format) && self.compression_available()
+ }
+}