From b8d46b0495d00815a699932ced0b43955c949ab9 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 1 Aug 2026 04:52:50 +0900 Subject: feat(php-src): add a BSD-licensed crate for php-src derived code The audit in .ken/php-shim-copying.md judged 14 functions in shirabe-php-shim (plus php_wordwrap in shirabe-external-packages) to be line-by-line transcriptions or structural imitations of php-src. PHP's relicensing to 3-clause BSD makes keeping them legal, but the boundary between BSD-derived and MIT code was invisible in the source tree. Moving them into their own crate puts the license into the build metadata (so NOTICE generation follows the binary), makes a reverse dependency a compile error, and encodes the origin in the module path, which mirrors php-src's ext tree. Each function records its origin in a fixed-format doc comment, and a new php_src_derivation_boundary linter fails if `php-src` appears in any Rust source outside the crate. Public paths under shirabe_php_shim:: are unchanged: functions that are themselves derived are re-exported with `pub use`, and the wrappers that only validate arguments stay on the MIT side. This also resolves the duplicate wordwrap implementation. shirabe_php_shim::wordwrap was todo!(), so SymfonyStyle::block panicked, while shirabe-external-packages carried its own copy. Both now go through the single port, verified against real PHP on 13 cases covering multi-character breaks and cut. Co-Authored-By: Claude Opus 5 (1M context) --- crates/shirabe-php-shim/src/process.rs | 39 +--------------------------------- 1 file changed, 1 insertion(+), 38 deletions(-) (limited to 'crates/shirabe-php-shim/src/process.rs') diff --git a/crates/shirabe-php-shim/src/process.rs b/crates/shirabe-php-shim/src/process.rs index 4615ff91..57d9b35a 100644 --- a/crates/shirabe-php-shim/src/process.rs +++ b/crates/shirabe-php-shim/src/process.rs @@ -1,5 +1,6 @@ use crate::{ChildPipe, PhpMixed, PhpResource, StreamBacking, StreamState}; use indexmap::IndexMap; +pub use shirabe_php_src::standard::exec::escapeshellcmd; pub const SIGINT: i64 = 2; pub const SIGTERM: i64 = 15; @@ -77,44 +78,6 @@ pub fn system(command: &str, result_code: Option<&mut i64>) -> Option { Some(lines.last().cloned().unwrap_or_default()) } -// Port of PHP's escapeshellcmd (Unix branch). Shell metacharacters are backslash-escaped; quote -// characters are escaped only when unpaired, paired quotes being left intact. -pub fn escapeshellcmd(command: &str) -> String { - let bytes = command.as_bytes(); - let len = bytes.len(); - let mut out: Vec = Vec::with_capacity(len); - // Byte index of the matching closing quote while inside a paired quote run. - let mut paired: Option = None; - let mut x = 0; - while x < len { - let c = bytes[x]; - match c { - b'"' | b'\'' => { - if paired.is_none() { - if let Some(rel) = bytes[x + 1..].iter().position(|&b| b == c) { - paired = Some(x + 1 + rel); - } else { - out.push(b'\\'); - } - } else if paired == Some(x) { - paired = None; - } else { - out.push(b'\\'); - } - out.push(c); - } - b'#' | b'&' | b';' | b'`' | b'|' | b'*' | b'?' | b'~' | b'<' | b'>' | b'^' | b'(' - | b')' | b'[' | b']' | b'{' | b'}' | b'$' | b'\\' | 0x0A | 0xFF => { - out.push(b'\\'); - out.push(c); - } - _ => out.push(c), - } - x += 1; - } - String::from_utf8_lossy(&out).into_owned() -} - // Unix branch of PHP's escapeshellarg: wrap in single quotes, escaping embedded single quotes. pub fn escapeshellarg(arg: &str) -> String { let mut out = String::with_capacity(arg.len() + 2); -- cgit v1.3.1