Update: 将子项目从 submodule 转为完整内容

- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
This commit is contained in:
freedak
2026-07-04 19:20:46 +08:00
parent 54d6465fa7
commit f7a720204a
3360 changed files with 802660 additions and 3 deletions
@@ -0,0 +1,10 @@
[package]
name = "nomifun-net"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
reqwest.workspace = true
tracing.workspace = true
@@ -0,0 +1,7 @@
pub mod proxy;
pub fn http_client() -> reqwest::Client {
proxy::apply_detected_proxy(reqwest::Client::builder())
.build()
.unwrap_or_else(|_| reqwest::Client::new())
}
@@ -0,0 +1,521 @@
use std::collections::HashSet;
use std::sync::OnceLock;
use tracing::{debug, warn};
const PROXY_ENV_KEYS: &[&str] = &[
"HTTP_PROXY",
"HTTPS_PROXY",
"ALL_PROXY",
"http_proxy",
"https_proxy",
"all_proxy",
];
#[derive(Debug, Clone, PartialEq, Eq)]
struct SystemProxyConfig {
http_proxy: Option<String>,
https_proxy: Option<String>,
all_proxy: Option<String>,
no_proxy: Option<String>,
}
pub fn apply_detected_proxy(mut builder: reqwest::ClientBuilder) -> reqwest::ClientBuilder {
if process_has_proxy_env() {
return builder;
}
let Some(config) = system_proxy_config() else {
return builder;
};
let no_proxy = effective_no_proxy(config);
if let Some(proxy_url) = config.all_proxy.as_deref() {
match reqwest::Proxy::all(proxy_url) {
Ok(proxy) => {
debug!("Using detected system ALL_PROXY for outbound HTTP client");
return builder.proxy(proxy.no_proxy(no_proxy));
}
Err(err) => warn!(error = %err, "Ignoring invalid detected system ALL_PROXY"),
}
}
if let Some(proxy_url) = config.http_proxy.as_deref() {
match reqwest::Proxy::http(proxy_url) {
Ok(proxy) => {
debug!("Using detected system HTTP_PROXY for outbound HTTP client");
builder = builder.proxy(proxy.no_proxy(no_proxy.clone()));
}
Err(err) => warn!(error = %err, "Ignoring invalid detected system HTTP_PROXY"),
}
}
if let Some(proxy_url) = config.https_proxy.as_deref() {
match reqwest::Proxy::https(proxy_url) {
Ok(proxy) => {
debug!("Using detected system HTTPS_PROXY for outbound HTTP client");
builder = builder.proxy(proxy.no_proxy(no_proxy));
}
Err(err) => warn!(error = %err, "Ignoring invalid detected system HTTPS_PROXY"),
}
}
builder
}
pub fn child_proxy_env<'a, I>(configured_env_names: I) -> Vec<(String, String)>
where
I: IntoIterator<Item = &'a str>,
{
let configured_names: HashSet<String> = configured_env_names
.into_iter()
.map(|name| name.to_ascii_uppercase())
.collect();
let process_names = process_env_proxy_names();
if has_proxy_name(&configured_names) || has_proxy_name(&process_names) {
return Vec::new();
}
let Some(config) = system_proxy_config() else {
return Vec::new();
};
proxy_env_from_config(config, &configured_names, &process_names)
}
fn system_proxy_config() -> Option<&'static SystemProxyConfig> {
static CONFIG: OnceLock<Option<SystemProxyConfig>> = OnceLock::new();
CONFIG.get_or_init(detect_system_proxy).as_ref()
}
fn process_has_proxy_env() -> bool {
has_proxy_name(&process_env_proxy_names())
}
fn process_env_proxy_names() -> HashSet<String> {
std::env::vars()
.filter(|(_, value)| !value.trim().is_empty())
.map(|(name, _)| name.to_ascii_uppercase())
.collect()
}
fn has_proxy_name(names: &HashSet<String>) -> bool {
PROXY_ENV_KEYS
.iter()
.any(|key| names.contains(&key.to_ascii_uppercase()))
}
fn has_env_name(names: &HashSet<String>, key: &str) -> bool {
names.contains(&key.to_ascii_uppercase())
}
fn proxy_env_from_config(
config: &SystemProxyConfig,
configured_names: &HashSet<String>,
process_names: &HashSet<String>,
) -> Vec<(String, String)> {
let mut vars = Vec::new();
push_proxy_pair(
&mut vars,
"HTTP_PROXY",
config.http_proxy.as_deref(),
configured_names,
process_names,
);
push_proxy_pair(
&mut vars,
"HTTPS_PROXY",
config.https_proxy.as_deref(),
configured_names,
process_names,
);
push_proxy_pair(
&mut vars,
"ALL_PROXY",
config.all_proxy.as_deref(),
configured_names,
process_names,
);
if let Some(no_proxy) = config.no_proxy.as_deref() {
push_if_missing(
&mut vars,
"NO_PROXY",
no_proxy,
configured_names,
process_names,
);
push_if_missing(
&mut vars,
"no_proxy",
no_proxy,
configured_names,
process_names,
);
}
vars
}
fn push_proxy_pair(
vars: &mut Vec<(String, String)>,
upper_key: &str,
value: Option<&str>,
configured_names: &HashSet<String>,
process_names: &HashSet<String>,
) {
let Some(value) = value else {
return;
};
push_if_missing(vars, upper_key, value, configured_names, process_names);
push_if_missing(
vars,
&upper_key.to_ascii_lowercase(),
value,
configured_names,
process_names,
);
}
fn push_if_missing(
vars: &mut Vec<(String, String)>,
key: &str,
value: &str,
configured_names: &HashSet<String>,
process_names: &HashSet<String>,
) {
if has_env_name(configured_names, key) || has_env_name(process_names, key) {
return;
}
vars.push((key.to_owned(), value.to_owned()));
}
fn effective_no_proxy(config: &SystemProxyConfig) -> Option<reqwest::NoProxy> {
let mut items = Vec::new();
if let Ok(value) = std::env::var("NO_PROXY").or_else(|_| std::env::var("no_proxy"))
&& !value.trim().is_empty()
{
items.push(value);
}
if let Some(value) = config.no_proxy.as_ref()
&& !value.trim().is_empty()
{
items.push(value.clone());
}
if items.is_empty() {
return None;
}
reqwest::NoProxy::from_string(&items.join(","))
}
fn detect_system_proxy() -> Option<SystemProxyConfig> {
detect_platform_proxy()
}
#[cfg(target_os = "macos")]
fn detect_platform_proxy() -> Option<SystemProxyConfig> {
use std::process::Command;
let output = Command::new("/usr/sbin/scutil")
.arg("--proxy")
.output()
.or_else(|_| Command::new("scutil").arg("--proxy").output())
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
parse_scutil_proxy(&stdout)
}
#[cfg(not(target_os = "macos"))]
fn detect_platform_proxy() -> Option<SystemProxyConfig> {
None
}
#[cfg(target_os = "macos")]
fn parse_scutil_proxy(text: &str) -> Option<SystemProxyConfig> {
let mut http_enable = false;
let mut https_enable = false;
let mut socks_enable = false;
let mut http_host = None;
let mut https_host = None;
let mut socks_host = None;
let mut http_port = None;
let mut https_port = None;
let mut socks_port = None;
let mut exceptions = Vec::new();
let mut in_exceptions = false;
for raw_line in text.lines() {
let line = raw_line.trim();
if line.starts_with("ExceptionsList") && line.contains("<array>") {
in_exceptions = true;
continue;
}
if in_exceptions {
if line.starts_with('}') {
in_exceptions = false;
continue;
}
if let Some((_, value)) = line.split_once(':') {
exceptions.extend(parse_exception_values(value));
}
continue;
}
let Some((key, value)) = line.split_once(':') else {
continue;
};
let key = key.trim();
let value = value.trim();
match key {
"HTTPEnable" => http_enable = value == "1",
"HTTPSEnable" => https_enable = value == "1",
"SOCKSEnable" => socks_enable = value == "1",
"HTTPProxy" => http_host = non_empty(value),
"HTTPSProxy" => https_host = non_empty(value),
"SOCKSProxy" => socks_host = non_empty(value),
"HTTPPort" => http_port = parse_port(value),
"HTTPSPort" => https_port = parse_port(value),
"SOCKSPort" => socks_port = parse_port(value),
_ => {}
}
}
let http_proxy = enabled_proxy_url(http_enable, "http", http_host.as_deref(), http_port);
let https_proxy = enabled_proxy_url(https_enable, "http", https_host.as_deref(), https_port);
let all_proxy = if http_proxy.is_none() && https_proxy.is_none() && socks_enable {
enabled_proxy_url(true, "socks5h", socks_host.as_deref(), socks_port)
} else {
None
};
if http_proxy.is_none() && https_proxy.is_none() && all_proxy.is_none() {
return None;
}
Some(SystemProxyConfig {
http_proxy,
https_proxy,
all_proxy,
no_proxy: build_no_proxy(exceptions),
})
}
#[cfg(target_os = "macos")]
fn enabled_proxy_url(
enabled: bool,
scheme: &str,
host: Option<&str>,
port: Option<u16>,
) -> Option<String> {
if !enabled {
return None;
}
proxy_url(scheme, host?, port?)
}
#[cfg(target_os = "macos")]
fn non_empty(value: &str) -> Option<String> {
let value = value.trim();
(!value.is_empty()).then(|| value.to_owned())
}
#[cfg(target_os = "macos")]
fn parse_port(value: &str) -> Option<u16> {
value.trim().parse().ok()
}
#[cfg(target_os = "macos")]
fn proxy_url(scheme: &str, host: &str, port: u16) -> Option<String> {
let host = host.trim();
if host.is_empty() {
return None;
}
let host = if host.contains(':') && !host.starts_with('[') {
format!("[{host}]")
} else {
host.to_owned()
};
Some(format!("{scheme}://{host}:{port}"))
}
#[cfg(target_os = "macos")]
fn parse_exception_values(value: &str) -> Vec<String> {
value
.split(',')
.filter_map(|item| {
let item = item.trim();
if item.is_empty() {
return None;
}
Some(item.to_owned())
})
.collect()
}
#[cfg(target_os = "macos")]
fn build_no_proxy(exceptions: Vec<String>) -> Option<String> {
let mut items = vec![
"localhost".to_owned(),
"127.0.0.1".to_owned(),
"::1".to_owned(),
"10.0.0.0/8".to_owned(),
"127.0.0.0/8".to_owned(),
"172.16.0.0/12".to_owned(),
"192.168.0.0/16".to_owned(),
];
for item in exceptions {
if let Some(normalized) = normalize_no_proxy_item(&item) {
items.push(normalized);
}
}
items.sort();
items.dedup();
(!items.is_empty()).then(|| items.join(","))
}
#[cfg(target_os = "macos")]
fn normalize_no_proxy_item(item: &str) -> Option<String> {
let item = item.trim();
if item.is_empty() {
return None;
}
match item {
"127.*" => return Some("127.0.0.0/8".to_owned()),
"10.*" => return Some("10.0.0.0/8".to_owned()),
"192.168.*" => return Some("192.168.0.0/16".to_owned()),
_ => {}
}
if item.starts_with("172.") {
return Some("172.16.0.0/12".to_owned());
}
if let Some(domain) = item.strip_prefix("*.") {
return Some(format!(".{domain}"));
}
if let Some(domain) = item.strip_prefix('*') {
return (!domain.is_empty()).then(|| domain.to_owned());
}
Some(item.to_owned())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn proxy_env_from_config_adds_upper_and_lowercase_keys() {
let config = SystemProxyConfig {
http_proxy: Some("http://127.0.0.1:7892".to_owned()),
https_proxy: Some("http://127.0.0.1:7892".to_owned()),
all_proxy: None,
no_proxy: Some("localhost,127.0.0.1".to_owned()),
};
let vars = proxy_env_from_config(&config, &HashSet::new(), &HashSet::new());
assert!(vars.contains(&("HTTP_PROXY".to_owned(), "http://127.0.0.1:7892".to_owned())));
assert!(vars.contains(&("http_proxy".to_owned(), "http://127.0.0.1:7892".to_owned())));
assert!(vars.contains(&("HTTPS_PROXY".to_owned(), "http://127.0.0.1:7892".to_owned())));
assert!(vars.contains(&("NO_PROXY".to_owned(), "localhost,127.0.0.1".to_owned())));
}
#[test]
fn proxy_env_from_config_respects_existing_names() {
let config = SystemProxyConfig {
http_proxy: Some("http://127.0.0.1:7892".to_owned()),
https_proxy: Some("http://127.0.0.1:7892".to_owned()),
all_proxy: None,
no_proxy: Some("localhost,127.0.0.1".to_owned()),
};
let configured_names = HashSet::from(["HTTPS_PROXY".to_owned()]);
let process_names = HashSet::from(["NO_PROXY".to_owned()]);
let vars = proxy_env_from_config(&config, &configured_names, &process_names);
assert!(!vars.iter().any(|(name, _)| name == "HTTPS_PROXY"));
assert!(!vars.iter().any(|(name, _)| name == "https_proxy"));
assert!(!vars.iter().any(|(name, _)| name == "NO_PROXY"));
assert!(vars.contains(&("HTTP_PROXY".to_owned(), "http://127.0.0.1:7892".to_owned())));
}
#[cfg(target_os = "macos")]
#[test]
fn child_proxy_env_uses_current_macos_system_proxy_when_needed() {
let Some(config) = detect_platform_proxy() else {
return;
};
if config.http_proxy.is_none() && config.https_proxy.is_none() && config.all_proxy.is_none()
{
return;
}
let vars = child_proxy_env(std::iter::empty());
if process_has_proxy_env() {
assert!(vars.is_empty());
} else {
let values: HashSet<&str> = vars.iter().map(|(_, value)| value.as_str()).collect();
if let Some(http_proxy) = config.http_proxy.as_deref() {
assert!(values.contains(http_proxy));
}
if let Some(https_proxy) = config.https_proxy.as_deref() {
assert!(values.contains(https_proxy));
}
if let Some(all_proxy) = config.all_proxy.as_deref() {
assert!(values.contains(all_proxy));
}
}
}
#[cfg(target_os = "macos")]
#[test]
fn parse_scutil_proxy_extracts_http_https_and_exceptions() {
let input = r#"<dictionary> {
ExceptionsList : <array> {
0 : *zhihu.com,*zhimg.com,localhost,*.local,127.*,10.*,172.16.*,192.168.*
}
HTTPEnable : 1
HTTPPort : 7892
HTTPProxy : 127.0.0.1
HTTPSEnable : 1
HTTPSPort : 7892
HTTPSProxy : 127.0.0.1
SOCKSEnable : 1
SOCKSPort : 7892
SOCKSProxy : 127.0.0.1
}"#;
let config = parse_scutil_proxy(input).expect("proxy config");
assert_eq!(config.http_proxy.as_deref(), Some("http://127.0.0.1:7892"));
assert_eq!(config.https_proxy.as_deref(), Some("http://127.0.0.1:7892"));
assert_eq!(config.all_proxy, None);
let no_proxy = config.no_proxy.expect("no_proxy");
assert!(no_proxy.contains("zhihu.com"));
assert!(no_proxy.contains(".local"));
assert!(no_proxy.contains("127.0.0.0/8"));
assert!(no_proxy.contains("192.168.0.0/16"));
}
#[cfg(target_os = "macos")]
#[test]
fn parse_scutil_proxy_uses_socks_when_http_is_absent() {
let input = r#"<dictionary> {
SOCKSEnable : 1
SOCKSPort : 7892
SOCKSProxy : 127.0.0.1
}"#;
let config = parse_scutil_proxy(input).expect("proxy config");
assert_eq!(
config.all_proxy.as_deref(),
Some("socks5h://127.0.0.1:7892")
);
}
}