diff --git a/Cargo.lock b/Cargo.lock index d4606fa..3b92287 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,12 +32,30 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -47,6 +65,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -178,6 +216,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -329,6 +377,7 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" name = "just" version = "0.2.0" dependencies = [ + "sha2", "zed_extension_api", ] @@ -338,6 +387,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + [[package]] name = "litemap" version = "0.8.1" @@ -480,6 +535,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "simd-adler32" version = "0.3.8" @@ -551,6 +617,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.22" @@ -581,6 +653,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasm-encoder" version = "0.227.1" diff --git a/Cargo.toml b/Cargo.toml index 0f3e3d8..5b8b911 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" crate-type = ["cdylib"] [dependencies] +sha2 = "0.10" zed_extension_api = "0.7.0" [profile.release] diff --git a/src/lib.rs b/src/lib.rs index 1f7decb..ffd6088 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ use std::fs; +use sha2::{Digest, Sha256}; use zed_extension_api::{ + http_client::{HttpMethod, HttpRequest, RedirectPolicy}, Architecture, Command, DownloadedFileType, Extension, GithubReleaseOptions, LanguageServerId, Os, Result, Worktree, current_platform, download_file, latest_github_release, make_file_executable, register_extension, @@ -75,6 +77,52 @@ impl JustExtension { // Check if already downloaded if !fs::metadata(&binary_path).is_ok_and(|stat| stat.is_file()) { + // Fetch SHA256SUMS from the release + let checksums_asset = release + .assets + .iter() + .find(|a| a.name == "SHA256SUMS") + .ok_or("SHA256SUMS not found in release assets")?; + + let checksums_response = HttpRequest { + url: checksums_asset.download_url.clone(), + method: HttpMethod::Get, + headers: vec![], + body: None, + redirect_policy: RedirectPolicy::FollowAll, + } + .fetch()?; + + let checksums_text = String::from_utf8(checksums_response.body) + .map_err(|e| format!("Invalid SHA256SUMS encoding: {e}"))?; + + let expected_hash = checksums_text + .lines() + .find(|line| line.ends_with(asset_name.as_str())) + .and_then(|line| line.split_whitespace().next()) + .ok_or_else(|| { + format!("Checksum for {asset_name} not found in SHA256SUMS") + })?; + + // Download archive and verify its SHA256 checksum + let archive_response = HttpRequest { + url: asset.download_url.clone(), + method: HttpMethod::Get, + headers: vec![], + body: None, + redirect_policy: RedirectPolicy::FollowAll, + } + .fetch()?; + + let computed_hash = format!("{:x}", Sha256::digest(&archive_response.body)); + + if computed_hash != expected_hash { + return Err(format!( + "Checksum verification failed for {asset_name}: expected {expected_hash}, got {computed_hash}" + )); + } + + // Checksum verified — download and extract download_file( &asset.download_url, &version_dir, @@ -83,7 +131,7 @@ impl JustExtension { _ => DownloadedFileType::GzipTar, }, ) - .map_err(|e| format!("Failed to download just-lsp: {}", e))?; + .map_err(|e| format!("Failed to download just-lsp: {e}"))?; make_file_executable(&binary_path)?; }