use std::path::{Path, PathBuf};
use super::hook_files::{quote_posix as sh_quote, quote_powershell as ps1_quote};
use crate::error::{OlError, ERR_MODEL_RELAY_IO};
const MARKER_OPEN_PREFIX: &str = "# >>> openlatch model-relay: ";
const MARKER_OPEN_SUFFIX: &str = " >>>";
const MARKER_CLOSE_PREFIX: &str = "# <<< openlatch model-relay: ";
const MARKER_CLOSE_SUFFIX: &str = " <<<";
fn io_err(e: std::io::Error) -> OlError {
OlError::new(ERR_MODEL_RELAY_IO, e.to_string())
}
pub fn env_sh_path(openlatch_dir: &Path) -> PathBuf {
openlatch_dir.join("model-relay").join("env.sh")
}
pub fn env_ps1_path(openlatch_dir: &Path) -> PathBuf {
openlatch_dir.join("model-relay").join("env.ps1")
}
pub fn live_marker_path(openlatch_dir: &Path) -> PathBuf {
openlatch_dir.join("model-relay").join("proxy-live")
}
pub fn set_live(live: bool) -> Result<(), OlError> {
let path = live_marker_path(&crate::config::openlatch_dir());
if live {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(io_err)?;
}
let pid = std::process::id().to_string();
crate::fs_secure::write_preserving_mode(&path, pid.as_bytes()).map_err(io_err)
} else {
match std::fs::remove_file(&path) {
Ok(()) => Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(io_err(e)),
}
}
}
pub fn relay_no_proxy(main_port: u16) -> String {
let mut entries = vec![format!("127.0.0.1:{main_port}")];
entries.extend(
crate::model_relay::endpoints::endpoint_port_block(main_port)
.map(|p| format!("127.0.0.1:{p}")),
);
entries.join(",")
}
fn valid_command(cmd: &str) -> bool {
let mut chars = cmd.chars();
matches!(chars.next(), Some(c) if c.is_ascii_alphanumeric())
&& chars.all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-'))
}
fn valid_agent(agent: &str) -> bool {
let mut chars = agent.chars();
matches!(chars.next(), Some(c) if c.is_ascii_lowercase() || c.is_ascii_digit())
&& chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
}
fn open_marker(agent: &str) -> String {
format!("{MARKER_OPEN_PREFIX}{agent}{MARKER_OPEN_SUFFIX}")
}
fn close_marker(agent: &str) -> String {
format!("{MARKER_CLOSE_PREFIX}{agent}{MARKER_CLOSE_SUFFIX}")
}
fn sh_preamble(marker: &Path, env_sh: &Path, url: &str, ca_pem: &Path, no_proxy: &str) -> String {
let marker = sh_quote(&marker.display().to_string());
let env_sh = sh_quote(&env_sh.display().to_string());
let url = sh_quote(url);
let ca_pem = sh_quote(&ca_pem.display().to_string());
format!(
"# Generated by OpenLatch (model relay). Rewritten on every wiring pass; do not edit.\n\
__openlatch_relay_exec() {{\n\
__openlatch_agent=$1\n\
__openlatch_cmd=$2\n\
shift 2\n\
if [ -f {marker} ] && kill -0 \"$(cat {marker} 2>/dev/null)\" 2>/dev/null && grep -qF \"# >>> openlatch model-relay: $__openlatch_agent >>>\" {env_sh} 2>/dev/null; then\n\
HTTPS_PROXY={url} HTTP_PROXY={url} https_proxy={url} http_proxy={url} \\\n\
NODE_EXTRA_CA_CERTS={ca_pem} \\\n\
NO_PROXY=\"${{NO_PROXY:+$NO_PROXY,}}{no_proxy}\" no_proxy=\"${{no_proxy:+$no_proxy,}}{no_proxy}\" \\\n\
command \"$__openlatch_cmd\" \"$@\"\n\
else\n\
command \"$__openlatch_cmd\" \"$@\"\n\
fi\n\
}}\n"
)
}
fn ps1_preamble(marker: &Path, env_ps1: &Path, url: &str, ca_pem: &Path, no_proxy: &str) -> String {
let marker = ps1_quote(&marker.display().to_string());
let env_ps1 = ps1_quote(&env_ps1.display().to_string());
let url = ps1_quote(url);
let ca_pem = ps1_quote(&ca_pem.display().to_string());
let no_proxy_q = ps1_quote(no_proxy);
format!(
"# Generated by OpenLatch (model relay). Rewritten on every wiring pass; do not edit.\n\
function global:__OpenlatchRelayExec {{\n\
$agent = $args[0]; $cmd = $args[1]; $rest = @($args | Select-Object -Skip 2)\n\
$app = Get-Command -Name $cmd -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1\n\
if (-not $app) {{ throw \"${{cmd}}: command not found\" }}\n\
$mpid = (Get-Content -LiteralPath {marker} -ErrorAction SilentlyContinue) -as [int]\n\
$live = $mpid -and (Get-Process -Id $mpid -ErrorAction SilentlyContinue) -and (Select-String -LiteralPath {env_ps1} -SimpleMatch -Quiet -Pattern \"# >>> openlatch model-relay: $agent >>>\")\n\
if (-not $live) {{ & $app @rest; return }}\n\
$names = 'HTTPS_PROXY','HTTP_PROXY','https_proxy','http_proxy','NODE_EXTRA_CA_CERTS','NO_PROXY','no_proxy'\n\
$saved = @{{}}; foreach ($n in $names) {{ $saved[$n] = [Environment]::GetEnvironmentVariable($n, 'Process') }}\n\
$np = $saved['NO_PROXY']; $lp = $saved['no_proxy']\n\
try {{\n\
foreach ($n in 'HTTPS_PROXY','HTTP_PROXY','https_proxy','http_proxy') {{ [Environment]::SetEnvironmentVariable($n, {url}, 'Process') }}\n\
[Environment]::SetEnvironmentVariable('NODE_EXTRA_CA_CERTS', {ca_pem}, 'Process')\n\
[Environment]::SetEnvironmentVariable('NO_PROXY', $(if ($np) {{ \"$np,{no_proxy}\" }} else {{ {no_proxy_q} }}), 'Process')\n\
[Environment]::SetEnvironmentVariable('no_proxy', $(if ($lp) {{ \"$lp,{no_proxy}\" }} else {{ {no_proxy_q} }}), 'Process')\n\
& $app @rest\n\
}} finally {{\n\
foreach ($n in $names) {{ [Environment]::SetEnvironmentVariable($n, $saved[$n], 'Process') }}\n\
}}\n\
}}\n"
)
}
fn sh_block(agent: &str, commands: &[&str]) -> String {
let mut out = format!("{}\n", open_marker(agent));
for cmd in commands {
out.push_str(&format!(
"alias {cmd} >/dev/null 2>&1 || eval '{cmd}() {{ __openlatch_relay_exec {agent} {cmd} \"$@\"; }}'\n"
));
}
out.push_str(&format!("{}\n", close_marker(agent)));
out
}
fn ps1_block(agent: &str, commands: &[&str]) -> String {
let mut out = format!("{}\n", open_marker(agent));
for cmd in commands {
out.push_str(&format!(
"function global:{cmd} {{ __OpenlatchRelayExec '{agent}' '{cmd}' @args }}\n"
));
}
out.push_str(&format!("{}\n", close_marker(agent)));
out
}
fn parse_blocks(existing: &str) -> Vec<(String, String)> {
let lines: Vec<&str> = existing.lines().collect();
let mut blocks = Vec::new();
let mut i = 0;
while i < lines.len() {
if let Some(agent) = lines[i]
.strip_prefix(MARKER_OPEN_PREFIX)
.and_then(|s| s.strip_suffix(MARKER_OPEN_SUFFIX))
{
let close = close_marker(agent);
if let Some(rel_end) = lines[i..].iter().position(|l| *l == close) {
let end = i + rel_end;
let block = format!("{}\n", lines[i..=end].join("\n"));
blocks.push((agent.to_string(), block));
i = end + 1;
continue;
}
}
i += 1;
}
blocks
}
fn preamble_only(existing: &str) -> String {
let mut offset = 0usize;
for raw_line in existing.split_inclusive('\n') {
let line = raw_line.strip_suffix('\n').unwrap_or(raw_line);
if line.starts_with(MARKER_OPEN_PREFIX) {
return existing[..offset].to_string();
}
offset += raw_line.len();
}
existing.to_string()
}
fn write_merged(path: &Path, preamble: &str, agent: &str, block: &str) -> Result<(), OlError> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(io_err)?;
}
let existing = std::fs::read_to_string(path).unwrap_or_default();
let mut blocks = parse_blocks(&existing);
match blocks.iter_mut().find(|(a, _)| a == agent) {
Some(entry) => entry.1 = block.to_string(),
None => blocks.push((agent.to_string(), block.to_string())),
}
let mut out = preamble.to_string();
for (_, b) in &blocks {
out.push_str(b);
}
crate::fs_secure::write_preserving_mode(path, out.as_bytes()).map_err(io_err)
}
pub fn write_block(
agent: &str,
commands: &[&str],
proxy_url: &str,
ca_pem: &Path,
no_proxy: &str,
) -> Result<(), OlError> {
if !valid_agent(agent) {
return Err(OlError::new(
ERR_MODEL_RELAY_IO,
format!("{agent:?} is not a valid agent identifier for the proxy wrapper"),
));
}
for cmd in commands {
if !valid_command(cmd) {
return Err(OlError::new(
ERR_MODEL_RELAY_IO,
format!("{cmd:?} is not a valid command name for the proxy wrapper"),
));
}
}
let ol = crate::config::openlatch_dir();
let env_sh = env_sh_path(&ol);
let env_ps1 = env_ps1_path(&ol);
let marker = live_marker_path(&ol);
write_merged(
&env_sh,
&sh_preamble(&marker, &env_sh, proxy_url, ca_pem, no_proxy),
agent,
&sh_block(agent, commands),
)?;
write_merged(
&env_ps1,
&ps1_preamble(&marker, &env_ps1, proxy_url, ca_pem, no_proxy),
agent,
&ps1_block(agent, commands),
)?;
Ok(())
}
pub fn remove_block(agent: &str) -> Result<bool, OlError> {
let ol = crate::config::openlatch_dir();
let mut removed = false;
for path in [env_sh_path(&ol), env_ps1_path(&ol)] {
let existing = match std::fs::read_to_string(&path) {
Ok(s) => s,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
Err(e) => return Err(io_err(e)),
};
let blocks = parse_blocks(&existing);
if !blocks.iter().any(|(a, _)| a == agent) {
continue;
}
removed = true;
let remaining: Vec<(String, String)> =
blocks.into_iter().filter(|(a, _)| a != agent).collect();
if remaining.is_empty() {
std::fs::remove_file(&path).map_err(io_err)?;
} else {
let mut out = preamble_only(&existing);
for (_, b) in &remaining {
out.push_str(b);
}
crate::fs_secure::write_preserving_mode(&path, out.as_bytes()).map_err(io_err)?;
}
}
Ok(removed)
}
fn marker_alive(path: &Path) -> bool {
let Ok(raw) = std::fs::read_to_string(path) else {
return false;
};
let Ok(pid) = raw.trim().parse::<u32>() else {
return false;
};
crate::cli::commands::lifecycle::is_process_alive(pid)
}
fn extract_https_proxy(content: &str) -> Option<String> {
const NEEDLE: &str = "HTTPS_PROXY='";
let start = content.find(NEEDLE)? + NEEDLE.len();
let rest = &content[start..];
let end = rest.find('\'')?;
Some(rest[..end].to_string())
}
pub(crate) fn live_proxy_url(agent: &str) -> Option<String> {
let ol = crate::config::openlatch_dir();
if !marker_alive(&live_marker_path(&ol)) {
return None;
}
let content = std::fs::read_to_string(env_sh_path(&ol)).ok()?;
if !parse_blocks(&content).iter().any(|(a, _)| a == agent) {
return None;
}
extract_https_proxy(&content)
}
pub(crate) fn has_blocks() -> bool {
let ol = crate::config::openlatch_dir();
for path in [env_sh_path(&ol), env_ps1_path(&ol)] {
if let Ok(content) = std::fs::read_to_string(&path) {
if !parse_blocks(&content).is_empty() {
return true;
}
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
fn tempdir() -> tempfile::TempDir {
tempfile::tempdir().expect("tempdir")
}
#[test]
fn valid_command_and_agent_accept_and_refuse() {
assert!(valid_command("fakecli"));
assert!(valid_command("a.b-c_9"));
assert!(!valid_command(""));
assert!(!valid_command("-oops"));
assert!(!valid_command("has space"));
assert!(valid_agent("fake-a"));
assert!(!valid_agent("Fake"));
assert!(!valid_agent(""));
}
#[test]
fn a_command_name_that_is_not_a_plain_word_is_refused() {
let dir = tempdir();
let _lock = crate::config::OPENLATCH_DIR_ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
std::env::set_var("OPENLATCH_DIR", dir.path());
for bad in ["a;b", "-x", ""] {
let err = write_block(
"fake-a",
&[bad],
"http://127.0.0.1:7600",
Path::new("/tmp/ca.pem"),
"127.0.0.1:7600",
)
.expect_err(&format!("{bad:?} must be refused"));
assert_eq!(err.code, ERR_MODEL_RELAY_IO);
}
assert!(!env_sh_path(dir.path()).exists());
assert!(!env_ps1_path(dir.path()).exists());
std::env::remove_var("OPENLATCH_DIR");
}
#[test]
fn two_agents_two_blocks_one_file() {
let dir = tempdir();
let _lock = crate::config::OPENLATCH_DIR_ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
std::env::set_var("OPENLATCH_DIR", dir.path());
write_block(
"fake-a",
&["fakecli"],
"http://127.0.0.1:7600",
Path::new("/tmp/ca.pem"),
"127.0.0.1:7600",
)
.expect("write fake-a");
write_block(
"fake-b",
&["othercli"],
"http://127.0.0.1:7600",
Path::new("/tmp/ca.pem"),
"127.0.0.1:7600",
)
.expect("write fake-b");
let sh = std::fs::read_to_string(env_sh_path(dir.path())).expect("env.sh");
let (_, b_block_before) = parse_blocks(&sh)
.into_iter()
.find(|(a, _)| a == "fake-b")
.expect("fake-b's block");
assert!(has_blocks());
assert!(remove_block("fake-a").expect("remove fake-a"));
let sh_after = std::fs::read_to_string(env_sh_path(dir.path())).expect("env.sh");
assert!(!sh_after.contains("openlatch model-relay: fake-a"));
let (_, b_block_after) = parse_blocks(&sh_after)
.into_iter()
.find(|(a, _)| a == "fake-b")
.expect("fake-b's block survives");
assert_eq!(
b_block_before, b_block_after,
"fake-b's block must be byte-identical"
);
assert!(remove_block("fake-b").expect("remove fake-b"));
assert!(
!env_sh_path(dir.path()).exists(),
"the file is deleted once empty"
);
assert!(!env_ps1_path(dir.path()).exists());
assert!(!has_blocks());
std::env::remove_var("OPENLATCH_DIR");
}
#[test]
fn write_block_is_idempotent_and_replaces() {
let dir = tempdir();
let _lock = crate::config::OPENLATCH_DIR_ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
std::env::set_var("OPENLATCH_DIR", dir.path());
write_block(
"fake-a",
&["fakecli"],
"http://127.0.0.1:7600",
Path::new("/tmp/ca.pem"),
"127.0.0.1:7600",
)
.expect("first write");
write_block(
"fake-a",
&["fakecli"],
"http://127.0.0.1:7700",
Path::new("/tmp/ca.pem"),
"127.0.0.1:7700",
)
.expect("second write");
let sh = std::fs::read_to_string(env_sh_path(dir.path())).unwrap();
assert_eq!(
sh.matches("openlatch model-relay: fake-a").count(),
2,
"one open marker and one close marker — never two of each: {sh}"
);
assert!(sh.contains("http://127.0.0.1:7700"));
assert!(
!sh.contains("http://127.0.0.1:7600"),
"the old port must not survive a rewrite"
);
std::env::remove_var("OPENLATCH_DIR");
}
#[test]
fn set_live_round_trip() {
let dir = tempdir();
let _lock = crate::config::OPENLATCH_DIR_ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
std::env::set_var("OPENLATCH_DIR", dir.path());
let marker = live_marker_path(dir.path());
set_live(true).expect("set_live true");
assert_eq!(
std::fs::read_to_string(&marker).unwrap(),
std::process::id().to_string()
);
set_live(true).expect("idempotent true");
assert_eq!(
std::fs::read_to_string(&marker).unwrap(),
std::process::id().to_string()
);
set_live(false).expect("set_live false");
assert!(!marker.exists());
set_live(false).expect("idempotent false");
assert!(!marker.exists());
std::env::remove_var("OPENLATCH_DIR");
}
#[test]
fn live_proxy_url_needs_the_marker_and_the_block() {
let dir = tempdir();
let _lock = crate::config::OPENLATCH_DIR_ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
std::env::set_var("OPENLATCH_DIR", dir.path());
write_block(
"fake-a",
&["fakecli"],
"http://127.0.0.1:7600",
Path::new("/tmp/ca.pem"),
"127.0.0.1:7600",
)
.expect("write");
assert_eq!(live_proxy_url("fake-a"), None, "no marker yet");
set_live(true).expect("set_live");
assert_eq!(
live_proxy_url("fake-a"),
Some("http://127.0.0.1:7600".to_string())
);
assert_eq!(live_proxy_url("fake-b"), None, "no such agent block");
set_live(false).expect("set_live off");
assert_eq!(live_proxy_url("fake-a"), None, "marker gone");
std::env::remove_var("OPENLATCH_DIR");
}
#[test]
fn relay_no_proxy_names_the_main_port_and_the_block() {
let np = relay_no_proxy(7600);
let entries: Vec<&str> = np.split(',').collect();
assert_eq!(entries.len(), 33);
assert_eq!(entries[0], "127.0.0.1:7600");
assert_eq!(*entries.last().unwrap(), "127.0.0.1:7632");
assert!(!np.contains("localhost"));
assert!(
!entries.contains(&"127.0.0.1"),
"a bare 127.0.0.1 must never appear"
);
}
#[test]
fn render_sh_is_exact() {
let marker = Path::new("/OL/model-relay/proxy-live");
let env_sh = Path::new("/OL/model-relay/env.sh");
let ca_pem = Path::new("/OL/model-relay/ca/ca.pem");
let no_proxy = relay_no_proxy(7600);
let rendered = format!(
"{}{}",
sh_preamble(marker, env_sh, "http://127.0.0.1:7600", ca_pem, &no_proxy),
sh_block("fake-a", &["fakecli"]),
);
let expected = format!(
"# Generated by OpenLatch (model relay). Rewritten on every wiring pass; do not edit.\n\
__openlatch_relay_exec() {{\n\
__openlatch_agent=$1\n\
__openlatch_cmd=$2\n\
shift 2\n\
if [ -f '/OL/model-relay/proxy-live' ] && kill -0 \"$(cat '/OL/model-relay/proxy-live' 2>/dev/null)\" 2>/dev/null && grep -qF \"# >>> openlatch model-relay: $__openlatch_agent >>>\" '/OL/model-relay/env.sh' 2>/dev/null; then\n\
HTTPS_PROXY='http://127.0.0.1:7600' HTTP_PROXY='http://127.0.0.1:7600' https_proxy='http://127.0.0.1:7600' http_proxy='http://127.0.0.1:7600' \\\n\
NODE_EXTRA_CA_CERTS='/OL/model-relay/ca/ca.pem' \\\n\
NO_PROXY=\"${{NO_PROXY:+$NO_PROXY,}}{no_proxy}\" no_proxy=\"${{no_proxy:+$no_proxy,}}{no_proxy}\" \\\n\
command \"$__openlatch_cmd\" \"$@\"\n\
else\n\
command \"$__openlatch_cmd\" \"$@\"\n\
fi\n\
}}\n\
# >>> openlatch model-relay: fake-a >>>\n\
alias fakecli >/dev/null 2>&1 || eval 'fakecli() {{ __openlatch_relay_exec fake-a fakecli \"$@\"; }}'\n\
# <<< openlatch model-relay: fake-a <<<\n"
);
assert_eq!(rendered, expected);
}
#[test]
fn render_ps1_is_exact() {
let marker = Path::new("C:\\OL\\model-relay\\proxy-live");
let env_ps1 = Path::new("C:\\OL\\model-relay\\env.ps1");
let ca_pem = Path::new("C:\\OL\\model-relay\\ca\\ca.pem");
let no_proxy = relay_no_proxy(7600);
let rendered = format!(
"{}{}",
ps1_preamble(marker, env_ps1, "http://127.0.0.1:7600", ca_pem, &no_proxy),
ps1_block("fake-a", &["fakecli"]),
);
let expected = format!(
"# Generated by OpenLatch (model relay). Rewritten on every wiring pass; do not edit.\n\
function global:__OpenlatchRelayExec {{\n\
$agent = $args[0]; $cmd = $args[1]; $rest = @($args | Select-Object -Skip 2)\n\
$app = Get-Command -Name $cmd -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1\n\
if (-not $app) {{ throw \"${{cmd}}: command not found\" }}\n\
$mpid = (Get-Content -LiteralPath 'C:\\OL\\model-relay\\proxy-live' -ErrorAction SilentlyContinue) -as [int]\n\
$live = $mpid -and (Get-Process -Id $mpid -ErrorAction SilentlyContinue) -and (Select-String -LiteralPath 'C:\\OL\\model-relay\\env.ps1' -SimpleMatch -Quiet -Pattern \"# >>> openlatch model-relay: $agent >>>\")\n\
if (-not $live) {{ & $app @rest; return }}\n\
$names = 'HTTPS_PROXY','HTTP_PROXY','https_proxy','http_proxy','NODE_EXTRA_CA_CERTS','NO_PROXY','no_proxy'\n\
$saved = @{{}}; foreach ($n in $names) {{ $saved[$n] = [Environment]::GetEnvironmentVariable($n, 'Process') }}\n\
$np = $saved['NO_PROXY']; $lp = $saved['no_proxy']\n\
try {{\n\
foreach ($n in 'HTTPS_PROXY','HTTP_PROXY','https_proxy','http_proxy') {{ [Environment]::SetEnvironmentVariable($n, 'http://127.0.0.1:7600', 'Process') }}\n\
[Environment]::SetEnvironmentVariable('NODE_EXTRA_CA_CERTS', 'C:\\OL\\model-relay\\ca\\ca.pem', 'Process')\n\
[Environment]::SetEnvironmentVariable('NO_PROXY', $(if ($np) {{ \"$np,{no_proxy}\" }} else {{ '{no_proxy}' }}), 'Process')\n\
[Environment]::SetEnvironmentVariable('no_proxy', $(if ($lp) {{ \"$lp,{no_proxy}\" }} else {{ '{no_proxy}' }}), 'Process')\n\
& $app @rest\n\
}} finally {{\n\
foreach ($n in $names) {{ [Environment]::SetEnvironmentVariable($n, $saved[$n], 'Process') }}\n\
}}\n\
}}\n\
# >>> openlatch model-relay: fake-a >>>\n\
function global:fakecli {{ __OpenlatchRelayExec 'fake-a' 'fakecli' @args }}\n\
# <<< openlatch model-relay: fake-a <<<\n"
);
assert_eq!(rendered, expected);
}
#[cfg(unix)]
fn write_executable(path: &Path, body: &str) {
use std::os::unix::fs::PermissionsExt;
std::fs::write(path, body).expect("write script");
let mut perms = std::fs::metadata(path).unwrap().permissions();
perms.set_mode(0o755);
std::fs::set_permissions(path, perms).unwrap();
}
#[cfg(unix)]
const PROXY_ENV_VARS: &[&str] = &[
"HTTPS_PROXY",
"HTTP_PROXY",
"https_proxy",
"http_proxy",
"NODE_EXTRA_CA_CERTS",
"NO_PROXY",
"no_proxy",
];
#[cfg(unix)]
fn run_sh(script: &str, path_dir: &std::path::Path, extra_env: &[(&str, &str)]) -> String {
let path_var = format!(
"{}:{}",
path_dir.display(),
std::env::var("PATH").unwrap_or_default()
);
let mut cmd = std::process::Command::new("sh");
cmd.arg("-c").arg(script).env("PATH", path_var);
for var in PROXY_ENV_VARS {
cmd.env_remove(var);
}
for (k, v) in extra_env {
cmd.env(k, v);
}
let out = cmd.output().expect("run sh");
assert!(
out.stderr.is_empty(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).into_owned()
}
#[test]
#[cfg(unix)]
fn a_marker_naming_a_dead_pid_goes_direct() {
let dir = tempdir();
let _lock = crate::config::OPENLATCH_DIR_ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
std::env::set_var("OPENLATCH_DIR", dir.path());
write_block(
"fake-a",
&["env"],
"http://127.0.0.1:7600",
Path::new("/tmp/ca.pem"),
"127.0.0.1:7600",
)
.expect("write");
let env_sh = env_sh_path(dir.path());
let marker = live_marker_path(dir.path());
let run = || run_sh(&format!(". '{}'; env", env_sh.display()), dir.path(), &[]);
std::fs::write(&marker, std::process::id().to_string()).unwrap();
let stdout = run();
assert!(stdout.contains("HTTPS_PROXY=http://127.0.0.1:"), "{stdout}");
let mut child = std::process::Command::new("true")
.spawn()
.expect("spawn true");
let dead_pid = child.id();
child.wait().expect("reap it");
std::fs::write(&marker, dead_pid.to_string()).unwrap();
let stdout = run();
assert!(!stdout.contains("HTTPS_PROXY="), "{stdout}");
std::env::remove_var("OPENLATCH_DIR");
}
#[test]
#[cfg(unix)]
fn the_wrapper_proxies_only_the_declared_command() {
let dir = tempdir();
let _lock = crate::config::OPENLATCH_DIR_ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
std::env::set_var("OPENLATCH_DIR", dir.path());
write_block(
"fake-a",
&["fakecli"],
"http://127.0.0.1:7600",
Path::new("/tmp/ca.pem"),
"127.0.0.1:7600",
)
.expect("write");
set_live(true).expect("set_live");
let bin_dir = tempdir();
write_executable(
&bin_dir.path().join("fakecli"),
"#!/bin/sh\necho \"HTTPS_PROXY=$HTTPS_PROXY\"\necho \"NODE_EXTRA_CA_CERTS=$NODE_EXTRA_CA_CERTS\"\n",
);
let env_sh = env_sh_path(dir.path());
let stdout = run_sh(
&format!(". '{}'; fakecli; printenv HTTPS_PROXY", env_sh.display()),
bin_dir.path(),
&[],
);
assert!(
stdout.contains("HTTPS_PROXY=http://127.0.0.1:7600"),
"{stdout}"
);
assert!(
stdout.contains("NODE_EXTRA_CA_CERTS=/tmp/ca.pem"),
"{stdout}"
);
assert_eq!(
stdout.lines().count(),
2,
"printenv in the same shell must print nothing: {stdout:?}"
);
set_live(false).unwrap();
std::env::remove_var("OPENLATCH_DIR");
}
#[test]
#[cfg(unix)]
fn the_wrapper_goes_direct_without_the_marker() {
let dir = tempdir();
let _lock = crate::config::OPENLATCH_DIR_ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
std::env::set_var("OPENLATCH_DIR", dir.path());
write_block(
"fake-a",
&["fakecli"],
"http://127.0.0.1:7600",
Path::new("/tmp/ca.pem"),
"127.0.0.1:7600",
)
.expect("write");
let bin_dir = tempdir();
write_executable(
&bin_dir.path().join("fakecli"),
"#!/bin/sh\necho \"HTTPS_PROXY=$HTTPS_PROXY\"\n",
);
let env_sh = env_sh_path(dir.path());
let stdout = run_sh(
&format!(". '{}'; fakecli", env_sh.display()),
bin_dir.path(),
&[],
);
assert_eq!(
stdout, "HTTPS_PROXY=\n",
"without the marker, no proxy variable is seen"
);
std::env::remove_var("OPENLATCH_DIR");
}
#[test]
#[cfg(unix)]
fn the_wrapper_goes_direct_once_its_block_is_removed() {
let dir = tempdir();
let _lock = crate::config::OPENLATCH_DIR_ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
std::env::set_var("OPENLATCH_DIR", dir.path());
write_block(
"fake-a",
&["fakecli"],
"http://127.0.0.1:7600",
Path::new("/tmp/ca.pem"),
"127.0.0.1:7600",
)
.expect("write a");
write_block(
"fake-b",
&["othercli"],
"http://127.0.0.1:7600",
Path::new("/tmp/ca.pem"),
"127.0.0.1:7600",
)
.expect("write b");
set_live(true).unwrap();
let bin_dir = tempdir();
write_executable(
&bin_dir.path().join("fakecli"),
"#!/bin/sh\necho \"HTTPS_PROXY=$HTTPS_PROXY\"\n",
);
write_executable(
&bin_dir.path().join("othercli"),
"#!/bin/sh\necho \"HTTPS_PROXY=$HTTPS_PROXY\"\n",
);
assert!(remove_block("fake-a").expect("remove a"));
let env_sh = env_sh_path(dir.path());
let stdout_a = run_sh(
&format!(". '{}'; fakecli", env_sh.display()),
bin_dir.path(),
&[],
);
assert_eq!(
stdout_a, "HTTPS_PROXY=\n",
"fake-a's command must go direct"
);
let stdout_b = run_sh(
&format!(". '{}'; othercli", env_sh.display()),
bin_dir.path(),
&[],
);
assert_eq!(
stdout_b, "HTTPS_PROXY=http://127.0.0.1:7600\n",
"fake-b is still proxied"
);
std::env::remove_var("OPENLATCH_DIR");
}
#[test]
#[cfg(unix)]
fn a_customer_no_proxy_is_merged_at_invocation() {
let dir = tempdir();
let _lock = crate::config::OPENLATCH_DIR_ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
std::env::set_var("OPENLATCH_DIR", dir.path());
write_block(
"fake-a",
&["fakecli"],
"http://127.0.0.1:7600",
Path::new("/tmp/ca.pem"),
"127.0.0.1:7600,127.0.0.1:7601",
)
.expect("write");
set_live(true).unwrap();
let bin_dir = tempdir();
write_executable(
&bin_dir.path().join("fakecli"),
"#!/bin/sh\necho \"NO_PROXY=$NO_PROXY\"\n",
);
let env_sh = env_sh_path(dir.path());
let stdout_customer = run_sh(
&format!(". '{}'; fakecli", env_sh.display()),
bin_dir.path(),
&[("NO_PROXY", "internal.corp")],
);
assert_eq!(
stdout_customer,
"NO_PROXY=internal.corp,127.0.0.1:7600,127.0.0.1:7601\n"
);
let stdout_unset = run_sh(
&format!(". '{}'; fakecli", env_sh.display()),
bin_dir.path(),
&[],
);
assert_eq!(stdout_unset, "NO_PROXY=127.0.0.1:7600,127.0.0.1:7601\n");
std::env::remove_var("OPENLATCH_DIR");
}
#[test]
#[cfg(unix)]
fn an_alias_of_the_same_name_is_left_alone() {
let dir = tempdir();
let _lock = crate::config::OPENLATCH_DIR_ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
std::env::set_var("OPENLATCH_DIR", dir.path());
write_block(
"fake-a",
&["fakecli"],
"http://127.0.0.1:7600",
Path::new("/tmp/ca.pem"),
"127.0.0.1:7600",
)
.expect("write");
let env_sh = env_sh_path(dir.path());
let script = format!(
"shopt -s expand_aliases; alias fakecli=true; . '{}'; type fakecli",
env_sh.display()
);
let out = std::process::Command::new("bash")
.arg("-c")
.arg(&script)
.output()
.expect("run bash");
assert!(
out.stderr.is_empty(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("fakecli is aliased to"), "{stdout}");
std::env::remove_var("OPENLATCH_DIR");
}
#[test]
#[cfg(windows)]
fn the_ps1_wrapper_runs_under_windows_powershell() {
let dir = tempdir();
let _lock = crate::config::OPENLATCH_DIR_ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
std::env::set_var("OPENLATCH_DIR", dir.path());
write_block(
"fake-a",
&["fakecli"],
"http://127.0.0.1:7600",
Path::new("C:\\OL\\ca.pem"),
"127.0.0.1:7600",
)
.expect("write");
set_live(true).expect("set_live");
let bin_dir = tempdir();
std::fs::write(
bin_dir.path().join("fakecli.cmd"),
"@echo off\r\necho %HTTPS_PROXY%\r\n",
)
.expect("write fakecli.cmd");
let prior = std::env::var("HTTPS_PROXY").ok();
let env_ps1 = env_ps1_path(dir.path());
let path_var = format!(
"{};{}",
bin_dir.path().display(),
std::env::var("PATH").unwrap_or_default()
);
let out = std::process::Command::new("powershell")
.args([
"-NoProfile",
"-Command",
&format!(
". '{}'; fakecli; Write-Output \"AFTER=$env:HTTPS_PROXY\"",
env_ps1.display()
),
])
.env("PATH", path_var)
.output()
.expect("run powershell");
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("http://127.0.0.1:7600"), "{stdout}");
let after_line = stdout
.lines()
.find(|l| l.starts_with("AFTER="))
.expect("AFTER line");
assert_eq!(
after_line.trim_start_matches("AFTER="),
prior.unwrap_or_default(),
"$env:HTTPS_PROXY must be restored after the call"
);
std::env::remove_var("OPENLATCH_DIR");
}
}