diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-20 22:11:59 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-20 22:25:15 +0900 |
| commit | 8538a375a4ddcfad3fabc8d19ca41c148136b63b (patch) | |
| tree | e5b93b856be1ae8cddb4973484f94d03731c246e /crates/shirabe-php-shim | |
| parent | 6a5484cbf8e75c99098e5fba98a1c5e4c1b7c2f2 (diff) | |
| download | php-shirabe-8538a375a4ddcfad3fabc8d19ca41c148136b63b.tar.gz php-shirabe-8538a375a4ddcfad3fabc8d19ca41c148136b63b.tar.zst php-shirabe-8538a375a4ddcfad3fabc8d19ca41c148136b63b.zip | |
feat(php-shim): implement parse_url and filter_var URL/boolean filters
Back parse_url/parse_url_all with reqwest::Url and add the
FILTER_VALIDATE_URL and FILTER_VALIDATE_BOOLEAN arms to filter_var, so
Config::prohibit_url_by_config no longer hits todo!(). reqwest::Url is
not a byte-for-byte port of PHP's parser; the divergences are noted with
TODO(phase-c).
Switch the scheme in_array in prohibit_url_by_config to strict: the
needle is a string-or-null and the haystack holds only non-numeric
string literals, so loose and strict comparison are provably equivalent
here, avoiding a dependency on PHP loose `==` semantics.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-shim')
| -rw-r--r-- | crates/shirabe-php-shim/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/shirabe-php-shim/src/lib.rs | 89 |
2 files changed, 84 insertions, 6 deletions
diff --git a/crates/shirabe-php-shim/Cargo.toml b/crates/shirabe-php-shim/Cargo.toml index abfe89e..797191e 100644 --- a/crates/shirabe-php-shim/Cargo.toml +++ b/crates/shirabe-php-shim/Cargo.toml @@ -9,6 +9,7 @@ chrono.workspace = true fastrand.workspace = true indexmap.workspace = true regex.workspace = true +reqwest.workspace = true serde.workspace = true serde_json.workspace = true zip.workspace = true diff --git a/crates/shirabe-php-shim/src/lib.rs b/crates/shirabe-php-shim/src/lib.rs index d47a14a..4bb309e 100644 --- a/crates/shirabe-php-shim/src/lib.rs +++ b/crates/shirabe-php-shim/src/lib.rs @@ -750,12 +750,71 @@ pub fn fclose(_file: PhpMixed) { todo!() } -pub fn parse_url(_url: &str, _component: i64) -> PhpMixed { - todo!() +pub fn parse_url(url: &str, component: i64) -> PhpMixed { + let all = parse_url_all(url); + let map = match all.as_array() { + Some(map) => map, + // parse_url_all already collapsed a malformed URL to false; propagate it. + None => return all, + }; + let key = match component { + PHP_URL_SCHEME => "scheme", + PHP_URL_HOST => "host", + PHP_URL_PORT => "port", + PHP_URL_USER => "user", + PHP_URL_PASS => "pass", + PHP_URL_PATH => "path", + PHP_URL_QUERY => "query", + PHP_URL_FRAGMENT => "fragment", + _ => return PhpMixed::Null, + }; + map.get(key).cloned().unwrap_or(PhpMixed::Null) } -pub fn parse_url_all(_url: &str) -> PhpMixed { - todo!() +pub fn parse_url_all(url: &str) -> PhpMixed { + // TODO(phase-c): PHP's parse_url uses php_url_parse_ex, which accepts relative + // and partial URLs and leaves an absent component absent. reqwest::Url + // (WHATWG/RFC 3986) requires an absolute URL, lowercases the host of special + // schemes, and normalizes the path (e.g. "http://host" yields path "/"). This + // is therefore not a byte-for-byte compatible port of parse_url. + let parsed = match reqwest::Url::parse(url) { + Ok(parsed) => parsed, + Err(_) => return PhpMixed::Bool(false), + }; + let mut map: IndexMap<String, PhpMixed> = IndexMap::new(); + map.insert( + "scheme".to_string(), + PhpMixed::String(parsed.scheme().to_string()), + ); + if let Some(host) = parsed.host_str() { + map.insert("host".to_string(), PhpMixed::String(host.to_string())); + } + if let Some(port) = parsed.port() { + map.insert("port".to_string(), PhpMixed::Int(port as i64)); + } + if !parsed.username().is_empty() { + map.insert( + "user".to_string(), + PhpMixed::String(parsed.username().to_string()), + ); + } + if let Some(pass) = parsed.password() { + map.insert("pass".to_string(), PhpMixed::String(pass.to_string())); + } + let path = parsed.path(); + if !path.is_empty() { + map.insert("path".to_string(), PhpMixed::String(path.to_string())); + } + if let Some(query) = parsed.query() { + map.insert("query".to_string(), PhpMixed::String(query.to_string())); + } + if let Some(fragment) = parsed.fragment() { + map.insert( + "fragment".to_string(), + PhpMixed::String(fragment.to_string()), + ); + } + PhpMixed::Array(map) } pub fn pathinfo(path: PhpMixed, option: i64) -> PhpMixed { @@ -1238,8 +1297,26 @@ pub const FILTER_VALIDATE_URL: i64 = 273; pub const FILTER_VALIDATE_IP: i64 = 275; pub const FILTER_VALIDATE_INT: i64 = 257; -pub fn filter_var(_value: &str, _filter: i64) -> bool { - todo!() +pub fn filter_var(value: &str, filter: i64) -> bool { + match filter { + // Without FILTER_NULL_ON_FAILURE, php_filter_boolean trims surrounding + // whitespace, lowercases, and yields true only for "1"/"true"/"on"/"yes"; + // every other input (including the "0"/"false"/"off"/"no"/"" set) yields + // false. + FILTER_VALIDATE_BOOLEAN => { + let trimmed = value.trim_matches([' ', '\t', '\n', '\r', '\0', '\x0B']); + matches!( + trimmed.to_ascii_lowercase().as_str(), + "1" | "true" | "on" | "yes" + ) + } + // TODO(phase-c): PHP's FILTER_VALIDATE_URL parses with php_url_parse_ex and + // additionally validates the host as a domain/IPv6 literal. reqwest::Url + // (WHATWG/RFC 3986) is stricter on some inputs and more lenient on others, + // so this is not a byte-for-byte compatible validator. + FILTER_VALIDATE_URL => reqwest::Url::parse(value).is_ok(), + _ => todo!(), + } } // Models the configuration of a standard PHP CLI environment. Settings belonging |
