diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-15 07:37:45 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-15 07:37:45 +0900 |
| commit | 548463bad1f72c97f68b47f54a263a4f4ad87b3a (patch) | |
| tree | 1285d740611be3ad175f9e57c61d880b500dd818 /crates/shirabe-php-shim/src | |
| parent | ed8694f89eb7702eb7e617af29f8f444f32b8d3c (diff) | |
| download | php-shirabe-548463bad1f72c97f68b47f54a263a4f4ad87b3a.tar.gz php-shirabe-548463bad1f72c97f68b47f54a263a4f4ad87b3a.tar.zst php-shirabe-548463bad1f72c97f68b47f54a263a4f4ad87b3a.zip | |
feat(php-rpc): embed the Composer PHP runtime in the executable
Plugins and scripts need the real `Composer\` classes and the packages
Composer depends on, which so far came from a checkout found through
SHIRABE_COMPOSER_PHP_DIR or a path next to the workspace. Neither exists
for a distributed binary.
The build script now archives those PHP sources into a phar the way
Compiler.php does and the executable carries it. The worker maps it with
Phar::loadPhar and reads a content-addressed sentinel back to tell a
bundle it can use from one it cannot; where its PHP cannot open the phar,
the bundle is unpacked once into the cache directory and autoloaded from
there. SHIRABE_COMPOSER_PHP_DIR still overrides both for development.
PHP locates a phar's manifest by the first __HALT_COMPILER(); token in
the file, so the executable must hold no other copy of it: phar.rs builds
the token at run time, and a linter keeps further literals out of the
sources that reach the binary.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-shim/src')
| -rw-r--r-- | crates/shirabe-php-shim/src/phar.rs | 39 |
1 files changed, 36 insertions, 3 deletions
diff --git a/crates/shirabe-php-shim/src/phar.rs b/crates/shirabe-php-shim/src/phar.rs index dccdfebf..ecd842f3 100644 --- a/crates/shirabe-php-shim/src/phar.rs +++ b/crates/shirabe-php-shim/src/phar.rs @@ -286,15 +286,35 @@ fn verify_phar_signature(path: &std::path::Path, bytes: &[u8]) -> anyhow::Result Ok(()) } +/// The special token to separate a phar stub and contents. +/// +/// The Shirabe executable embeds the Composer runtime bundle as a phar archive, so the token must +/// not appear in the binary. See `docs/dev/composer-runtime-bundle.md`. We use `black_box()` to +/// prevent the Rust compiler from inlining the function to a constant. +fn halt_compiler_token() -> [u8; 18] { + let mut token = *std::hint::black_box(b"__UNYG_PBZCVYRE();"); + for byte in &mut token { + if byte.is_ascii_uppercase() { + *byte = b'A' + (*byte - b'A' + 13) % 26; + } + } + token +} + fn parse_native_phar(path: &std::path::Path) -> anyhow::Result<Vec<PharEntry>> { let bytes = std::fs::read(path) .map_err(|e| corruption_error(path, &format!("unable to open phar: {}", e)))?; - let halt = b"__HALT_COMPILER();"; + let halt = halt_compiler_token(); let halt_pos = bytes .windows(halt.len()) .position(|window| window == halt) - .ok_or_else(|| corruption_error(path, "__HALT_COMPILER(); not found in stub"))?; + .ok_or_else(|| { + corruption_error( + path, + &format!("{} not found in stub", String::from_utf8_lossy(&halt)), + ) + })?; let mut offset = halt_pos + halt.len(); for close_tag in [&b" ?>"[..], &b"\n?>"[..]] { if bytes[offset..].starts_with(close_tag) { @@ -846,6 +866,17 @@ mod tests { use super::*; use crate::Catch as _; + #[test] + fn halt_compiler_token_decodes() { + assert_eq!( + halt_compiler_token(), + [ + b'_', b'_', b'H', b'A', b'L', b'T', b'_', b'C', b'O', b'M', b'P', b'I', b'L', b'E', + b'R', b'(', b')', b';', + ], + ); + } + fn write_file(dir: &std::path::Path, name: &str, content: &[u8]) -> std::path::PathBuf { let path = dir.join(name); std::fs::create_dir_all(path.parent().unwrap()).unwrap(); @@ -971,7 +1002,9 @@ mod tests { manifest.extend_from_slice(&0u32.to_le_bytes()); } - let mut bytes = b"<?php __HALT_COMPILER(); ?>\r\n".to_vec(); + let mut bytes = b"<?php ".to_vec(); + bytes.extend_from_slice(&halt_compiler_token()); + bytes.extend_from_slice(b" ?>\r\n"); bytes.extend_from_slice(&(manifest.len() as u32).to_le_bytes()); bytes.extend_from_slice(&manifest); bytes.extend_from_slice(&stored.0); |
