aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/shirabe-external-packages/src/composer/xdebug_handler/xdebug_handler.rs3
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/input/input_option.rs2
-rw-r--r--crates/shirabe-php-shim/src/lib.rs74
-rw-r--r--crates/shirabe-php-shim/src/preg.rs15
-rw-r--r--crates/shirabe/src/io/console_io.rs27
5 files changed, 96 insertions, 25 deletions
diff --git a/crates/shirabe-external-packages/src/composer/xdebug_handler/xdebug_handler.rs b/crates/shirabe-external-packages/src/composer/xdebug_handler/xdebug_handler.rs
index 9442d08..388d557 100644
--- a/crates/shirabe-external-packages/src/composer/xdebug_handler/xdebug_handler.rs
+++ b/crates/shirabe-external-packages/src/composer/xdebug_handler/xdebug_handler.rs
@@ -5,7 +5,8 @@ pub struct XdebugHandler;
impl XdebugHandler {
pub fn is_xdebug_active() -> bool {
- todo!()
+ // TODO(php-runtime)
+ false
}
pub fn get_skipped_version() -> Option<String> {
diff --git a/crates/shirabe-external-packages/src/symfony/console/input/input_option.rs b/crates/shirabe-external-packages/src/symfony/console/input/input_option.rs
index 4b3feb8..afb36a5 100644
--- a/crates/shirabe-external-packages/src/symfony/console/input/input_option.rs
+++ b/crates/shirabe-external-packages/src/symfony/console/input/input_option.rs
@@ -110,7 +110,7 @@ impl InputOption {
fn normalize_shortcut(s: String) -> anyhow::Result<Option<String>> {
let stripped = shirabe_php_shim::ltrim(&s, Some("-"));
- let parts = shirabe_php_shim::preg_split(r"\|(-?)", &stripped);
+ let parts = shirabe_php_shim::preg_split(r"{(\|)-?}", &stripped);
let filtered: Vec<String> =
shirabe_php_shim::array_filter(&parts, |s: &String| !s.is_empty());
let result = shirabe_php_shim::implode("|", &filtered);
diff --git a/crates/shirabe-php-shim/src/lib.rs b/crates/shirabe-php-shim/src/lib.rs
index c236727..cb99546 100644
--- a/crates/shirabe-php-shim/src/lib.rs
+++ b/crates/shirabe-php-shim/src/lib.rs
@@ -1592,9 +1592,9 @@ pub fn curl_handle_id(_handle: &CurlHandle) -> i64 {
todo!()
}
-pub fn restore_error_handler() {
- todo!()
-}
+// TODO(php-runtime): the previous handler should be restored in the PHP runtime.
+// Paired with set_error_handler, which is a no-op in this shim.
+pub fn restore_error_handler() {}
pub fn stream_get_contents_with_max(stream: PhpMixed, max_length: Option<i64>) -> Option<String> {
let _ = (stream, max_length);
@@ -1610,7 +1610,9 @@ pub fn is_dir(_path: &str) -> bool {
}
pub fn file_get_contents(_path: &str) -> Option<String> {
- todo!()
+ std::fs::read(_path)
+ .ok()
+ .map(|bytes| String::from_utf8_lossy(&bytes).into_owned())
}
pub fn file_get_contents5(
@@ -1795,6 +1797,12 @@ pub fn php_uname(mode: &str) -> String {
other => other,
}
.to_string(),
+ // TODO(phase-c): use libc?
+ // release, as reported by uname(2). On Linux this matches the contents
+ // of /proc/sys/kernel/osrelease.
+ "r" => std::fs::read_to_string("/proc/sys/kernel/osrelease")
+ .map(|s| s.trim_end().to_string())
+ .unwrap_or_default(),
_ => todo!(),
}
}
@@ -1921,8 +1929,44 @@ pub fn strcmp(_s1: &str, _s2: &str) -> i64 {
_s1.cmp(_s2) as i64
}
-pub fn rtrim(_s: &str, _chars: Option<&str>) -> String {
- todo!()
+/// PHP's default trim character mask: " \t\n\r\0\x0B".
+const PHP_TRIM_DEFAULT_CHARS: &[u8] = b" \t\n\r\0\x0B";
+
+/// Build the set of bytes to strip from a PHP trim `$characters` argument,
+/// expanding `a..b` range syntax as PHP does.
+fn php_trim_mask(chars: &[u8]) -> [bool; 256] {
+ let mut mask = [false; 256];
+ let mut i = 0;
+ while i < chars.len() {
+ if i + 3 < chars.len() && chars[i + 1] == b'.' && chars[i + 2] == b'.' {
+ let start = chars[i];
+ let end = chars[i + 3];
+ if start <= end {
+ for b in start..=end {
+ mask[b as usize] = true;
+ }
+ i += 4;
+ continue;
+ }
+ }
+ mask[chars[i] as usize] = true;
+ i += 1;
+ }
+ mask
+}
+
+pub fn rtrim(s: &str, chars: Option<&str>) -> String {
+ let mask = php_trim_mask(
+ chars
+ .map(|c| c.as_bytes())
+ .unwrap_or(PHP_TRIM_DEFAULT_CHARS),
+ );
+ let bytes = s.as_bytes();
+ let mut end = bytes.len();
+ while end > 0 && mask[bytes[end - 1] as usize] {
+ end -= 1;
+ }
+ String::from_utf8_lossy(&bytes[..end]).into_owned()
}
pub fn rmdir(_dir: &str) -> bool {
@@ -2227,7 +2271,11 @@ pub fn memory_get_usage() -> i64 {
}
pub fn mb_check_encoding(_value: &str, _encoding: &str) -> bool {
- todo!()
+ match _encoding.to_ascii_uppercase().replace('-', "").as_str() {
+ // A Rust &str is, by construction, valid UTF-8.
+ "UTF8" => true,
+ _ => todo!(),
+ }
}
pub fn iconv(_in_charset: &str, _out_charset: &str, _string: &str) -> Option<String> {
@@ -2379,7 +2427,17 @@ pub fn error_get_last() -> Option<IndexMap<String, Box<PhpMixed>>> {
}
pub fn is_readable(_path: &str) -> bool {
- todo!()
+ let path = std::path::Path::new(_path);
+ match std::fs::metadata(path) {
+ Ok(meta) => {
+ if meta.is_dir() {
+ std::fs::read_dir(path).is_ok()
+ } else {
+ std::fs::File::open(path).is_ok()
+ }
+ }
+ Err(_) => false,
+ }
}
pub fn stream_get_wrappers() -> Vec<String> {
diff --git a/crates/shirabe-php-shim/src/preg.rs b/crates/shirabe-php-shim/src/preg.rs
index 1429f1c..46d9428 100644
--- a/crates/shirabe-php-shim/src/preg.rs
+++ b/crates/shirabe-php-shim/src/preg.rs
@@ -393,12 +393,21 @@ fn compile_php_pattern(pattern: &str) -> anyhow::Result<regex::Regex> {
.chars()
.next()
.ok_or_else(|| anyhow::anyhow!("empty regex pattern"))?;
+ // PCRE allows bracket-style delimiters whose closing character differs from
+ // the opening one: `(...)`, `{...}`, `[...]`, `<...>`.
+ let closing = match delimiter {
+ '(' => ')',
+ '{' => '}',
+ '[' => ']',
+ '<' => '>',
+ c => c,
+ };
let end = pattern
- .rfind(delimiter)
- .filter(|&i| i > 0)
+ .rfind(closing)
+ .filter(|&i| i >= delimiter.len_utf8())
.ok_or_else(|| anyhow::anyhow!("unterminated regex pattern: {pattern}"))?;
let inner = &pattern[delimiter.len_utf8()..end];
- let modifiers = &pattern[end + delimiter.len_utf8()..];
+ let modifiers = &pattern[end + closing.len_utf8()..];
let flags: String = modifiers
.chars()
diff --git a/crates/shirabe/src/io/console_io.rs b/crates/shirabe/src/io/console_io.rs
index 3afd300..ecb12b1 100644
--- a/crates/shirabe/src/io/console_io.rs
+++ b/crates/shirabe/src/io/console_io.rs
@@ -275,10 +275,6 @@ impl ConsoleIO {
/// All other control chars (except NULL bytes) as well as ANSI escape sequences are removed.
///
/// Invalid unicode sequences are turned into question marks.
- ///
- /// @param string|iterable<string> $messages
- /// @return string|array<string>
- /// @phpstan-return ($messages is string ? string : array<string>)
pub fn sanitize(messages: PhpMixed, allow_newlines: bool) -> PhpMixed {
// Match ANSI escape sequences:
// - CSI (Control Sequence Introducer): ESC [ params intermediate final
@@ -286,17 +282,24 @@ impl ConsoleIO {
// - Other ESC sequences: ESC followed by any character
let escape_pattern =
r"\x1B\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]|\x1B\].*?(?:\x1B\\|\x07)|\x1B.";
- let pattern = if allow_newlines {
- format!(
- "{{{}|[\\x01-\\x09\\x0B\\x0C\\x0E-\\x1A]|\\r(?!\\n)}}u",
- escape_pattern
+ // Regex pattern compatibility:
+ // PCRE's `\r(?!\n)` (non-CRLF CR) uses a negative lookahead, which the regex crate does
+ // not support. Instead we capture `\r\n` and re-emit it via the `$1` backreference. A CRLF
+ // pair is preserved while a lone CR is removed.
+ let (pattern, replacement) = if allow_newlines {
+ (
+ format!(
+ r#"@{}|[\x01-\x09\x0B\x0C\x0E-\x1A]|(\r\n)|\r@u"#,
+ escape_pattern
+ ),
+ "$1",
)
} else {
- format!("{{{}|[\\x01-\\x1A]}}u", escape_pattern)
+ (format!("{{{}|[\\x01-\\x1A]}}u", escape_pattern), "")
};
if is_string(&messages) {
let message = Self::ensure_valid_utf8(messages.as_string().unwrap_or(""));
- return PhpMixed::String(Preg::replace(&pattern, "", &message));
+ return PhpMixed::String(Preg::replace(&pattern, replacement, &message));
}
// PHP: $sanitized = []; foreach ($messages as $key => $message) { ... }
@@ -307,7 +310,7 @@ impl ConsoleIO {
let s = Self::ensure_valid_utf8(message.as_string().unwrap_or(""));
sanitized.insert(
key.to_string(),
- PhpMixed::String(Preg::replace(&pattern, "", &s)),
+ PhpMixed::String(Preg::replace(&pattern, replacement, &s)),
);
}
}
@@ -316,7 +319,7 @@ impl ConsoleIO {
let s = Self::ensure_valid_utf8(message.as_string().unwrap_or(""));
sanitized.insert(
key.clone(),
- PhpMixed::String(Preg::replace(&pattern, "", &s)),
+ PhpMixed::String(Preg::replace(&pattern, replacement, &s)),
);
}
}