xtask_lib/tasks/util/
http.rs1use std::path::Path;
4
5use anyhow::{Context, Result, bail};
6use sha2::{Digest, Sha256};
7
8pub fn download(url: &str, dest: &Path) -> Result<()> {
17 if let Some(parent) = dest.parent() {
18 std::fs::create_dir_all(parent)
19 .with_context(|| format!("failed to create parent directory for {}", dest.display()))?;
20 }
21
22 let response = reqwest::blocking::get(url)
23 .with_context(|| format!("HTTP request failed for {url}"))?
24 .error_for_status()
25 .with_context(|| format!("server returned error status for {url}"))?;
26
27 let bytes = response
28 .bytes()
29 .with_context(|| format!("failed to read response body from {url}"))?;
30
31 std::fs::write(dest, &bytes)
32 .with_context(|| format!("failed to write downloaded file to {}", dest.display()))
33}
34
35pub fn verify_sha256(file: &Path, expected: &str) -> Result<()> {
43 let bytes = std::fs::read(file).with_context(|| {
44 format!(
45 "failed to read {} for checksum verification",
46 file.display()
47 )
48 })?;
49
50 let mut hasher = Sha256::new();
51 hasher.update(&bytes);
52 let actual: String = hasher
53 .finalize()
54 .iter()
55 .map(|b| format!("{b:02x}"))
56 .collect();
57
58 if actual != expected {
59 bail!(
60 "SHA-256 checksum mismatch for {}\n expected: {expected}\n got: {actual}",
61 file.display()
62 );
63 }
64
65 println!("Checksum verified.");
66
67 Ok(())
68}
69
70pub fn download_verified(url: &str, dest: &Path, expected_sha256: &str) -> Result<()> {
77 let tmp = dest.with_extension("tmp");
78 download(url, &tmp)?;
79
80 if let Err(e) = verify_sha256(&tmp, expected_sha256) {
81 std::fs::remove_file(&tmp).ok();
82 return Err(e);
83 }
84
85 std::fs::rename(&tmp, dest)
86 .with_context(|| format!("failed to move download to {}", dest.display()))
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use std::fs;
93
94 #[test]
95 fn verify_sha256_accepts_correct_hash() {
96 let tmp = tempfile::tempdir().unwrap();
97 let file = tmp.path().join("data.bin");
98 fs::write(&file, b"hello world").unwrap();
99
100 let mut hasher = Sha256::new();
101 hasher.update(b"hello world");
102 let real_hash: String = hasher
103 .finalize()
104 .iter()
105 .map(|b| format!("{b:02x}"))
106 .collect();
107
108 assert!(verify_sha256(&file, &real_hash).is_ok());
109 }
110
111 #[test]
112 fn verify_sha256_rejects_wrong_hash() {
113 let tmp = tempfile::tempdir().unwrap();
114 let file = tmp.path().join("data.bin");
115 fs::write(&file, b"hello world").unwrap();
116
117 let result = verify_sha256(
118 &file,
119 "0000000000000000000000000000000000000000000000000000000000000000",
120 );
121 assert!(result.is_err());
122 let msg = format!("{}", result.unwrap_err());
123 assert!(msg.contains("checksum mismatch"));
124 }
125}