mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
fix(tools): skip an unreachable revocation check when curl downloads on Windows (#833)
Schannel curl fails every download with CRYPT_E_REVOCATION_OFFLINE when the CRL/OCSP servers cannot be reached (corporate proxies, firewalls, some regions), although the certificate is valid, so jdtls and other tools could not be installed. On Windows, pass --ssl-revoke-best-effort when `curl --version` reports 7.70+ with Schannel as the active TLS backend: the check is skipped only when the revocation server is offline, and actually revoked certificates are still rejected. Older curl, which rejects the option, is left as is. Fixes #801. Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
This commit is contained in:
@@ -144,18 +144,46 @@ fn strip_ansi(s: &str) -> String {
|
||||
out
|
||||
}
|
||||
|
||||
/// Whether `curl` checks certificate revocation with Schannel and takes
|
||||
/// `--ssl-revoke-best-effort` (7.70+). Schannel fails the whole download with
|
||||
/// CRYPT_E_REVOCATION_OFFLINE when the CRL/OCSP servers are unreachable (corporate
|
||||
/// proxies, firewalls); best effort skips only that check, and still rejects
|
||||
/// certificates that are actually revoked.
|
||||
fn revoke_best_effort(curl: &Path) -> bool {
|
||||
static CHECKED: parking_lot::Mutex<Vec<(std::path::PathBuf, bool)>> = parking_lot::Mutex::new(Vec::new());
|
||||
if let Some((_, ok)) = CHECKED.lock().iter().find(|(path, _)| path == curl) {
|
||||
return *ok;
|
||||
}
|
||||
let mut cmd = Command::new(curl);
|
||||
cmd.arg("--version");
|
||||
let ok = run_quiet(cmd, Duration::from_secs(5))
|
||||
.is_some_and(|(success, out)| success && curl_takes_revoke_best_effort(&out));
|
||||
CHECKED.lock().push((curl.to_path_buf(), ok));
|
||||
ok
|
||||
}
|
||||
|
||||
/// Reads `curl --version`: `curl 8.9.1 (Windows) libcurl/8.9.1 Schannel …`.
|
||||
fn curl_takes_revoke_best_effort(version: &str) -> bool {
|
||||
let Some(first) = version.lines().find(|l| l.starts_with("curl ")) else { return false };
|
||||
let mut parts = first.split_whitespace().nth(1).unwrap_or("").split('.').map(|n| n.parse::<u32>().ok());
|
||||
let (Some(Some(major)), Some(Some(minor))) = (parts.next(), parts.next()) else { return false };
|
||||
(major, minor) >= (7, 70) && first.split_whitespace().any(|w| w.eq_ignore_ascii_case("schannel"))
|
||||
}
|
||||
|
||||
/// HTTP via the system `curl` (shipped with macOS, Windows 10+ and every
|
||||
/// mainstream Linux), so the IDE carries no TLS stack of its own.
|
||||
pub struct Http {
|
||||
curl: std::path::PathBuf,
|
||||
https_only: bool,
|
||||
revoke_best_effort: bool,
|
||||
}
|
||||
|
||||
impl Http {
|
||||
pub fn new(search_path: &std::ffi::OsStr) -> IdeResult<Self> {
|
||||
let curl = which::which_in("curl", Some(search_path), ".")
|
||||
.map_err(|_| IdeError::unavailable("`curl` is required to download tools"))?;
|
||||
Ok(Self { curl, https_only: false })
|
||||
let revoke_best_effort = cfg!(windows) && revoke_best_effort(&curl);
|
||||
Ok(Self { curl, https_only: false, revoke_best_effort })
|
||||
}
|
||||
|
||||
/// Refuses every protocol but HTTPS, redirects included.
|
||||
@@ -173,6 +201,9 @@ impl Http {
|
||||
if self.https_only {
|
||||
cmd.args(["--proto", "=https", "--proto-redir", "=https"]);
|
||||
}
|
||||
if self.revoke_best_effort {
|
||||
cmd.arg("--ssl-revoke-best-effort");
|
||||
}
|
||||
if url.starts_with("https://api.github.com/")
|
||||
&& let Ok(token) = std::env::var("GITHUB_TOKEN").or_else(|_| std::env::var("GH_TOKEN"))
|
||||
&& !token.is_empty()
|
||||
@@ -438,13 +469,28 @@ mod tests {
|
||||
assert!(lines.contains(&"one".to_string()) && lines.contains(&"two".to_string()), "{lines:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revoke_best_effort_needs_schannel_curl_7_70() {
|
||||
let schannel = "curl 8.9.1 (Windows) libcurl/8.9.1 Schannel zlib/1.3 WinIDN\nRelease-Date: 2024-07-31\n";
|
||||
assert!(curl_takes_revoke_best_effort(schannel));
|
||||
assert!(curl_takes_revoke_best_effort("curl 7.70.0 (x86_64-pc-win32) libcurl/7.70.0 Schannel\n"));
|
||||
// Windows 10 1803 shipped 7.55, which rejects the unknown option.
|
||||
assert!(!curl_takes_revoke_best_effort("curl 7.55.1 (Windows) libcurl/7.55.1 WinSSL\n"));
|
||||
// OpenSSL never checks revocation this way; Schannel in parentheses is not the active backend.
|
||||
assert!(!curl_takes_revoke_best_effort(
|
||||
"curl 8.10.1 (x86_64-w64-mingw32) libcurl/8.10.1 OpenSSL/3.2.3 (Schannel)\n"
|
||||
));
|
||||
assert!(!curl_takes_revoke_best_effort("WARNING: this libcurl is Debug-enabled\n"));
|
||||
assert!(curl_takes_revoke_best_effort(&format!("WARNING: this libcurl is Debug-enabled\n\n{schannel}")));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn absent_build_is_empty_but_http_failures_are_not() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let curl = tmp.path().join("curl");
|
||||
let http = Http { curl: curl.clone(), https_only: false };
|
||||
let http = Http { curl: curl.clone(), https_only: false, revoke_best_effort: false };
|
||||
for (code, exit, expected) in [(404, 22, Some("[]")), (200, 0, Some("payload")), (403, 22, None), (0, 7, None)]
|
||||
{
|
||||
std::fs::write(&curl, format!("#!/bin/sh\nprintf 'payload\\n{code:03}'\nexit {exit}\n")).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user