diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-11 00:07:15 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-11 00:08:05 +0900 |
| commit | 144d059b725e2d178d7f4a6403cde9474fe65dcc (patch) | |
| tree | 09f48dc2296a1c1e052018968b34fec31b364767 | |
| parent | b65e338d4cf06afb8237512e49a51e354277196d (diff) | |
| download | php-shirabe-144d059b725e2d178d7f4a6403cde9474fe65dcc.tar.gz php-shirabe-144d059b725e2d178d7f4a6403cde9474fe65dcc.tar.zst php-shirabe-144d059b725e2d178d7f4a6403cde9474fe65dcc.zip | |
refactor(util): drop the unused TlsHelper port
Composer's Composer\Util\TlsHelper is marked deprecated for removal in
Composer 3.0 and has no caller in composer/composer outside its own
test: PHP's stream layer verifies certificate hostnames itself, and the
one surviving method delegates to composer/ca-bundle.
The Rust port had no caller either, so it, its test, and the
openssl_x509_parse shim it was the sole user of are removed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| -rw-r--r-- | crates/shirabe-php-shim/src/openssl.rs | 10 | ||||
| -rw-r--r-- | crates/shirabe/src/util.rs | 2 | ||||
| -rw-r--r-- | crates/shirabe/src/util/tls_helper.rs | 149 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/main.rs | 1 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/tls_helper_test.rs | 138 | ||||
| -rw-r--r-- | docs/dev/plugin-class-classification.md | 2 |
6 files changed, 1 insertions, 301 deletions
diff --git a/crates/shirabe-php-shim/src/openssl.rs b/crates/shirabe-php-shim/src/openssl.rs index d24caa63..471f80f3 100644 --- a/crates/shirabe-php-shim/src/openssl.rs +++ b/crates/shirabe-php-shim/src/openssl.rs @@ -1,13 +1,3 @@ -use crate::PhpMixed; -use indexmap::IndexMap; - pub const OPENSSL_ALGO_SHA384: i64 = 9; pub const OPENSSL_VERSION_NUMBER: i64 = 0; pub const OPENSSL_VERSION_TEXT: &str = ""; - -pub fn openssl_x509_parse( - _certificate: &str, - _short_names: bool, -) -> Option<IndexMap<String, PhpMixed>> { - todo!() -} diff --git a/crates/shirabe/src/util.rs b/crates/shirabe/src/util.rs index 35402d79..613b615e 100644 --- a/crates/shirabe/src/util.rs +++ b/crates/shirabe/src/util.rs @@ -28,7 +28,6 @@ pub mod svn; pub mod sync_executor; pub mod sync_helper; pub mod tar; -pub mod tls_helper; pub mod url; pub mod zip; @@ -60,6 +59,5 @@ pub use stream_context_factory::*; pub use svn::*; pub use sync_helper::*; pub use tar::*; -pub use tls_helper::*; pub use url::*; pub use zip::*; diff --git a/crates/shirabe/src/util/tls_helper.rs b/crates/shirabe/src/util/tls_helper.rs deleted file mode 100644 index 0c3f44dc..00000000 --- a/crates/shirabe/src/util/tls_helper.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! ref: composer/src/Composer/Util/TlsHelper.php - -use shirabe_ca_bundle::ca_bundle::CaBundle; -use shirabe_pcre::Preg; -use shirabe_php_shim::{ - PhpMixed, ltrim, php_regex, preg_quote, str_replace, strtolower, substr, substr_count, -}; - -/// Extracted certificate names. Mirrors PHP's `array{cn: string, san: string[]}`. -#[derive(Debug, Clone)] -pub struct CertificateNames { - pub cn: String, - pub san: Vec<String>, -} - -/// Match hostname against a certificate. -/// -/// @deprecated Use composer/ca-bundle and composer/composer 2.2 if you still need PHP 5 -/// compatibility, this class will be removed in Composer 3.0 -#[derive(Debug)] -pub struct TlsHelper; - -impl TlsHelper { - /// Match hostname against a certificate. Sets `cn` to the common name of the - /// certificate iff a match is found. - pub fn check_certificate_host( - certificate: &PhpMixed, - hostname: &str, - cn: &mut Option<String>, - ) -> bool { - let names = Self::get_certificate_names(certificate); - - let Some(names) = names else { - return false; - }; - - let mut combined_names = names.san.clone(); - combined_names.push(names.cn.clone()); - let hostname = strtolower(hostname); - - for cert_name in &combined_names { - let matcher = Self::cert_name_matcher(cert_name); - - if let Some(matcher) = matcher - && matcher(&hostname) - { - *cn = Some(names.cn); - - return true; - } - } - - false - } - - /// Extract DNS names out of an X.509 certificate. - pub fn get_certificate_names(certificate: &PhpMixed) -> Option<CertificateNames> { - let info: Option<&PhpMixed> = if certificate.as_array().is_some() { - Some(certificate) - } else if CaBundle::is_openssl_parse_safe() { - // TODO(phase-c): openssl_x509_parse on a PEM string certificate. - todo!("openssl_x509_parse for non-array certificates") - } else { - None - }; - - let info = info?.as_array()?; - - let common_name = info - .get("subject") - .and_then(|s| s.as_array()) - .and_then(|s| s.get("commonName")) - .and_then(|c| c.as_string()); - - let common_name = strtolower(common_name?); - let mut subject_alt_names: Vec<String> = Vec::new(); - - if let Some(san) = info - .get("extensions") - .and_then(|e| e.as_array()) - .and_then(|e| e.get("subjectAltName")) - .and_then(|s| s.as_string()) - { - let split = Preg::split(php_regex!("{\\s*,\\s*}"), san); - subject_alt_names = split - .into_iter() - .filter_map(|name| { - if name.starts_with("DNS:") { - Some(strtolower(<rim(&substr(&name, 4, None), None))) - } else { - None - } - }) - .collect(); - } - - Some(CertificateNames { - cn: common_name, - san: subject_alt_names, - }) - } - - /// Get the certificate pin. - pub fn get_certificate_fingerprint(_certificate: &str) -> String { - todo!("openssl public key extraction and sha1 fingerprint") - } - - /// Test if it is safe to use the PHP function openssl_x509_parse(). - pub fn is_openssl_parse_safe() -> bool { - CaBundle::is_openssl_parse_safe() - } - - /// Convert certificate name into matching function. - fn cert_name_matcher(cert_name: &str) -> Option<Box<dyn Fn(&str) -> bool>> { - let wildcards = substr_count(cert_name, "*"); - - if wildcards == 0 { - // Literal match. - let cert_name = cert_name.to_string(); - return Some(Box::new(move |hostname: &str| hostname == cert_name)); - } - - if wildcards == 1 { - let components: Vec<&str> = cert_name.split('.').collect(); - - if components.len() < 3 { - // Must have 3+ components - return None; - } - - let first_component = components[0]; - - // Wildcard must be the last character. - if !first_component.ends_with('*') { - return None; - } - - let mut wildcard_regex = preg_quote(cert_name, None); - wildcard_regex = str_replace("\\*", "[a-z0-9-]+", &wildcard_regex); - let wildcard_regex = format!("{{^{}$}}", wildcard_regex); - - return Some(Box::new(move |hostname: &str| { - Preg::is_match(&wildcard_regex, hostname) - })); - } - - None - } -} diff --git a/crates/shirabe/tests/util/main.rs b/crates/shirabe/tests/util/main.rs index 0208ca48..05e8ea14 100644 --- a/crates/shirabe/tests/util/main.rs +++ b/crates/shirabe/tests/util/main.rs @@ -33,6 +33,5 @@ mod silencer_test; mod stream_context_factory_test; mod svn_test; mod tar_test; -mod tls_helper_test; mod url_test; mod zip_test; diff --git a/crates/shirabe/tests/util/tls_helper_test.rs b/crates/shirabe/tests/util/tls_helper_test.rs deleted file mode 100644 index 53e39d69..00000000 --- a/crates/shirabe/tests/util/tls_helper_test.rs +++ /dev/null @@ -1,138 +0,0 @@ -//! ref: composer/tests/Composer/Test/Util/TlsHelperTest.php - -use indexmap::IndexMap; -use shirabe::util::tls_helper::TlsHelper; -use shirabe_php_shim::PhpMixed; - -// Builds the `['subject' => ['commonName' => ..], 'extensions' => ['subjectAltName' => ..]]` -// certificate array used by the test, given the common name and the subjectAltName string. -fn certificate(common_name: &str, subject_alt_name: &str) -> PhpMixed { - let mut subject = IndexMap::new(); - subject.insert( - "commonName".to_string(), - PhpMixed::String(common_name.to_string()), - ); - let mut extensions = IndexMap::new(); - extensions.insert( - "subjectAltName".to_string(), - PhpMixed::String(subject_alt_name.to_string()), - ); - let mut cert = IndexMap::new(); - cert.insert("subject".to_string(), PhpMixed::Array(subject)); - cert.insert("extensions".to_string(), PhpMixed::Array(extensions)); - PhpMixed::Array(cert) -} - -/// ref: TlsHelperTest::dataCheckCertificateHost -fn data_check_certificate_host() -> Vec<(bool, &'static str, Vec<&'static str>)> { - vec![ - (true, "getcomposer.org", vec!["getcomposer.org"]), - ( - true, - "getcomposer.org", - vec!["getcomposer.org", "packagist.org"], - ), - ( - true, - "getcomposer.org", - vec!["packagist.org", "getcomposer.org"], - ), - (true, "foo.getcomposer.org", vec!["*.getcomposer.org"]), - (false, "xyz.foo.getcomposer.org", vec!["*.getcomposer.org"]), - ( - true, - "foo.getcomposer.org", - vec!["getcomposer.org", "*.getcomposer.org"], - ), - ( - true, - "foo.getcomposer.org", - vec!["foo.getcomposer.org", "foo*.getcomposer.org"], - ), - ( - true, - "foo1.getcomposer.org", - vec!["foo.getcomposer.org", "foo*.getcomposer.org"], - ), - ( - true, - "foo2.getcomposer.org", - vec!["foo.getcomposer.org", "foo*.getcomposer.org"], - ), - ( - false, - "foo2.another.getcomposer.org", - vec!["foo.getcomposer.org", "foo*.getcomposer.org"], - ), - ( - false, - "test.example.net", - vec!["**.example.net", "**.example.net"], - ), - ( - false, - "test.example.net", - vec!["t*t.example.net", "t*t.example.net"], - ), - ( - false, - "xyz.example.org", - vec!["*z.example.org", "*z.example.org"], - ), - ( - false, - "foo.bar.example.com", - vec!["foo.*.example.com", "foo.*.example.com"], - ), - (false, "example.com", vec!["example.*", "example.*"]), - (true, "localhost", vec!["localhost"]), - (false, "localhost", vec!["*"]), - (false, "localhost", vec!["local*"]), - (false, "example.net", vec!["*.net", "*.org", "ex*.net"]), - (true, "example.net", vec!["*.net", "*.org", "example.net"]), - ] -} - -#[test] -fn test_check_certificate_host() { - for (expected_result, hostname, mut cert_names) in data_check_certificate_host() { - let expected_cn = cert_names.remove(0); - let subject_alt_name = if cert_names.is_empty() { - String::new() - } else { - format!("DNS:{}", cert_names.join(",DNS:")) - }; - let cert = certificate(expected_cn, &subject_alt_name); - - let mut found_cn: Option<String> = None; - let result = TlsHelper::check_certificate_host(&cert, hostname, &mut found_cn); - - if expected_result { - assert!(result, "hostname {hostname} should match"); - assert_eq!(found_cn.as_deref(), Some(expected_cn)); - } else { - assert!(!result, "hostname {hostname} should not match"); - assert_eq!(found_cn, None); - } - } -} - -#[test] -fn test_get_certificate_names() { - let cert = certificate( - "example.net", - "DNS: example.com, IP: 127.0.0.1, DNS: getcomposer.org, Junk: blah, DNS: composer.example.org", - ); - - let names = TlsHelper::get_certificate_names(&cert).unwrap(); - - assert_eq!(names.cn, "example.net"); - assert_eq!( - names.san, - vec![ - "example.com".to_string(), - "getcomposer.org".to_string(), - "composer.example.org".to_string(), - ] - ); -} diff --git a/docs/dev/plugin-class-classification.md b/docs/dev/plugin-class-classification.md index ddba364d..c77eca52 100644 --- a/docs/dev/plugin-class-classification.md +++ b/docs/dev/plugin-class-classification.md @@ -87,7 +87,7 @@ the boundary. #### php-native Composer-plugin-api's pure type definitions, the stateless utility classes -(`TlsHelper`, `Platform\Version`, `ClassMapGenerator`, …), exception +(`Platform\Version`, `ClassMapGenerator`, …), exception classes, constants-only classes, the state-decoupled vendor packages (composer/pcre, composer/semver, seld/jsonlint, justinrainbow/json-schema, …) — and *reachable* classes that are stateless and pure (`VersionParser`, |
