diff --git a/src-tauri/src/bin/proxy_server.rs b/src-tauri/src/bin/proxy_server.rs index 7546f489..a72ad2a8 100644 --- a/src-tauri/src/bin/proxy_server.rs +++ b/src-tauri/src/bin/proxy_server.rs @@ -174,7 +174,7 @@ async fn main() { Command::new("start") .about( "Start a worker serving authenticated SOCKS5 on 127.0.0.1. The share link is \ - read from DONUT_PROXY_SHARE_LINK, never from the command line", + read from the first line of stdin, never from the command line", ) .arg( Arg::new("owner-pid") @@ -390,25 +390,22 @@ async fn main() { let owner_pid = *start_matches .get_one::("owner-pid") .expect("owner-pid is required"); - let share_link = std::env::var("DONUT_PROXY_SHARE_LINK") - .ok() - .filter(|link| !link.trim().is_empty()); - // The worker processes started below inherit this environment. - std::env::remove_var("DONUT_PROXY_SHARE_LINK"); - let Some(share_link) = share_link else { - eprintln!("DONUT_PROXY_SHARE_LINK is not set"); + let mut share_link = String::new(); + if std::io::stdin().read_line(&mut share_link).is_err() || share_link.trim().is_empty() { + eprintln!("No share link on stdin"); process::exit(2); - }; + } - // The detached worker would otherwise inherit the caller's stdout and - // stderr pipes, and a caller that reads to EOF would wait for the worker - // to exit. Same fix as `proxy start` (proxy_runner.rs). + // The detached worker would otherwise inherit the caller's pipes, and a + // caller that reads to EOF would wait for the worker to exit. Same fix + // as `proxy start` (proxy_runner.rs). #[cfg(windows)] { use std::os::windows::io::AsRawHandle; use windows::Win32::Foundation::{SetHandleInformation, HANDLE, HANDLE_FLAGS}; const HANDLE_FLAG_INHERIT: u32 = 0x00000001; for handle in [ + std::io::stdin().as_raw_handle(), std::io::stdout().as_raw_handle(), std::io::stderr().as_raw_handle(), ] { diff --git a/src-tauri/tests/donut_proxy_integration.rs b/src-tauri/tests/donut_proxy_integration.rs index 69beab17..39ec37b8 100644 --- a/src-tauri/tests/donut_proxy_integration.rs +++ b/src-tauri/tests/donut_proxy_integration.rs @@ -2172,6 +2172,28 @@ async fn port_refuses_connections(port: u16) -> bool { false } +/// Runs `donut-proxy xray ...` with `input` on stdin, where `start` reads the +/// share link. +async fn run_xray_cli( + binary: std::path::PathBuf, + cache: std::path::PathBuf, + args: Vec, + input: String, +) -> std::io::Result { + let mut child = tokio::process::Command::new(binary) + .arg("xray") + .args(args) + .env("DONUTBROWSER_CACHE_DIR", cache) + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn()?; + let mut stdin = child.stdin.take().expect("stdin is piped"); + stdin.write_all(input.as_bytes()).await?; + drop(stdin); + child.wait_with_output().await +} + /// `donut-proxy xray start|bind|stop` is what a remote host without the app /// drives, so its exit codes and output shape are a contract. This runs it /// against a real Xray-core VLESS server on loopback. @@ -2232,22 +2254,20 @@ async fn test_xray_cli_contract() -> Result<(), Box| { - let mut command = tokio::process::Command::new(&binary_path); - command - .arg("xray") - .args(args) - .env("DONUTBROWSER_CACHE_DIR", cache.path()) - .env_remove("DONUT_PROXY_SHARE_LINK"); - if let Some(link) = share_link { - command.env("DONUT_PROXY_SHARE_LINK", link); - } - command.output() + run_xray_cli( + binary_path.clone(), + cache.path().to_path_buf(), + args.iter().map(|arg| arg.to_string()).collect(), + share_link + .map(|link| format!("{link}\n")) + .unwrap_or_default(), + ) }; let owner = std::process::id().to_string(); let missing = xray_cli(&["start", "--owner-pid", &owner], None).await?; assert_eq!(missing.status.code(), Some(2)); - assert!(String::from_utf8_lossy(&missing.stderr).contains("DONUT_PROXY_SHARE_LINK")); + assert!(String::from_utf8_lossy(&missing.stderr).contains("stdin")); let malformed = xray_cli( &["start", "--owner-pid", &owner],