refactor: enhance Windows proxy retrieval by using WinAPI for registry access

This commit is contained in:
wonfen
2025-06-22 21:05:50 +08:00
parent f6b5524e0e
commit bdfc383a18
4 changed files with 215 additions and 104 deletions

View File

@@ -96,6 +96,8 @@ winapi = { version = "0.3.9", features = [
"winerror", "winerror",
"tlhelp32", "tlhelp32",
"processthreadsapi", "processthreadsapi",
"winhttp",
"winreg",
] } ] }
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]

View File

@@ -1,10 +1,12 @@
#[cfg(target_os = "linux")]
use anyhow::anyhow;
use anyhow::Result; use anyhow::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::process::Command;
use tokio::time::{timeout, Duration}; use tokio::time::{timeout, Duration};
#[cfg(target_os = "linux")]
use anyhow::anyhow;
#[cfg(not(target_os = "windows"))]
use tokio::process::Command;
#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AsyncAutoproxy { pub struct AsyncAutoproxy {
pub enable: bool, pub enable: bool,
@@ -71,55 +73,97 @@ impl AsyncProxyQuery {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
async fn get_auto_proxy_impl() -> Result<AsyncAutoproxy> { async fn get_auto_proxy_impl() -> Result<AsyncAutoproxy> {
// Windows: 使用 netsh winhttp show proxy 命令 // Windows: 从注册表读取PAC配置
let output = Command::new("netsh") tokio::task::spawn_blocking(move || -> Result<AsyncAutoproxy> {
.args(["winhttp", "show", "proxy"]) Self::get_pac_config_from_registry()
.output() })
.await?; .await?
}
if !output.status.success() { #[cfg(target_os = "windows")]
fn get_pac_config_from_registry() -> Result<AsyncAutoproxy> {
use std::ptr;
use winapi::shared::minwindef::{DWORD, HKEY};
use winapi::um::winnt::{KEY_READ, REG_DWORD, REG_SZ};
use winapi::um::winreg::{RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY_CURRENT_USER};
unsafe {
let key_path = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\0"
.encode_utf16()
.collect::<Vec<u16>>();
let mut hkey: HKEY = ptr::null_mut();
let result =
RegOpenKeyExW(HKEY_CURRENT_USER, key_path.as_ptr(), 0, KEY_READ, &mut hkey);
if result != 0 {
log::debug!(target: "app", "无法打开注册表项");
return Ok(AsyncAutoproxy::default()); return Ok(AsyncAutoproxy::default());
} }
let stdout = String::from_utf8_lossy(&output.stdout); // 1. 检查自动配置是否启用 (AutoConfigURL 存在且不为空即表示启用)
log::debug!(target: "app", "netsh output: {}", stdout); let auto_config_url_name = "AutoConfigURL\0".encode_utf16().collect::<Vec<u16>>();
let mut url_buffer = vec![0u16; 1024];
let mut url_buffer_size: DWORD = (url_buffer.len() * 2) as DWORD;
let mut url_value_type: DWORD = 0;
// 解析输出,查找 PAC 配置 let url_query_result = RegQueryValueExW(
for line in stdout.lines() { hkey,
let line = line.trim(); auto_config_url_name.as_ptr(),
if line.starts_with("代理自动配置脚本") || line.starts_with("Proxy auto-config script") ptr::null_mut(),
{ &mut url_value_type,
// 修复正确解析包含冒号的URL url_buffer.as_mut_ptr() as *mut u8,
// 格式: "代理自动配置脚本 : http://127.0.0.1:11233/commands/pac" &mut url_buffer_size,
// 或: "Proxy auto-config script : http://127.0.0.1:11233/commands/pac" );
if let Some(colon_pos) = line.find(" : ") {
let url = line[colon_pos + 3..].trim(); let mut pac_url = String::new();
if !url.is_empty() && url != "(none)" && url != "" { if url_query_result == 0 && url_value_type == REG_SZ && url_buffer_size > 0 {
log::debug!(target: "app", "解析到PAC URL: {}", url); let end_pos = url_buffer
return Ok(AsyncAutoproxy { .iter()
enable: true, .position(|&x| x == 0)
url: url.to_string(), .unwrap_or(url_buffer.len());
}); pac_url = String::from_utf16_lossy(&url_buffer[..end_pos]);
} log::debug!(target: "app", "从注册表读取到PAC URL: {}", pac_url);
} else if let Some(colon_pos) = line.find(':') {
// 兼容其他可能的格式
let url = line[colon_pos + 1..].trim();
// 确保这不是URL中的协议部分
if url.starts_with("http") && !url.is_empty() && url != "(none)" && url != ""
{
log::debug!(target: "app", "解析到PAC URL (fallback): {}", url);
return Ok(AsyncAutoproxy {
enable: true,
url: url.to_string(),
});
}
}
}
} }
log::debug!(target: "app", "未找到有效的PAC配置"); // 2. 检查自动检测设置是否启用
let auto_detect_name = "AutoDetect\0".encode_utf16().collect::<Vec<u16>>();
let mut auto_detect: DWORD = 0;
let mut detect_buffer_size: DWORD = 4;
let mut detect_value_type: DWORD = 0;
let detect_query_result = RegQueryValueExW(
hkey,
auto_detect_name.as_ptr(),
ptr::null_mut(),
&mut detect_value_type,
&mut auto_detect as *mut DWORD as *mut u8,
&mut detect_buffer_size,
);
RegCloseKey(hkey);
// PAC 启用的条件AutoConfigURL 不为空,或 AutoDetect 被启用
let pac_enabled = !pac_url.is_empty()
|| (detect_query_result == 0 && detect_value_type == REG_DWORD && auto_detect != 0);
if pac_enabled {
log::debug!(target: "app", "PAC配置启用: URL={}, AutoDetect={}", pac_url, auto_detect);
if pac_url.is_empty() && auto_detect != 0 {
pac_url = "auto-detect".to_string();
}
Ok(AsyncAutoproxy {
enable: true,
url: pac_url,
})
} else {
log::debug!(target: "app", "PAC配置未启用");
Ok(AsyncAutoproxy::default()) Ok(AsyncAutoproxy::default())
} }
}
}
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
async fn get_auto_proxy_impl() -> Result<AsyncAutoproxy> { async fn get_auto_proxy_impl() -> Result<AsyncAutoproxy> {
@@ -142,7 +186,7 @@ impl AsyncProxyQuery {
if line.contains("ProxyAutoConfigEnable") && line.contains("1") { if line.contains("ProxyAutoConfigEnable") && line.contains("1") {
pac_enabled = true; pac_enabled = true;
} else if line.contains("ProxyAutoConfigURLString") { } else if line.contains("ProxyAutoConfigURLString") {
// 修复:正确解析包含冒号的URL // 正确解析包含冒号的URL
// 格式: "ProxyAutoConfigURLString : http://127.0.0.1:11233/commands/pac" // 格式: "ProxyAutoConfigURLString : http://127.0.0.1:11233/commands/pac"
if let Some(colon_pos) = line.find(" : ") { if let Some(colon_pos) = line.find(" : ") {
pac_url = line[colon_pos + 3..].trim().to_string(); pac_url = line[colon_pos + 3..].trim().to_string();
@@ -213,40 +257,101 @@ impl AsyncProxyQuery {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
async fn get_system_proxy_impl() -> Result<AsyncSysproxy> { async fn get_system_proxy_impl() -> Result<AsyncSysproxy> {
let output = Command::new("netsh") // Windows: 使用注册表直接读取代理设置
.args(["winhttp", "show", "proxy"]) tokio::task::spawn_blocking(move || -> Result<AsyncSysproxy> {
.output() Self::get_system_proxy_from_registry()
.await?; })
.await?
}
if !output.status.success() { #[cfg(target_os = "windows")]
fn get_system_proxy_from_registry() -> Result<AsyncSysproxy> {
use std::ptr;
use winapi::shared::minwindef::{DWORD, HKEY};
use winapi::um::winnt::{KEY_READ, REG_DWORD, REG_SZ};
use winapi::um::winreg::{RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY_CURRENT_USER};
unsafe {
let key_path = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\0"
.encode_utf16()
.collect::<Vec<u16>>();
let mut hkey: HKEY = ptr::null_mut();
let result =
RegOpenKeyExW(HKEY_CURRENT_USER, key_path.as_ptr(), 0, KEY_READ, &mut hkey);
if result != 0 {
return Ok(AsyncSysproxy::default()); return Ok(AsyncSysproxy::default());
} }
let stdout = String::from_utf8_lossy(&output.stdout); // 检查代理是否启用
log::debug!(target: "app", "netsh proxy output: {}", stdout); let proxy_enable_name = "ProxyEnable\0".encode_utf16().collect::<Vec<u16>>();
let mut proxy_enable: DWORD = 0;
let mut buffer_size: DWORD = 4;
let mut value_type: DWORD = 0;
let enable_result = RegQueryValueExW(
hkey,
proxy_enable_name.as_ptr(),
ptr::null_mut(),
&mut value_type,
&mut proxy_enable as *mut DWORD as *mut u8,
&mut buffer_size,
);
if enable_result != 0 || value_type != REG_DWORD || proxy_enable == 0 {
RegCloseKey(hkey);
return Ok(AsyncSysproxy::default());
}
// 读取代理服务器设置
let proxy_server_name = "ProxyServer\0".encode_utf16().collect::<Vec<u16>>();
let mut buffer = vec![0u16; 1024];
let mut buffer_size: DWORD = (buffer.len() * 2) as DWORD;
let mut value_type: DWORD = 0;
let server_result = RegQueryValueExW(
hkey,
proxy_server_name.as_ptr(),
ptr::null_mut(),
&mut value_type,
buffer.as_mut_ptr() as *mut u8,
&mut buffer_size,
);
let mut proxy_enabled = false;
let mut proxy_server = String::new(); let mut proxy_server = String::new();
if server_result == 0 && value_type == REG_SZ && buffer_size > 0 {
let end_pos = buffer.iter().position(|&x| x == 0).unwrap_or(buffer.len());
proxy_server = String::from_utf16_lossy(&buffer[..end_pos]);
}
// 读取代理绕过列表
let proxy_override_name = "ProxyOverride\0".encode_utf16().collect::<Vec<u16>>();
let mut bypass_buffer = vec![0u16; 1024];
let mut bypass_buffer_size: DWORD = (bypass_buffer.len() * 2) as DWORD;
let mut bypass_value_type: DWORD = 0;
let override_result = RegQueryValueExW(
hkey,
proxy_override_name.as_ptr(),
ptr::null_mut(),
&mut bypass_value_type,
bypass_buffer.as_mut_ptr() as *mut u8,
&mut bypass_buffer_size,
);
let mut bypass_list = String::new(); let mut bypass_list = String::new();
if override_result == 0 && bypass_value_type == REG_SZ && bypass_buffer_size > 0 {
for line in stdout.lines() { let end_pos = bypass_buffer
let line = line.trim(); .iter()
if line.starts_with("代理服务器") || line.starts_with("Proxy Server") { .position(|&x| x == 0)
if let Some(server_part) = line.split(':').nth(1) { .unwrap_or(bypass_buffer.len());
let server = server_part.trim(); bypass_list = String::from_utf16_lossy(&bypass_buffer[..end_pos]);
if !server.is_empty() && server != "(none)" && server != "" {
proxy_server = server.to_string();
proxy_enabled = true;
}
}
} else if line.starts_with("绕过列表") || line.starts_with("Bypass List") {
if let Some(bypass_part) = line.split(':').nth(1) {
bypass_list = bypass_part.trim().to_string();
}
}
} }
if proxy_enabled && !proxy_server.is_empty() { RegCloseKey(hkey);
if !proxy_server.is_empty() {
// 解析服务器地址和端口 // 解析服务器地址和端口
let (host, port) = if let Some(colon_pos) = proxy_server.rfind(':') { let (host, port) = if let Some(colon_pos) = proxy_server.rfind(':') {
let host = proxy_server[..colon_pos].to_string(); let host = proxy_server[..colon_pos].to_string();
@@ -256,6 +361,8 @@ impl AsyncProxyQuery {
(proxy_server, 8080) (proxy_server, 8080)
}; };
log::debug!(target: "app", "从注册表读取到代理设置: {}:{}, bypass: {}", host, port, bypass_list);
Ok(AsyncSysproxy { Ok(AsyncSysproxy {
enable: true, enable: true,
host, host,
@@ -266,6 +373,7 @@ impl AsyncProxyQuery {
Ok(AsyncSysproxy::default()) Ok(AsyncSysproxy::default())
} }
} }
}
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
async fn get_system_proxy_impl() -> Result<AsyncSysproxy> { async fn get_system_proxy_impl() -> Result<AsyncSysproxy> {

View File

@@ -526,16 +526,10 @@ impl EventDrivenProxyManager {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
async fn execute_sysproxy_command(args: &[&str]) { async fn execute_sysproxy_command(args: &[&str]) {
use crate::{core::handle::Handle, utils::dirs}; use crate::utils::dirs;
use tauri_plugin_shell::ShellExt; #[allow(unused_imports)] // creation_flags必须
use std::os::windows::process::CommandExt;
let app_handle = match Handle::global().app_handle() { use tokio::process::Command;
Some(handle) => handle,
None => {
log::error!(target: "app", "获取应用句柄失败");
return;
}
};
let binary_path = match dirs::service_path() { let binary_path = match dirs::service_path() {
Ok(path) => path, Ok(path) => path,
@@ -551,10 +545,9 @@ impl EventDrivenProxyManager {
return; return;
} }
let shell = app_handle.shell(); let output = Command::new(sysproxy_exe)
let output = shell
.command(sysproxy_exe.as_path().to_str().unwrap())
.args(args) .args(args)
.creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏窗口
.output() .output()
.await; .await;
@@ -562,6 +555,12 @@ impl EventDrivenProxyManager {
Ok(output) => { Ok(output) => {
if !output.status.success() { if !output.status.success() {
log::error!(target: "app", "执行sysproxy命令失败: {:?}", args); log::error!(target: "app", "执行sysproxy命令失败: {:?}", args);
let stderr = String::from_utf8_lossy(&output.stderr);
if !stderr.is_empty() {
log::error!(target: "app", "sysproxy错误输出: {}", stderr);
}
} else {
log::debug!(target: "app", "成功执行sysproxy命令: {:?}", args);
} }
} }
Err(e) => { Err(e) => {

View File

@@ -4,7 +4,7 @@ use anyhow::{anyhow, Result};
use log::info; use log::info;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
use std::{fs, path::Path, path::PathBuf}; use std::{fs, os::windows::process::CommandExt, path::Path, path::PathBuf};
/// Windows 下的开机启动文件夹路径 /// Windows 下的开机启动文件夹路径
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@@ -59,6 +59,8 @@ pub fn create_shortcut() -> Result<()> {
let output = std::process::Command::new("powershell") let output = std::process::Command::new("powershell")
.args(["-Command", &powershell_command]) .args(["-Command", &powershell_command])
// 隐藏 PowerShell 窗口
.creation_flags(0x08000000) // CREATE_NO_WINDOW
.output() .output()
.map_err(|e| anyhow!("执行 PowerShell 命令失败: {}", e))?; .map_err(|e| anyhow!("执行 PowerShell 命令失败: {}", e))?;