diff --git a/src/bin/dosh-client.rs b/src/bin/dosh-client.rs index d7cb274..40dc845 100644 --- a/src/bin/dosh-client.rs +++ b/src/bin/dosh-client.rs @@ -4330,20 +4330,34 @@ fn latest_release_tag(repo: &str) -> Result> { if !web.starts_with("http://") && !web.starts_with("https://") { return Ok(None); } - let output = Command::new("curl") - .arg("-fsSL") - .arg("-o") - .arg("/dev/null") - .arg("-w") - .arg("%{url_effective}") - .arg(format!("{web}/releases/latest")) - .stdin(Stdio::null()) - .output() - .with_context(|| format!("resolve latest release for {web}"))?; + let latest_url = format!("{web}/releases/latest"); + let output = if cfg!(windows) { + Command::new("powershell.exe") + .arg("-NoProfile") + .arg("-ExecutionPolicy") + .arg("Bypass") + .arg("-Command") + .arg(windows_effective_url_script(&latest_url)) + .stdin(Stdio::null()) + .output() + .with_context(|| format!("resolve latest release for {web}"))? + } else { + Command::new("curl") + .arg("-fsSL") + .arg("-o") + .arg("/dev/null") + .arg("-w") + .arg("%{url_effective}") + .arg(latest_url) + .stdin(Stdio::null()) + .output() + .with_context(|| format!("resolve latest release for {web}"))? + }; if !output.status.success() { return Ok(None); } let effective = String::from_utf8_lossy(&output.stdout); + let effective = effective.trim(); Ok(release_tag_from_effective_url(web, &effective)) } @@ -4359,14 +4373,28 @@ fn release_tag_download_url(repo: &str, tag: &str, artifact: &str) -> Option Result { - let status = Command::new("curl") - .arg("-fsIL") - .arg(url) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .with_context(|| format!("check {url}"))?; + let status = if cfg!(windows) { + Command::new("powershell.exe") + .arg("-NoProfile") + .arg("-ExecutionPolicy") + .arg("Bypass") + .arg("-Command") + .arg(windows_url_reachable_script(url)) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .with_context(|| format!("check {url}"))? + } else { + Command::new("curl") + .arg("-fsIL") + .arg(url) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .with_context(|| format!("check {url}"))? + }; Ok(status.success()) } @@ -4570,6 +4598,36 @@ fn windows_update_script( script } +fn windows_effective_url_script(url: &str) -> String { + let url = powershell_string(url); + format!( + "$ErrorActionPreference='Stop';\ + $ProgressPreference='SilentlyContinue';\ + $response=Invoke-WebRequest -UseBasicParsing -Uri {url} -MaximumRedirection 5;\ + if ($response.BaseResponse.ResponseUri) {{ \ + $response.BaseResponse.ResponseUri.AbsoluteUri \ + }} elseif ($response.BaseResponse.RequestMessage -and $response.BaseResponse.RequestMessage.RequestUri) {{ \ + $response.BaseResponse.RequestMessage.RequestUri.AbsoluteUri \ + }} else {{ \ + {url} \ + }}" + ) +} + +fn windows_url_reachable_script(url: &str) -> String { + format!( + "$ErrorActionPreference='Stop';\ + $ProgressPreference='SilentlyContinue';\ + try {{ \ + Invoke-WebRequest -UseBasicParsing -Uri {} -Method Head -MaximumRedirection 5 | Out-Null;\ + exit 0 \ + }} catch {{ \ + exit 1 \ + }}", + powershell_string(url) + ) +} + fn windows_deferred_update_script(parent_pid: u32, installer_script: &str) -> String { format!( "$ErrorActionPreference='Stop';\ @@ -10190,8 +10248,8 @@ mod tests { trace_report_warnings, unix_update_script, update_binary_version_for_installer, update_installer_url, update_version_status, upsert_managed_block, valid_forward_host, vscode_safe_alias, wake_repaint_retry_deadline, windows_command_word, - windows_deferred_update_script, windows_mode_from_readonly, windows_readonly_from_mode, - windows_update_script, + windows_deferred_update_script, windows_effective_url_script, windows_mode_from_readonly, + windows_readonly_from_mode, windows_update_script, windows_url_reachable_script, }; use dosh::config::{ClientConfig, CommandExtension, HostConfig}; use dosh::native::EnvVar; @@ -12464,6 +12522,25 @@ mod tests { ); } + #[test] + fn windows_update_probes_are_powershell_native() { + let latest = + windows_effective_url_script("https://git.palav.dev/Palav/dosh/releases/latest"); + assert!(latest.contains("Invoke-WebRequest -UseBasicParsing")); + assert!(latest.contains("-MaximumRedirection 5")); + assert!(latest.contains("$response.BaseResponse.ResponseUri.AbsoluteUri")); + assert!(!latest.contains("curl")); + + let reachable = windows_url_reachable_script( + "https://git.palav.dev/Palav/dosh/releases/latest/download/dosh-windows-x86_64.zip", + ); + assert!(reachable.contains("Invoke-WebRequest -UseBasicParsing")); + assert!(reachable.contains("-Method Head")); + assert!(reachable.contains("exit 0")); + assert!(reachable.contains("exit 1")); + assert!(!reachable.contains("curl")); + } + #[test] fn update_uses_local_tag_when_release_latest_is_older() { assert_eq!(