mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-23 12:12:09 +08:00
308 lines
8.9 KiB
Rust
308 lines
8.9 KiB
Rust
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<std::string::String>,
|
|
default_headers: HeaderMap,
|
|
accept_invalid_certs: bool,
|
|
timeout_secs: Option<u64>,
|
|
tls_root_mode: TlsRootMode,
|
|
) -> Result<Client> {
|
|
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<rustls::ClientConfig> {
|
|
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<u64>,
|
|
user_agent: Option<String>,
|
|
accept_invalid_certs: bool,
|
|
) -> Result<Client> {
|
|
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<u64>,
|
|
user_agent: Option<String>,
|
|
accept_invalid_certs: bool,
|
|
tls_root_mode: TlsRootMode,
|
|
) -> Result<HttpResponse> {
|
|
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<u64>,
|
|
user_agent: Option<String>,
|
|
accept_invalid_certs: bool,
|
|
tls_root_mode: TlsRootMode,
|
|
) -> Result<Client> {
|
|
let proxy_url: Option<std::string::String> = 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<u64>,
|
|
user_agent: Option<String>,
|
|
accept_invalid_certs: bool,
|
|
) -> Result<HttpResponse> {
|
|
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),
|
|
}
|
|
}
|
|
}
|