From d6ee7d49506a87c34aa5b407932d414a552a4523 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Fri, 15 May 2026 23:41:41 +0900 Subject: feat(port): port RequestProxy.php --- crates/shirabe/src/util/http/request_proxy.rs | 105 ++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) (limited to 'crates/shirabe/src/util') diff --git a/crates/shirabe/src/util/http/request_proxy.rs b/crates/shirabe/src/util/http/request_proxy.rs index 826598f..1405a32 100644 --- a/crates/shirabe/src/util/http/request_proxy.rs +++ b/crates/shirabe/src/util/http/request_proxy.rs @@ -1 +1,106 @@ //! ref: composer/src/Composer/Util/Http/RequestProxy.php + +use indexmap::IndexMap; +use shirabe_php_shim::{ + curl_version, PhpMixed, CURLAUTH_BASIC, CURL_VERSION_HTTPS_PROXY, CURLOPT_NOPROXY, + CURLOPT_PROXY, CURLOPT_PROXY_CAINFO, CURLOPT_PROXY_CAPATH, CURLOPT_PROXYAUTH, + CURLOPT_PROXYUSERPWD, InvalidArgumentException, +}; + +use crate::downloader::transport_exception::TransportException; + +// contextOptions = array{http: array{proxy: string, header?: string, request_fulluri?: bool}} +type ContextOptions = IndexMap>; + +#[derive(Debug)] +pub struct RequestProxy { + context_options: Option, + status: Option, + url: Option, + auth: Option, +} + +impl RequestProxy { + pub fn new(url: Option, auth: Option, context_options: Option, status: Option) -> Self { + Self { url, auth, context_options, status } + } + + pub fn none() -> Self { + Self::new(None, None, None, None) + } + + pub fn no_proxy() -> Self { + Self::new(None, None, None, Some("excluded by no_proxy".to_string())) + } + + pub fn get_context_options(&self) -> Option<&ContextOptions> { + self.context_options.as_ref() + } + + pub fn get_curl_options(&self, ssl_options: &IndexMap) -> Result, TransportException> { + if self.is_secure() && !self.supports_secure_proxy() { + return Err(TransportException::new("Cannot use an HTTPS proxy. PHP >= 7.3 and cUrl >= 7.52.0 are required.".to_string())); + } + + let mut options: IndexMap = IndexMap::new(); + options.insert(CURLOPT_PROXY, PhpMixed::String(self.url.as_deref().unwrap_or("").to_string())); + + if self.url.is_some() { + options.insert(CURLOPT_NOPROXY, PhpMixed::String(String::new())); + } + + if let Some(auth) = &self.auth { + options.insert(CURLOPT_PROXYAUTH, PhpMixed::Int(CURLAUTH_BASIC)); + options.insert(CURLOPT_PROXYUSERPWD, PhpMixed::String(auth.clone())); + } + + if self.is_secure() { + if let Some(cafile) = ssl_options.get("cafile") { + options.insert(CURLOPT_PROXY_CAINFO, cafile.clone()); + } + if let Some(capath) = ssl_options.get("capath") { + options.insert(CURLOPT_PROXY_CAPATH, capath.clone()); + } + } + + Ok(options) + } + + pub fn get_status(&self, format: Option<&str>) -> Result { + if self.status.is_none() { + return Ok(String::new()); + } + + let format = format.unwrap_or("%s"); + if format.contains("%s") { + return Ok(format.replace("%s", self.status.as_deref().unwrap())); + } + + Err(InvalidArgumentException { + message: "String format specifier is missing".to_string(), + code: 0, + }) + } + + pub fn is_excluded_by_no_proxy(&self) -> bool { + self.status.is_some() && self.url.is_none() + } + + pub fn is_secure(&self) -> bool { + self.url.as_deref().unwrap_or("").starts_with("https://") + } + + pub fn supports_secure_proxy(&self) -> bool { + let version = match curl_version() { + None => return false, + Some(v) => v, + }; + + if !shirabe_php_shim::defined("CURL_VERSION_HTTPS_PROXY") { + return false; + } + + let features = version.get("features").and_then(|v| v.as_int()).unwrap_or(0); + (features & CURL_VERSION_HTTPS_PROXY) != 0 + } +} -- cgit v1.3.1