aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-shim/src/compress.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-01 05:26:16 +0900
committernsfisis <nsfisis@gmail.com>2026-08-01 18:41:11 +0900
commit06d2c5c884eee0b5b663730f4d379a7ceae3a8e4 (patch)
tree5cd768e0bdad3f8a056dd9b047de363963315135 /crates/shirabe-php-shim/src/compress.rs
parentb8d46b0495d00815a699932ced0b43955c949ab9 (diff)
downloadphp-shirabe-06d2c5c884eee0b5b663730f4d379a7ceae3a8e4.tar.gz
php-shirabe-06d2c5c884eee0b5b663730f4d379a7ceae3a8e4.tar.zst
php-shirabe-06d2c5c884eee0b5b663730f4d379a7ceae3a8e4.zip
feat(php-shim): implement Phar/PharData and the zlib/bzip2 functions
Adopt the tar, flate2, and bzip2 crates to fill in the phar.rs and compress.rs todos: PharData tar/zip reading, building, and whole-archive compression, plus a native .phar reader that follows the php.net file-format manual and verifies hash-based signatures. Callers now propagate the constructor/extract errors PHP throws, and fwrite accepts byte strings so gzread no longer needs lossy UTF-8. The native .phar writing API stays todo!() (no call sites; Composer's Compiler is not ported) and OPENSSL phar signatures are accepted unverified (TODO(phase-c)). This unblocks Tar::getComposerJson and the tar/phar/gzip downloaders; tar_test (7), artifact_repository_test (2), and phar_archiver_test zip (1) are un-ignored. The archive command itself still panics because ArchiveManager::archive always generates glob excludes whose look-ahead regexes the regex crate cannot compile; converting those patterns to regex-compatible ones is a separate, still-undecided work item. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-shim/src/compress.rs')
-rw-r--r--crates/shirabe-php-shim/src/compress.rs74
1 files changed, 61 insertions, 13 deletions
diff --git a/crates/shirabe-php-shim/src/compress.rs b/crates/shirabe-php-shim/src/compress.rs
index 68230007..d62be7c6 100644
--- a/crates/shirabe-php-shim/src/compress.rs
+++ b/crates/shirabe-php-shim/src/compress.rs
@@ -1,25 +1,73 @@
-use crate::PhpMixed;
+use std::io::Read as _;
+use std::io::Write as _;
-pub fn gzopen(_file: &str, _mode: &str) -> PhpMixed {
- todo!()
+/// Handle returned by `gzopen()`. PHP models this as a zlib stream resource;
+/// clones share the same underlying stream, like PHP resource copies do.
+/// Only the read modes used by Composer are supported.
+#[derive(Debug, Clone)]
+pub struct GzFile(std::rc::Rc<std::cell::RefCell<flate2::read::MultiGzDecoder<std::fs::File>>>);
+
+pub fn gzopen(file: &str, mode: &str) -> Result<GzFile, std::io::Error> {
+ assert!(
+ mode.starts_with('r'),
+ "gzopen: only read modes are supported (got {mode:?})"
+ );
+ let f = std::fs::File::open(file)?;
+ Ok(GzFile(std::rc::Rc::new(std::cell::RefCell::new(
+ flate2::read::MultiGzDecoder::new(f),
+ ))))
}
-pub fn gzread(_file: PhpMixed, _length: i64) -> String {
- todo!()
+/// PHP `gzread()` returns up to `length` bytes of decompressed data, or `false` on
+/// error; the `false` case maps to an empty buffer, which stops the caller's read
+/// loop just as PHP's falsy check does.
+pub fn gzread(file: GzFile, length: i64) -> Vec<u8> {
+ let mut decoder = file.0.borrow_mut();
+ let mut buf = vec![0u8; length.max(0) as usize];
+ let mut filled = 0;
+ while filled < buf.len() {
+ match decoder.read(&mut buf[filled..]) {
+ Ok(0) => break,
+ Ok(n) => filled += n,
+ Err(_) => {
+ filled = 0;
+ break;
+ }
+ }
+ }
+ buf.truncate(filled);
+ buf
}
-pub fn gzclose(_file: PhpMixed) {
- todo!()
+pub fn gzclose(file: GzFile) {
+ drop(file);
}
-pub fn gzcompress(_data: &[u8]) -> Option<Vec<u8>> {
- todo!()
+/// PHP `gzcompress()` with the default level (-1 = zlib default) and ZLIB encoding.
+pub fn gzcompress(data: &[u8]) -> Option<Vec<u8>> {
+ let mut encoder = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
+ encoder.write_all(data).ok()?;
+ encoder.finish().ok()
}
-pub fn bzcompress(_data: &[u8]) -> Option<Vec<u8>> {
- todo!()
+/// PHP `bzcompress()` with the default block size (4).
+pub fn bzcompress(data: &[u8]) -> Option<Vec<u8>> {
+ let mut encoder = bzip2::write::BzEncoder::new(Vec::new(), bzip2::Compression::new(4));
+ encoder.write_all(data).ok()?;
+ encoder.finish().ok()
}
-pub fn zlib_decode(_data: &str) -> Option<String> {
- todo!()
+/// PHP `zlib_decode()` detects the encoding (gzip or zlib) from the data itself.
+pub fn zlib_decode(data: &[u8]) -> Option<Vec<u8>> {
+ let mut decoded = Vec::new();
+ if data.starts_with(&[0x1f, 0x8b]) {
+ flate2::read::MultiGzDecoder::new(data)
+ .read_to_end(&mut decoded)
+ .ok()?;
+ } else {
+ flate2::read::ZlibDecoder::new(data)
+ .read_to_end(&mut decoded)
+ .ok()?;
+ }
+ Some(decoded)
}