aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-07-26 00:54:21 +0900
committernsfisis <nsfisis@gmail.com>2026-07-26 00:54:21 +0900
commit8e8a3c147aa388c4b0fc021a08b30113eb590727 (patch)
treed48ce77e051538b6746c2b41028b674149b5f464 /crates
parent9bf862774b0bae92967f007f4d49597b73d70bb1 (diff)
downloadphp-shirabe-8e8a3c147aa388c4b0fc021a08b30113eb590727.tar.gz
php-shirabe-8e8a3c147aa388c4b0fc021a08b30113eb590727.tar.zst
php-shirabe-8e8a3c147aa388c4b0fc021a08b30113eb590727.zip
feat(diagnose-command): report the real curl version
The line was a "TODO: curl_version()" placeholder. Extend the diagnose payload with curl_version() and the CURL_* constants getCurlVersion() consults, so the libz/brotli/zstd/ssl/HTTP details come from the PHP runtime instead of being guessed. curl_version() is only reachable while the extension is loaded, mirroring the ioncube_loader_* entries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates')
-rw-r--r--crates/shirabe-php-rpc/php/worker.php19
-rw-r--r--crates/shirabe-php-rpc/src/lib.rs50
-rw-r--r--crates/shirabe/src/command/diagnose_command.rs48
3 files changed, 114 insertions, 3 deletions
diff --git a/crates/shirabe-php-rpc/php/worker.php b/crates/shirabe-php-rpc/php/worker.php
index 1e5b726d..6c3ab443 100644
--- a/crates/shirabe-php-rpc/php/worker.php
+++ b/crates/shirabe-php-rpc/php/worker.php
@@ -99,6 +99,24 @@ $dispatch = [
$ini[$setting] = $value === false ? null : $value;
}
+ // curl_version() only exists while the extension is loaded; DiagnoseCommand reads these
+ // details only after its own extension_loaded('curl') check.
+ $curl = null;
+ if (extension_loaded('curl')) {
+ $version = curl_version();
+ $curl = [
+ 'version' => (string) ($version['version'] ?? ''),
+ 'libz_version' => $version['libz_version'] ?? null,
+ 'brotli_version' => $version['brotli_version'] ?? null,
+ 'ssl_version' => $version['ssl_version'] ?? null,
+ 'features' => $version['features'] ?? null,
+ 'version_zstd' => defined('CURL_VERSION_ZSTD') ? CURL_VERSION_ZSTD : null,
+ 'version_http2' => defined('CURL_VERSION_HTTP2') ? CURL_VERSION_HTTP2 : null,
+ 'has_http_version_2_0' => defined('CURL_HTTP_VERSION_2_0'),
+ 'version_http3' => defined('CURL_VERSION_HTTP3') ? CURL_VERSION_HTTP3 : null,
+ ];
+ }
+
ob_start();
phpinfo(INFO_GENERAL);
$phpinfo = (string) ob_get_clean();
@@ -115,6 +133,7 @@ $dispatch = [
'ioncube_loader_iversion' => extension_loaded('ionCube Loader') ? ioncube_loader_iversion() : 0,
'ioncube_loader_version' => extension_loaded('ionCube Loader') ? ioncube_loader_version() : '',
'phpinfo_general' => $phpinfo,
+ 'curl' => $curl,
'extensions' => $extensions,
'functions' => $functions,
'ini' => $ini,
diff --git a/crates/shirabe-php-rpc/src/lib.rs b/crates/shirabe-php-rpc/src/lib.rs
index ab39c251..7ec1f925 100644
--- a/crates/shirabe-php-rpc/src/lib.rs
+++ b/crates/shirabe-php-rpc/src/lib.rs
@@ -60,6 +60,21 @@ pub fn get_extension_info(name: &str) -> String {
}
}
+/// `curl_version()`, together with the `CURL_*` constants the `diagnose` command consults. Every
+/// `Option` field is `None` when the corresponding array key or constant is absent.
+#[derive(Debug)]
+pub struct Curl {
+ pub version: String,
+ pub libz_version: Option<String>,
+ pub brotli_version: Option<String>,
+ pub ssl_version: Option<String>,
+ pub features: Option<i64>,
+ pub version_zstd: Option<i64>,
+ pub version_http2: Option<i64>,
+ pub has_http_version_2_0: bool,
+ pub version_http3: Option<i64>,
+}
+
/// Everything the `diagnose` command needs to know about the PHP runtime, fetched in a single
/// round trip because the command would otherwise probe the same runtime dozens of times.
#[derive(Debug)]
@@ -82,6 +97,8 @@ pub struct Diagnostics {
pub ioncube_loader_version: String,
/// `phpinfo(INFO_GENERAL)` output, captured via `ob_start()`/`ob_get_clean()`.
pub phpinfo_general: String,
+ /// `None` when the curl extension is not loaded.
+ pub curl: Option<Curl>,
extensions: IndexMap<String, bool>,
functions: IndexMap<String, bool>,
ini_settings: IndexMap<String, Option<String>>,
@@ -138,6 +155,7 @@ pub fn get_diagnostics() -> &'static Diagnostics {
ioncube_loader_iversion: int_field(payload, "ioncube_loader_iversion"),
ioncube_loader_version: string_field(payload, "ioncube_loader_version"),
phpinfo_general: string_field(payload, "phpinfo_general"),
+ curl: curl_field(payload, "curl"),
extensions: map_field(payload, "extensions")
.iter()
.map(|(name, value)| (name.clone(), as_bool(value, name)))
@@ -199,6 +217,38 @@ fn as_bool(value: &PhpMixed, key: &str) -> bool {
}
}
+fn nullable_int_field(payload: &IndexMap<String, PhpMixed>, key: &str) -> Option<i64> {
+ match field(payload, key) {
+ PhpMixed::Int(n) => Some(*n),
+ PhpMixed::Null => None,
+ other => {
+ panic!("PHP RPC: `diagnose` payload entry `{key}` is not an int or null: {other:?}")
+ }
+ }
+}
+
+fn curl_field(payload: &IndexMap<String, PhpMixed>, key: &str) -> Option<Curl> {
+ let curl = match field(payload, key) {
+ PhpMixed::Null => return None,
+ PhpMixed::Array(map) => map,
+ other => {
+ panic!("PHP RPC: `diagnose` payload entry `{key}` is not an array or null: {other:?}")
+ }
+ };
+
+ Some(Curl {
+ version: string_field(curl, "version"),
+ libz_version: nullable_string_field(curl, "libz_version"),
+ brotli_version: nullable_string_field(curl, "brotli_version"),
+ ssl_version: nullable_string_field(curl, "ssl_version"),
+ features: nullable_int_field(curl, "features"),
+ version_zstd: nullable_int_field(curl, "version_zstd"),
+ version_http2: nullable_int_field(curl, "version_http2"),
+ has_http_version_2_0: bool_field(curl, "has_http_version_2_0"),
+ version_http3: nullable_int_field(curl, "version_http3"),
+ })
+}
+
fn as_nullable_string(value: &PhpMixed, key: &str) -> Option<String> {
match value {
PhpMixed::String(s) => Some(s.clone()),
diff --git a/crates/shirabe/src/command/diagnose_command.rs b/crates/shirabe/src/command/diagnose_command.rs
index 178b4e23..3f69722a 100644
--- a/crates/shirabe/src/command/diagnose_command.rs
+++ b/crates/shirabe/src/command/diagnose_command.rs
@@ -1052,9 +1052,51 @@ impl DiagnoseCommand {
return "<error>disabled via disable_functions, using php streams fallback, which reduces performance</error>".to_string();
}
- // TODO(phase-d): Shirabe does not use cURL, we will consider what should be shown here
- // later.
- return "TODO: curl_version()".to_string();
+ let version = shirabe_php_rpc::get_diagnostics()
+ .curl
+ .as_ref()
+ .expect("the diagnose payload carries curl details while the extension is loaded");
+ let libz_version = version
+ .libz_version
+ .as_deref()
+ .filter(|v| !v.is_empty())
+ .unwrap_or("missing");
+ let brotli_version = version
+ .brotli_version
+ .as_deref()
+ .filter(|v| !v.is_empty())
+ .unwrap_or("missing");
+ let ssl_version = version
+ .ssl_version
+ .as_deref()
+ .filter(|v| !v.is_empty())
+ .unwrap_or("missing");
+ let has_zstd = match (version.features, version.version_zstd) {
+ (Some(features), Some(zstd)) => features & zstd != 0,
+ _ => false,
+ };
+ let mut http_versions = "1.0, 1.1".to_string();
+ if let (Some(features), Some(http2)) = (version.features, version.version_http2)
+ && version.has_http_version_2_0
+ && http2 & features != 0
+ {
+ http_versions.push_str(", 2");
+ }
+ if let (Some(features), Some(http3)) = (version.features, version.version_http3)
+ && features & http3 != 0
+ {
+ http_versions.push_str(", 3");
+ }
+
+ return format!(
+ "<comment>{}</comment> libz <comment>{}</comment> brotli <comment>{}</comment> zstd <comment>{}</comment> ssl <comment>{}</comment> HTTP <comment>{}</comment>",
+ version.version,
+ libz_version,
+ brotli_version,
+ if has_zstd { "supported" } else { "missing" },
+ ssl_version,
+ http_versions,
+ );
}
"<error>missing, using php streams fallback, which reduces performance</error>".to_string()