aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-shim/src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-09 07:19:49 +0900
committernsfisis <nsfisis@gmail.com>2026-08-09 07:19:49 +0900
commitae19de8597f41c83983e6e6ca35f31d845f16bd3 (patch)
treec9050af966b680dc9ddda3dbb3fa8180fbae6e4a /crates/shirabe-php-shim/src
parent6b83e68d7961f150ec15dd59d457d519e0bf8663 (diff)
downloadphp-shirabe-ae19de8597f41c83983e6e6ca35f31d845f16bd3.tar.gz
php-shirabe-ae19de8597f41c83983e6e6ca35f31d845f16bd3.tar.zst
php-shirabe-ae19de8597f41c83983e6e6ca35f31d845f16bd3.zip
feat(archiver): preserve file permissions in zip archives
The shim reported setExternalAttributesName as missing, so ZipArchiver took the branch for libzip below 0.11.2 and every archived entry got the zip crate's default mode. The attributes now travel as arguments to add_file and add_empty_dir, because the crate fixes an entry's external attributes when the entry is started and offers no way to amend one already written. unix_permissions keeps only the low 9 mode bits, on top of which the crate restores S_IFREG and S_IFDIR, so setuid/setgid/sticky bits and other file types are still dropped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-shim/src')
-rw-r--r--crates/shirabe-php-shim/src/runtime.rs7
-rw-r--r--crates/shirabe-php-shim/src/zip.rs36
2 files changed, 25 insertions, 18 deletions
diff --git a/crates/shirabe-php-shim/src/runtime.rs b/crates/shirabe-php-shim/src/runtime.rs
index c00c8f51..13513b9d 100644
--- a/crates/shirabe-php-shim/src/runtime.rs
+++ b/crates/shirabe-php-shim/src/runtime.rs
@@ -77,14 +77,9 @@ pub fn defined(name: &str) -> bool {
// `Composer\Autoload\ClassLoader` (and its `getRegisteredLoaders`) is absent from the running
// process; the class-name form therefore reports no such method. The object form needs runtime
// reflection that PhpMixed::Object does not carry.
-pub fn method_exists(object_or_class: &PhpMixed, method_name: &str) -> bool {
+pub fn method_exists(object_or_class: &PhpMixed, _method_name: &str) -> bool {
match object_or_class {
PhpMixed::String(_) => false,
- // `ZipArchiver::archive` guards `$zip->setExternalAttributesName(...)` with
- // `method_exists($zip, 'setExternalAttributesName')` (only available with libzip >= 0.11.2).
- // The `zip`-crate-backed shim cannot amend an already-written entry's external attributes,
- // so the method is deliberately absent here and the guard reports it missing.
- PhpMixed::Null if method_name == "setExternalAttributesName" => false,
_ => todo!(),
}
}
diff --git a/crates/shirabe-php-shim/src/zip.rs b/crates/shirabe-php-shim/src/zip.rs
index 7284b029..f028ffd0 100644
--- a/crates/shirabe-php-shim/src/zip.rs
+++ b/crates/shirabe-php-shim/src/zip.rs
@@ -218,17 +218,23 @@ impl ZipArchive {
)))
}
- pub fn add_empty_dir(&self, local_name: &str) -> bool {
+ pub fn add_empty_dir(&self, local_name: &str, opsys: i64, attr: i64) -> bool {
let mut state = self.state.borrow_mut();
let ZipState::Writer { writer, .. } = &mut *state else {
return false;
};
writer
- .add_directory(local_name, SimpleFileOptions::default())
+ .add_directory(local_name, Self::entry_options(opsys, attr))
.is_ok()
}
- pub fn add_file(&self, filepath: impl AsRef<std::path::Path>, local_name: &str) -> bool {
+ pub fn add_file(
+ &self,
+ filepath: impl AsRef<std::path::Path>,
+ local_name: &str,
+ opsys: i64,
+ attr: i64,
+ ) -> bool {
let contents = match std::fs::read(filepath.as_ref()) {
Ok(c) => c,
Err(_) => return false,
@@ -238,21 +244,27 @@ impl ZipArchive {
return false;
};
let options =
- SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated);
+ Self::entry_options(opsys, attr).compression_method(zip::CompressionMethod::Deflated);
if writer.start_file(local_name, options).is_err() {
return false;
}
std::io::Write::write_all(writer, &contents).is_ok()
}
- pub fn set_external_attributes_name(&self, _name: &str, _opsys: i64, _attr: i64) -> bool {
- // TODO(phase-c): PHP's setExternalAttributesName mutates an already-added
- // entry's external attributes (e.g. Unix permissions) after addFile. The
- // `zip` crate fixes external attributes at start_file time via FileOptions
- // and exposes no API to amend a written entry, so this cannot be faithfully
- // reproduced without re-architecting add_file. Left unimplemented rather
- // than silently dropping the permission bits.
- todo!()
+ /// `opsys` and `attr` carry what PHP passes to `setExternalAttributesName`, which
+ /// amends an entry after `addFile`. The `zip` crate fixes external attributes when
+ /// the entry is started, so they are taken up front instead. `unix_permissions`
+ /// keeps only the low 9 mode bits, on top of which the crate restores `S_IFREG` for
+ /// files and `S_IFDIR` for directories; libzip stores `attr` verbatim, so
+ /// setuid/setgid/sticky bits and the remaining file types do not survive.
+ fn entry_options(opsys: i64, attr: i64) -> SimpleFileOptions {
+ let system = match opsys {
+ Self::OPSYS_UNIX => zip::System::Unix,
+ _ => todo!(),
+ };
+ SimpleFileOptions::default()
+ .system(system)
+ .unix_permissions((attr >> 16) as u32)
}
pub fn get_status_string(&self) -> String {