use crate::config::Config; use anyhow::Result; use base64::{Engine as _, engine::general_purpose}; use reqwest::{ Client, Proxy, StatusCode, header::{HeaderMap, HeaderValue, USER_AGENT}, }; use smartstring::alias::String; use std::{sync::Arc, time::Duration}; use sysproxy::Sysproxy; use tauri::Url; #[derive(Debug)] pub struct HttpResponse { status: StatusCode, headers: HeaderMap, body: String, } impl HttpResponse { pub const fn new(status: StatusCode, headers: HeaderMap, body: String) -> Self { Self { status, headers, body } } pub const fn status(&self) -> StatusCode { self.status } pub const fn headers(&self) -> &HeaderMap { &self.headers } pub fn text_with_charset(&self) -> Result<&str> { Ok(&self.body) } } #[derive(Debug, Clone, Copy)] pub enum ProxyType { None, Localhost, System, } #[derive(Debug, Clone, Copy)] enum TlsRootMode { PlatformVerifier, StaticWebpkiRoots, } pub struct NetworkManager; impl Default for NetworkManager { fn default() -> Self { Self::new() } } impl NetworkManager { pub const fn new() -> Self { Self } fn build_client( &self, proxy_url: Option, default_headers: HeaderMap, accept_invalid_certs: bool, timeout_secs: Option, tls_root_mode: TlsRootMode, ) -> Result { let mut builder = Client::builder() .tls_backend_rustls() .redirect(reqwest::redirect::Policy::limited(10)) .tcp_keepalive(Duration::from_secs(60)) .pool_max_idle_per_host(0) .pool_idle_timeout(None); if matches!(tls_root_mode, TlsRootMode::StaticWebpkiRoots) { builder = builder.tls_backend_preconfigured(Self::build_static_webpki_tls_config()?); } // 设置代理 if let Some(proxy_str) = proxy_url { let proxy = Proxy::all(proxy_str)?; builder = builder.proxy(proxy); } else { builder = builder.no_proxy(); } builder = builder.default_headers(default_headers); // SSL/TLS if accept_invalid_certs { builder = builder .danger_accept_invalid_certs(true) .danger_accept_invalid_hostnames(true); } // 超时设置 if let Some(secs) = timeout_secs { builder = builder .timeout(Duration::from_secs(secs)) .connect_timeout(Duration::from_secs(secs.min(30))); } Ok(builder.build()?) } fn build_static_webpki_tls_config() -> Result { let root_store = rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); let mut config = rustls::ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider())) .with_safe_default_protocol_versions()? .with_root_certificates(root_store) .with_no_client_auth(); config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; Ok(config) } fn should_retry_with_static_webpki_roots(err: &anyhow::Error) -> bool { let err_text = format!("{err:?}").to_ascii_lowercase(); [ "certificate", "cert", "tls", "ssl", "rustls", "webpki", "revocation", "ocsp", "crl", "issuer", "unknownissuer", ] .iter() .any(|needle| err_text.contains(needle)) } pub async fn create_request( &self, proxy_type: ProxyType, timeout_secs: Option, user_agent: Option, accept_invalid_certs: bool, ) -> Result { self.create_request_with_tls_mode( proxy_type, timeout_secs, user_agent, accept_invalid_certs, TlsRootMode::PlatformVerifier, ) .await } async fn get_with_tls_mode( &self, url: &str, proxy_type: ProxyType, timeout_secs: Option, user_agent: Option, accept_invalid_certs: bool, tls_root_mode: TlsRootMode, ) -> Result { let mut parsed = Url::parse(url)?; let mut extra_headers = HeaderMap::new(); if !parsed.username().is_empty() && let Some(pass) = parsed.password() { let username = percent_encoding::percent_decode_str(parsed.username()) .decode_utf8_lossy() .into_owned(); let password = percent_encoding::percent_decode_str(pass) .decode_utf8_lossy() .into_owned(); let auth_str = format!("{}:{}", username, password); let encoded = general_purpose::STANDARD.encode(auth_str); extra_headers.insert("Authorization", HeaderValue::from_str(&format!("Basic {}", encoded))?); } parsed.set_username("").ok(); parsed.set_password(None).ok(); // 创建请求 let client = self .create_request_with_tls_mode( proxy_type, timeout_secs, user_agent, accept_invalid_certs, tls_root_mode, ) .await?; let mut request_builder = client.get(parsed); for (key, value) in extra_headers.iter() { request_builder = request_builder.header(key, value); } let response = match request_builder.send().await { Ok(resp) => resp, Err(e) => { return Err(anyhow::anyhow!("Request failed: {}", e)); } }; let status = response.status(); let headers = response.headers().to_owned(); let body = match response.text().await { Ok(text) => text.into(), Err(e) => { return Err(anyhow::anyhow!("Failed to read response body: {}", e)); } }; Ok(HttpResponse::new(status, headers, body)) } async fn create_request_with_tls_mode( &self, proxy_type: ProxyType, timeout_secs: Option, user_agent: Option, accept_invalid_certs: bool, tls_root_mode: TlsRootMode, ) -> Result { let proxy_url: Option = match proxy_type { ProxyType::None => None, ProxyType::Localhost => { let port = { let verge_port = Config::verge().await.data_arc().verge_mixed_port; match verge_port { Some(port) => port, None => Config::clash().await.data_arc().get_mixed_port(), } }; Some(format!("http://127.0.0.1:{port}")) } ProxyType::System => { if let Ok(p @ Sysproxy { enable: true, .. }) = Sysproxy::get_system_proxy() { Some(format!("http://{}:{}", p.host, p.port)) } else { None } } }; let mut headers = HeaderMap::new(); // 设置 User-Agent if let Some(ua) = user_agent { headers.insert(USER_AGENT, HeaderValue::from_str(ua.as_str())?); } else { headers.insert( USER_AGENT, HeaderValue::from_str(&format!("clash-verge/v{}", env!("CARGO_PKG_VERSION")))?, ); } self.build_client(proxy_url, headers, accept_invalid_certs, timeout_secs, tls_root_mode) } pub async fn get_with_interrupt( &self, url: &str, proxy_type: ProxyType, timeout_secs: Option, user_agent: Option, accept_invalid_certs: bool, ) -> Result { let platform_result = self .get_with_tls_mode( url, proxy_type, timeout_secs, user_agent.clone(), accept_invalid_certs, TlsRootMode::PlatformVerifier, ) .await; match platform_result { Ok(response) => Ok(response), Err(err) if !accept_invalid_certs && Self::should_retry_with_static_webpki_roots(&err) => self .get_with_tls_mode( url, proxy_type, timeout_secs, user_agent, accept_invalid_certs, TlsRootMode::StaticWebpkiRoots, ) .await .map_err(|fallback_err| { anyhow::anyhow!( "platform TLS verifier failed: {err}; static webpki roots fallback failed: {fallback_err}" ) }), Err(err) => Err(err), } } }