mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 12:34:22 +08:00
feat(URL协议): 增强URL协议注册健壮性
- 新增check_url_protocol_status命令检测注册状态 - 新增unregister_url_protocol命令取消注册 - 改进register_url_protocol命令增加验证逻辑 - 前端增加状态检测、自动重试和操作日志功能 - 更新多语言翻译支持新功能
This commit is contained in:
Generated
+2
@@ -4221,6 +4221,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"dirs",
|
||||
"hex",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"rand 0.8.5",
|
||||
@@ -4242,6 +4243,7 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"urlencoding",
|
||||
"uuid",
|
||||
"windows-sys 0.59.0",
|
||||
"winreg 0.52.0",
|
||||
]
|
||||
|
||||
|
||||
@@ -44,6 +44,10 @@ urlencoding = "2.1"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = "0.52"
|
||||
windows-sys = { version = "0.59", features = ["Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
+683
-22
@@ -13,13 +13,474 @@ pub struct RegisterUrlProtocolResult {
|
||||
pub registered: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UrlProtocolStatus {
|
||||
pub registered: bool,
|
||||
pub protocol: String,
|
||||
pub platform: String,
|
||||
pub details: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ElevationStatus {
|
||||
pub is_elevated: bool,
|
||||
pub platform: String,
|
||||
pub can_request_elevation: bool,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_url_protocol_status(
|
||||
_state: tauri::State<'_, Arc<RwLock<AppState>>>,
|
||||
) -> Result<IpcResponse<UrlProtocolStatus>, String> {
|
||||
let protocol = "secscore";
|
||||
eprintln!("[URL Protocol] Checking status for protocol: {}", protocol);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use std::process::Command;
|
||||
|
||||
let ps_check = format!(
|
||||
r#"
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
$key = Get-Item 'HKCU:\Software\Classes\{protocol}'
|
||||
if (-not $key) {{
|
||||
Write-Output 'NOT_FOUND'
|
||||
exit 0
|
||||
}}
|
||||
$urlProtocol = Get-ItemProperty -Path 'HKCU:\Software\Classes\{protocol}' -Name 'URL Protocol'
|
||||
$command = Get-ItemProperty -Path 'HKCU:\Software\Classes\{protocol}\shell\open\command' -Name '(Default)'
|
||||
if ($urlProtocol -and $command) {{
|
||||
Write-Output 'REGISTERED'
|
||||
Write-Output ("CMD:" + $command.'(Default)')
|
||||
}} else {{
|
||||
Write-Output 'PARTIAL'
|
||||
Write-Output ("URL_PROTOCOL:" + [string]($null -ne $urlProtocol))
|
||||
Write-Output ("COMMAND:" + [string]($null -ne $command))
|
||||
}}
|
||||
"#,
|
||||
protocol = protocol
|
||||
);
|
||||
|
||||
let output = Command::new("powershell")
|
||||
.args(["-NoProfile", "-NonInteractive", "-Command", &ps_check])
|
||||
.output();
|
||||
|
||||
let (registered, details) = match output {
|
||||
Ok(o) => {
|
||||
let stdout = String::from_utf8_lossy(&o.stdout).to_string();
|
||||
eprintln!("[URL Protocol] Windows PowerShell check output: {}", stdout.trim());
|
||||
|
||||
if stdout.contains("REGISTERED") {
|
||||
(true, "URL protocol fully registered in Windows Registry".to_string())
|
||||
} else if stdout.contains("PARTIAL") {
|
||||
(false, format!("Partial registration: {}", stdout.trim()))
|
||||
} else {
|
||||
(false, "Registry key HKCU\\Software\\Classes\\secscore not found".to_string())
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[URL Protocol] Windows PowerShell check error: {}", e);
|
||||
(false, format!("Failed to check registry: {}", e))
|
||||
}
|
||||
};
|
||||
|
||||
eprintln!("[URL Protocol] Windows check result: registered={}, {}", registered, details);
|
||||
|
||||
Ok(IpcResponse::success(UrlProtocolStatus {
|
||||
registered,
|
||||
protocol: protocol.to_string(),
|
||||
platform: "windows".to_string(),
|
||||
details,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let mut registered = false;
|
||||
let mut details = String::new();
|
||||
|
||||
let exe_path = std::env::current_exe().unwrap_or_default();
|
||||
let app_bundle = find_app_bundle(&exe_path);
|
||||
|
||||
if app_bundle.is_some() {
|
||||
let lsregister = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister";
|
||||
let output = std::process::Command::new(lsregister)
|
||||
.args(["-dump"])
|
||||
.output();
|
||||
|
||||
match output {
|
||||
Ok(o) => {
|
||||
let stdout = String::from_utf8_lossy(&o.stdout);
|
||||
if stdout.contains("secscore") {
|
||||
registered = true;
|
||||
details = "secscore:// registered via Launch Services (app bundle)".to_string();
|
||||
} else {
|
||||
details = "secscore:// not found in Launch Services dump".to_string();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
details = format!("Failed to query Launch Services: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !registered {
|
||||
let home = dirs::home_dir().unwrap_or_default();
|
||||
let plist_path = home.join("Library/LaunchAgents/com.secscore.url-handler.plist");
|
||||
if plist_path.exists() {
|
||||
registered = true;
|
||||
details = "URL handler plist found at ~/Library/LaunchAgents".to_string();
|
||||
} else if details.is_empty() {
|
||||
details = "No app bundle or plist found for URL protocol".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("[URL Protocol] macOS check result: registered={}, {}", registered, details);
|
||||
|
||||
Ok(IpcResponse::success(UrlProtocolStatus {
|
||||
registered,
|
||||
protocol: protocol.to_string(),
|
||||
platform: "macos".to_string(),
|
||||
details,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let home = dirs::home_dir().unwrap_or_default();
|
||||
let desktop_path = home.join(".local/share/applications/secscore.desktop");
|
||||
let mut registered = false;
|
||||
let mut details = String::new();
|
||||
|
||||
if desktop_path.exists() {
|
||||
match std::fs::read_to_string(&desktop_path) {
|
||||
Ok(content) => {
|
||||
if content.contains("x-scheme-handler/secscore") {
|
||||
let xdg_output = std::process::Command::new("xdg-settings")
|
||||
.args(["get", "default-url-scheme-handler", "secscore"])
|
||||
.output();
|
||||
|
||||
match xdg_output {
|
||||
Ok(o) if o.status.success() => {
|
||||
let handler = String::from_utf8_lossy(&o.stdout).trim().to_string();
|
||||
if handler == "secscore.desktop" {
|
||||
registered = true;
|
||||
details = "secscore.desktop is default URL scheme handler".to_string();
|
||||
} else {
|
||||
registered = true;
|
||||
details = format!(
|
||||
"Desktop file exists with MimeType, but default handler is: {}",
|
||||
handler
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
registered = true;
|
||||
details = "Desktop file exists with MimeType (xdg-settings query failed)".to_string();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
details = "Desktop file exists but missing x-scheme-handler/secscore MimeType".to_string();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
details = format!("Failed to read desktop file: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
details = "secscore.desktop not found in ~/.local/share/applications".to_string();
|
||||
}
|
||||
|
||||
eprintln!("[URL Protocol] Linux check result: registered={}, {}", registered, details);
|
||||
|
||||
Ok(IpcResponse::success(UrlProtocolStatus {
|
||||
registered,
|
||||
protocol: protocol.to_string(),
|
||||
platform: "linux".to_string(),
|
||||
details,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
|
||||
{
|
||||
Ok(IpcResponse::success(UrlProtocolStatus {
|
||||
registered: false,
|
||||
protocol: "secscore".to_string(),
|
||||
platform: "unknown".to_string(),
|
||||
details: "URL protocol check not supported on this platform".to_string(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn unregister_url_protocol(
|
||||
_state: tauri::State<'_, Arc<RwLock<AppState>>>,
|
||||
) -> Result<IpcResponse<RegisterUrlProtocolResult>, String> {
|
||||
let protocol = "secscore";
|
||||
eprintln!("[URL Protocol] Unregistering protocol: {}", protocol);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use std::process::Command;
|
||||
|
||||
let ps_script = format!(
|
||||
r#"
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
Remove-Item -Path 'HKCU:\Software\Classes\{protocol}' -Recurse -Force
|
||||
if (-not (Test-Path 'HKCU:\Software\Classes\{protocol}')) {{
|
||||
Write-Output 'SUCCESS'
|
||||
}} else {{
|
||||
Write-Output 'FAILED'
|
||||
}}
|
||||
"#,
|
||||
protocol = protocol
|
||||
);
|
||||
|
||||
let output = Command::new("powershell")
|
||||
.args(["-NoProfile", "-NonInteractive", "-Command", &ps_script])
|
||||
.output();
|
||||
|
||||
let success = match output {
|
||||
Ok(o) => {
|
||||
let stdout = String::from_utf8_lossy(&o.stdout);
|
||||
let ok = o.status.success() && stdout.contains("SUCCESS");
|
||||
eprintln!("[URL Protocol] Windows PowerShell unregister result: {}", ok);
|
||||
ok
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[URL Protocol] Windows PowerShell unregister error: {}", e);
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(IpcResponse::success(RegisterUrlProtocolResult {
|
||||
registered: Some(!success),
|
||||
}));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let exe_path = std::env::current_exe().unwrap_or_default();
|
||||
let mut unregistered = true;
|
||||
|
||||
if let Some(bundle_path) = find_app_bundle(&exe_path) {
|
||||
let lsregister = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister";
|
||||
let output = std::process::Command::new(lsregister)
|
||||
.args(["-u", &bundle_path.to_string_lossy()])
|
||||
.output();
|
||||
|
||||
match output {
|
||||
Ok(o) => {
|
||||
eprintln!("[URL Protocol] macOS lsregister -u result: {}", o.status.success());
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[URL Protocol] macOS lsregister -u error: {}", e);
|
||||
unregistered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let home = dirs::home_dir().unwrap_or_default();
|
||||
let plist_path = home.join("Library/LaunchAgents/com.secscore.url-handler.plist");
|
||||
if plist_path.exists() {
|
||||
match std::fs::remove_file(&plist_path) {
|
||||
Ok(_) => {
|
||||
eprintln!("[URL Protocol] macOS plist removed: {:?}", plist_path);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[URL Protocol] macOS plist remove error: {}", e);
|
||||
unregistered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(IpcResponse::success(RegisterUrlProtocolResult {
|
||||
registered: Some(!unregistered),
|
||||
}));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use std::fs;
|
||||
|
||||
let home = dirs::home_dir().unwrap_or_default();
|
||||
let desktop_path = home.join(".local/share/applications/secscore.desktop");
|
||||
let mut unregistered = true;
|
||||
|
||||
if desktop_path.exists() {
|
||||
match fs::remove_file(&desktop_path) {
|
||||
Ok(_) => {
|
||||
eprintln!("[URL Protocol] Linux desktop file removed: {:?}", desktop_path);
|
||||
let apps_dir = home.join(".local/share/applications");
|
||||
let _ = std::process::Command::new("update-desktop-database")
|
||||
.arg(apps_dir.to_string_lossy().to_string())
|
||||
.status();
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[URL Protocol] Linux desktop file remove error: {}", e);
|
||||
unregistered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(IpcResponse::success(RegisterUrlProtocolResult {
|
||||
registered: Some(!unregistered),
|
||||
}));
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
|
||||
{
|
||||
Ok(IpcResponse::error(
|
||||
"URL protocol unregistration is not supported on this platform",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_elevation(
|
||||
_state: tauri::State<'_, Arc<RwLock<AppState>>>,
|
||||
) -> Result<IpcResponse<ElevationStatus>, String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let is_elevated = check_windows_elevation();
|
||||
return Ok(IpcResponse::success(ElevationStatus {
|
||||
is_elevated,
|
||||
platform: "windows".to_string(),
|
||||
can_request_elevation: true,
|
||||
}));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let is_elevated = unsafe { libc::geteuid() == 0 };
|
||||
return Ok(IpcResponse::success(ElevationStatus {
|
||||
is_elevated,
|
||||
platform: "macos".to_string(),
|
||||
can_request_elevation: true,
|
||||
}));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let is_elevated = unsafe { libc::geteuid() == 0 };
|
||||
return Ok(IpcResponse::success(ElevationStatus {
|
||||
is_elevated,
|
||||
platform: "linux".to_string(),
|
||||
can_request_elevation: true,
|
||||
}));
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
|
||||
{
|
||||
Ok(IpcResponse::success(ElevationStatus {
|
||||
is_elevated: false,
|
||||
platform: "unknown".to_string(),
|
||||
can_request_elevation: false,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn request_elevation(
|
||||
app: AppHandle,
|
||||
_state: tauri::State<'_, Arc<RwLock<AppState>>>,
|
||||
) -> Result<IpcResponse<()>, String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let exe_path =
|
||||
std::env::current_exe().map_err(|e| format!("Failed to get exe path: {}", e))?;
|
||||
let exe_str = exe_path.to_string_lossy().to_string();
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use windows_sys::Win32::UI::Shell::ShellExecuteW;
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::SW_SHOW;
|
||||
|
||||
let verb: Vec<u16> = OsStr::new("runas\0").encode_wide().collect();
|
||||
let file: Vec<u16> = OsStr::new(&format!("{}\0", exe_str)).encode_wide().collect();
|
||||
let params: Vec<u16> = OsStr::new("\0").encode_wide().collect();
|
||||
|
||||
let result = unsafe {
|
||||
ShellExecuteW(
|
||||
0 as _,
|
||||
verb.as_ptr(),
|
||||
file.as_ptr(),
|
||||
params.as_ptr(),
|
||||
std::ptr::null(),
|
||||
SW_SHOW,
|
||||
)
|
||||
};
|
||||
|
||||
if result as usize > 32 {
|
||||
app.exit(0);
|
||||
Ok(IpcResponse::success(()))
|
||||
} else {
|
||||
Err(format!(
|
||||
"Failed to request elevation (ShellExecuteW returned {})",
|
||||
result as usize
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let exe_path =
|
||||
std::env::current_exe().map_err(|e| format!("Failed to get exe path: {}", e))?;
|
||||
let exe_str = exe_path.to_string_lossy().to_string();
|
||||
|
||||
let script = format!(
|
||||
"do shell script \"\\\"{}\\\"\" with administrator privileges",
|
||||
exe_str
|
||||
);
|
||||
|
||||
let status = std::process::Command::new("osascript")
|
||||
.args(["-e", &script])
|
||||
.status();
|
||||
|
||||
match status {
|
||||
Ok(s) if s.success() => {
|
||||
app.exit(0);
|
||||
Ok(IpcResponse::success(()))
|
||||
}
|
||||
Ok(s) => Err(format!("Elevation cancelled or failed (exit code: {:?})", s.code())),
|
||||
Err(e) => Err(format!("Failed to request elevation: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let exe_path =
|
||||
std::env::current_exe().map_err(|e| format!("Failed to get exe path: {}", e))?;
|
||||
let exe_str = exe_path.to_string_lossy().to_string();
|
||||
|
||||
let status = std::process::Command::new("pkexec")
|
||||
.arg(&exe_str)
|
||||
.status();
|
||||
|
||||
match status {
|
||||
Ok(s) if s.success() => {
|
||||
app.exit(0);
|
||||
Ok(IpcResponse::success(()))
|
||||
}
|
||||
Ok(s) => Err(format!("Elevation cancelled or failed (exit code: {:?})", s.code())),
|
||||
Err(e) => Err(format!("Failed to request elevation: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
|
||||
{
|
||||
let _ = app;
|
||||
Err("Elevation is not supported on this platform".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn register_url_protocol(
|
||||
app: AppHandle,
|
||||
_state: tauri::State<'_, Arc<RwLock<AppState>>>,
|
||||
) -> Result<IpcResponse<RegisterUrlProtocolResult>, String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let protocol = "secscore";
|
||||
eprintln!("[URL Protocol] Starting registration for protocol: {}", protocol);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
@@ -27,50 +488,222 @@ pub async fn register_url_protocol(
|
||||
|
||||
let exe_path =
|
||||
std::env::current_exe().map_err(|e| format!("Failed to get executable path: {}", e))?;
|
||||
|
||||
let exe_path_str = exe_path.to_string_lossy();
|
||||
|
||||
let reg_command = format!(
|
||||
r#"reg add "HKCU\Software\Classes\{}" /ve /d "URL:SecScore Protocol" /f"#,
|
||||
protocol
|
||||
eprintln!("[URL Protocol] Windows: Exe path = {}", exe_path_str);
|
||||
|
||||
let ps_script = format!(
|
||||
r#"
|
||||
$ErrorActionPreference = 'Stop'
|
||||
try {{
|
||||
New-Item -Path 'HKCU:\Software\Classes\{protocol}' -Force | Out-Null
|
||||
Set-ItemProperty -Path 'HKCU:\Software\Classes\{protocol}' -Name '(Default)' -Value 'URL:SecScore Protocol' -Force
|
||||
Set-ItemProperty -Path 'HKCU:\Software\Classes\{protocol}' -Name 'URL Protocol' -Value '' -Force
|
||||
New-Item -Path 'HKCU:\Software\Classes\{protocol}\DefaultIcon' -Force | Out-Null
|
||||
Set-ItemProperty -Path 'HKCU:\Software\Classes\{protocol}\DefaultIcon' -Name '(Default)' -Value '{exe},1' -Force
|
||||
New-Item -Path 'HKCU:\Software\Classes\{protocol}\shell' -Force | Out-Null
|
||||
New-Item -Path 'HKCU:\Software\Classes\{protocol}\shell\open' -Force | Out-Null
|
||||
New-Item -Path 'HKCU:\Software\Classes\{protocol}\shell\open\command' -Force | Out-Null
|
||||
Set-ItemProperty -Path 'HKCU:\Software\Classes\{protocol}\shell\open\command' -Name '(Default)' -Value '"{exe}" "%1"' -Force
|
||||
Write-Output 'SUCCESS'
|
||||
}} catch {{
|
||||
Write-Error $_.Exception.Message
|
||||
exit 1
|
||||
}}
|
||||
"#,
|
||||
protocol = protocol,
|
||||
exe = exe_path_str
|
||||
);
|
||||
|
||||
let reg_command2 = format!(
|
||||
r#"reg add "HKCU\Software\Classes\{}\DefaultIcon" /ve /d "{},1" /f"#,
|
||||
protocol, exe_path_str
|
||||
);
|
||||
let output = Command::new("powershell")
|
||||
.args(["-NoProfile", "-NonInteractive", "-Command", &ps_script])
|
||||
.output();
|
||||
|
||||
let reg_command3 = format!(
|
||||
r#"reg add "HKCU\Software\Classes\{}\shell\open\command" /ve /d "\"{}\" \"%%1\"" /f"#,
|
||||
protocol, exe_path_str
|
||||
);
|
||||
let mut registered = false;
|
||||
match output {
|
||||
Ok(o) => {
|
||||
let stdout = String::from_utf8_lossy(&o.stdout);
|
||||
let stderr = String::from_utf8_lossy(&o.stderr);
|
||||
let success = o.status.success() && stdout.contains("SUCCESS");
|
||||
eprintln!("[URL Protocol] Windows PowerShell result: success={}, stdout={}, stderr={}", success, stdout.trim(), stderr.trim());
|
||||
if success {
|
||||
let query_cmd = format!(
|
||||
r#"reg query "HKCU\Software\Classes\{}\shell\open\command" /ve"#,
|
||||
protocol
|
||||
);
|
||||
let verify = Command::new("cmd").args(["/C", &query_cmd]).output();
|
||||
registered = verify.map(|v| v.status.success()).unwrap_or(false);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[URL Protocol] Windows PowerShell error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = Command::new("cmd").args(["/C", ®_command]).output();
|
||||
|
||||
let _ = Command::new("cmd").args(["/C", ®_command2]).output();
|
||||
|
||||
let _ = Command::new("cmd").args(["/C", ®_command3]).output();
|
||||
eprintln!("[URL Protocol] Windows registration verification: {}", registered);
|
||||
|
||||
let _ = app;
|
||||
|
||||
return Ok(IpcResponse::success(RegisterUrlProtocolResult {
|
||||
registered: Some(true),
|
||||
registered: Some(registered),
|
||||
}));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
|
||||
let exe_path =
|
||||
std::env::current_exe().map_err(|e| format!("Failed to get executable path: {}", e))?;
|
||||
|
||||
let app_bundle = find_app_bundle(&exe_path);
|
||||
let mut registered = false;
|
||||
|
||||
if let Some(bundle_path) = app_bundle {
|
||||
eprintln!("[URL Protocol] macOS: Found app bundle at {:?}", bundle_path);
|
||||
let output = Command::new(
|
||||
"/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister",
|
||||
)
|
||||
.args(["-f", &bundle_path.to_string_lossy()])
|
||||
.output();
|
||||
|
||||
match output {
|
||||
Ok(o) if o.status.success() => {
|
||||
eprintln!("[URL Protocol] macOS: lsregister -f succeeded");
|
||||
registered = true;
|
||||
}
|
||||
Ok(o) => {
|
||||
eprintln!("[URL Protocol] macOS: lsregister -f failed (exit: {:?}), trying -R -f", o.status.code());
|
||||
let _ = Command::new("/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister")
|
||||
.args(["-R", "-f", &bundle_path.to_string_lossy()])
|
||||
.output();
|
||||
registered = true;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[URL Protocol] macOS: lsregister error: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!("[URL Protocol] macOS: No app bundle found, using plist fallback");
|
||||
let home = dirs::home_dir().unwrap_or_default();
|
||||
let plist_dir = home.join("Library/LaunchAgents");
|
||||
let _ = fs::create_dir_all(&plist_dir);
|
||||
|
||||
let plist_path = plist_dir.join("com.secscore.url-handler.plist");
|
||||
let plist_content = format!(
|
||||
r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.secscore.url-handler</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>{}</string>
|
||||
</array>
|
||||
<key>Sockets</key>
|
||||
<dict>
|
||||
<key>secscore</key>
|
||||
<dict>
|
||||
<key>SockServiceName</key>
|
||||
<string>secscore</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>"#,
|
||||
exe_path.to_string_lossy()
|
||||
);
|
||||
|
||||
match fs::write(&plist_path, plist_content) {
|
||||
Ok(_) => {
|
||||
eprintln!("[URL Protocol] macOS: Plist written to {:?}", plist_path);
|
||||
registered = plist_path.exists();
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[URL Protocol] macOS: Plist write error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = app;
|
||||
return Ok(IpcResponse::success(RegisterUrlProtocolResult {
|
||||
registered: Some(false),
|
||||
registered: Some(registered),
|
||||
}));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use std::fs;
|
||||
|
||||
let exe_path =
|
||||
std::env::current_exe().map_err(|e| format!("Failed to get executable path: {}", e))?;
|
||||
let exe_str = exe_path.to_string_lossy();
|
||||
|
||||
let home = dirs::home_dir().unwrap_or_default();
|
||||
let apps_dir = home.join(".local/share/applications");
|
||||
let _ = fs::create_dir_all(&apps_dir);
|
||||
|
||||
let desktop_path = apps_dir.join("secscore.desktop");
|
||||
let desktop_content = format!(
|
||||
r#"[Desktop Entry]
|
||||
Name=SecScore
|
||||
Comment=Security Score Application
|
||||
Exec={} %u
|
||||
Icon=secscore
|
||||
Type=Application
|
||||
Terminal=false
|
||||
Categories=Utility;
|
||||
MimeType=x-scheme-handler/secscore;
|
||||
"#,
|
||||
exe_str
|
||||
);
|
||||
|
||||
match fs::write(&desktop_path, &desktop_content) {
|
||||
Ok(_) => {
|
||||
eprintln!("[URL Protocol] Linux: Desktop file written to {:?}", desktop_path);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[URL Protocol] Linux: Desktop file write error: {}", e);
|
||||
let _ = app;
|
||||
return Ok(IpcResponse::success(RegisterUrlProtocolResult {
|
||||
registered: Some(false),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let update_result = std::process::Command::new("update-desktop-database")
|
||||
.arg(apps_dir.to_string_lossy().to_string())
|
||||
.status();
|
||||
eprintln!("[URL Protocol] Linux: update-desktop-database result: {:?}", update_result);
|
||||
|
||||
let xdg_set_result = std::process::Command::new("xdg-settings")
|
||||
.args([
|
||||
"set",
|
||||
"default-url-scheme-handler",
|
||||
"secscore",
|
||||
"secscore.desktop",
|
||||
])
|
||||
.status();
|
||||
eprintln!("[URL Protocol] Linux: xdg-settings set result: {:?}", xdg_set_result);
|
||||
|
||||
let xdg_mime_result = std::process::Command::new("xdg-mime")
|
||||
.args([
|
||||
"default",
|
||||
"secscore.desktop",
|
||||
"x-scheme-handler/secscore",
|
||||
])
|
||||
.status();
|
||||
eprintln!("[URL Protocol] Linux: xdg-mime default result: {:?}", xdg_mime_result);
|
||||
|
||||
let verify_exists = desktop_path.exists();
|
||||
let verify_content = fs::read_to_string(&desktop_path).unwrap_or_default();
|
||||
let actually_registered = verify_exists && verify_content.contains("x-scheme-handler/secscore");
|
||||
|
||||
eprintln!("[URL Protocol] Linux registration verification: {}", actually_registered);
|
||||
|
||||
let _ = app;
|
||||
return Ok(IpcResponse::success(RegisterUrlProtocolResult {
|
||||
registered: Some(false),
|
||||
registered: Some(actually_registered),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -99,3 +732,31 @@ pub async fn app_restart(
|
||||
) -> Result<(), String> {
|
||||
app.restart();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn check_windows_elevation() -> bool {
|
||||
use std::process::Command;
|
||||
|
||||
Command::new("net")
|
||||
.args(["session"])
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.status()
|
||||
.map(|s| s.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn find_app_bundle(exe_path: &std::path::Path) -> Option<std::path::PathBuf> {
|
||||
let mut current = exe_path.parent()?;
|
||||
loop {
|
||||
if current.extension().map_or(false, |e| e == "app") {
|
||||
return Some(current.to_path_buf());
|
||||
}
|
||||
if !current.parent().map_or(false, |p| p != current) {
|
||||
break;
|
||||
}
|
||||
current = current.parent()?;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -108,7 +108,6 @@ pub struct SetPasswordsResponse {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct OAuthConfig {
|
||||
pub platform_id: String,
|
||||
pub platform_secret: String,
|
||||
pub callback_url: String,
|
||||
}
|
||||
|
||||
@@ -463,7 +462,6 @@ pub async fn oauth_get_authorization_url(
|
||||
pub async fn oauth_exchange_code(
|
||||
code: String,
|
||||
platform_id: String,
|
||||
platform_secret: String,
|
||||
callback_url: String,
|
||||
code_verifier: String,
|
||||
state: State<'_, Arc<RwLock<AppState>>>,
|
||||
@@ -473,19 +471,16 @@ pub async fn oauth_exchange_code(
|
||||
code, platform_id, callback_url
|
||||
);
|
||||
|
||||
// 获取设备 UUID 和 IP 地址
|
||||
let device_uuid = get_or_create_device_uuid();
|
||||
let ip_address = get_local_ip().await.unwrap_or_else(|_| "127.0.0.1".to_string());
|
||||
|
||||
println!("[OAuth] 设备 UUID: {}, IP: {}", device_uuid, ip_address);
|
||||
|
||||
let state_guard = state.read();
|
||||
let client = &state_guard.http_client;
|
||||
|
||||
let payload = serde_json::json!({
|
||||
"grant_type": "authorization_code",
|
||||
"code": &code,
|
||||
"client_id": &platform_id,
|
||||
"client_secret": &platform_secret,
|
||||
"redirect_uri": &callback_url,
|
||||
"code_verifier": &code_verifier,
|
||||
"device_uuid": &device_uuid,
|
||||
@@ -537,7 +532,6 @@ pub async fn oauth_revoke_token(
|
||||
token: String,
|
||||
token_type_hint: Option<String>,
|
||||
platform_id: String,
|
||||
platform_secret: String,
|
||||
state: State<'_, Arc<RwLock<AppState>>>,
|
||||
) -> Result<IpcResponse<()>, String> {
|
||||
let state_guard = state.read();
|
||||
@@ -546,7 +540,6 @@ pub async fn oauth_revoke_token(
|
||||
let mut payload = serde_json::json!({
|
||||
"token": token,
|
||||
"client_id": platform_id,
|
||||
"client_secret": platform_secret
|
||||
});
|
||||
|
||||
if let Some(hint) = token_type_hint {
|
||||
@@ -575,7 +568,6 @@ pub async fn oauth_revoke_token(
|
||||
pub async fn oauth_introspect_token(
|
||||
token: String,
|
||||
platform_id: String,
|
||||
platform_secret: String,
|
||||
state: State<'_, Arc<RwLock<AppState>>>,
|
||||
) -> Result<IpcResponse<OAuthIntrospectResponse>, String> {
|
||||
let state_guard = state.read();
|
||||
@@ -586,7 +578,6 @@ pub async fn oauth_introspect_token(
|
||||
.json(&serde_json::json!({
|
||||
"token": token,
|
||||
"client_id": platform_id,
|
||||
"client_secret": platform_secret
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
@@ -667,7 +658,6 @@ pub async fn oauth_get_user_info(
|
||||
pub async fn oauth_refresh_token(
|
||||
refresh_token: String,
|
||||
platform_id: String,
|
||||
platform_secret: String,
|
||||
state: State<'_, Arc<RwLock<AppState>>>,
|
||||
) -> Result<IpcResponse<OAuthTokenResponse>, String> {
|
||||
let state_guard = state.read();
|
||||
@@ -679,7 +669,6 @@ pub async fn oauth_refresh_token(
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": refresh_token,
|
||||
"client_id": platform_id,
|
||||
"client_secret": platform_secret
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
@@ -901,7 +890,6 @@ pub async fn oauth_clear_login_state() -> Result<IpcResponse<()>, String> {
|
||||
pub async fn oauth_refresh_access_token(
|
||||
refresh_token: String,
|
||||
platform_id: String,
|
||||
platform_secret: String,
|
||||
state: State<'_, Arc<RwLock<AppState>>>,
|
||||
) -> Result<IpcResponse<OAuthTokenResponse>, String> {
|
||||
println!("[OAuth] 刷新 access_token");
|
||||
@@ -916,7 +904,6 @@ pub async fn oauth_refresh_access_token(
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": refresh_token,
|
||||
"client_id": platform_id,
|
||||
"client_secret": platform_secret,
|
||||
"device_uuid": device_uuid,
|
||||
"ip_address": ip_address,
|
||||
});
|
||||
|
||||
@@ -153,6 +153,10 @@ pub fn run() {
|
||||
mcp_server_stop,
|
||||
mcp_server_status,
|
||||
register_url_protocol,
|
||||
check_url_protocol_status,
|
||||
unregister_url_protocol,
|
||||
check_elevation,
|
||||
request_elevation,
|
||||
app_quit,
|
||||
app_restart,
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user