http_client: Add integrity checks for GitHub binaries using digest checks (#43737)

Generalizes the digest verification logic from `rust-analyzer` and
`clangd` into a reusable helper function in
`http_client::github_download`.

This removes ~100 lines of duplicated code across the two language
adapters and makes it easier for other language servers to adopt digest
verification in the future.

Closes #35201

Release Notes:

- N/A
This commit is contained in:
Tom Planche
2025-12-01 09:00:27 +01:00
committed by GitHub
parent db86febc0c
commit 05764e8af7
3 changed files with 92 additions and 86 deletions
+60 -1
View File
@@ -1,4 +1,4 @@
use std::{path::Path, pin::Pin, task::Poll};
use std::{future::Future, path::Path, pin::Pin, task::Poll};
use anyhow::{Context, Result};
use async_compression::futures::bufread::GzipDecoder;
@@ -85,6 +85,65 @@ pub async fn download_server_binary(
Ok(())
}
pub async fn fetch_github_binary_with_digest_check<ValidityCheck, ValidityCheckFuture>(
binary_path: &Path,
metadata_path: &Path,
expected_digest: Option<String>,
url: &str,
asset_kind: AssetKind,
download_destination: &Path,
http_client: &dyn HttpClient,
validity_check: ValidityCheck,
) -> Result<()>
where
ValidityCheck: FnOnce() -> ValidityCheckFuture,
ValidityCheckFuture: Future<Output = Result<()>>,
{
let metadata = GithubBinaryMetadata::read_from_file(metadata_path)
.await
.ok();
if let Some(metadata) = metadata {
let validity_check_result = validity_check().await;
if let (Some(actual_digest), Some(expected_digest_ref)) =
(&metadata.digest, &expected_digest)
{
if actual_digest == expected_digest_ref {
if validity_check_result.is_ok() {
return Ok(());
}
} else {
log::info!(
"SHA-256 mismatch for {binary_path:?} asset, downloading new asset. Expected: {expected_digest_ref}, Got: {actual_digest}"
);
}
} else if validity_check_result.is_ok() {
return Ok(());
}
}
download_server_binary(
http_client,
url,
expected_digest.as_deref(),
download_destination,
asset_kind,
)
.await?;
GithubBinaryMetadata::write_to_file(
&GithubBinaryMetadata {
metadata_version: 1,
digest: expected_digest,
},
metadata_path,
)
.await?;
Ok(())
}
async fn stream_response_archive(
response: impl AsyncRead + Unpin,
url: &str,