Skip to main content

opys_dev/
pin.rs

1//! Pinning a file nobody published a hash for.
2//!
3//! Most providers say what a file's hash is. For a plain URL there is nobody
4//! to ask, so the file is downloaded once, at build time, and hashed. The
5//! manifest then carries that hash, and every install is held to the bytes
6//! that were seen here — which is the whole of what the old install-time
7//! `discovery` could not offer.
8
9use sha2::{Digest, Sha256};
10
11use crate::http::{get_bytes, HttpError};
12
13/// What a download turned out to be.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct Pinned {
16    pub size: u64,
17    /// sha256, lower-case hex.
18    pub sha256: String,
19}
20
21#[derive(Debug, thiserror::Error)]
22pub enum PinError {
23    #[error(transparent)]
24    Transport(#[from] HttpError),
25    #[error("{url} returned HTTP {status}")]
26    Status { url: String, status: u16 },
27}
28
29/// The sha256 of `bytes`, lower-case hex.
30pub fn sha256_hex(bytes: &[u8]) -> String {
31    hex::encode(Sha256::digest(bytes))
32}
33
34/// Download `url` and report its size and sha256.
35pub fn pin_url(url: &str, headers: &[(&str, &str)]) -> Result<Pinned, PinError> {
36    let response = get_bytes(url, headers)?;
37    if !response.ok() {
38        return Err(PinError::Status {
39            url: url.to_owned(),
40            status: response.status,
41        });
42    }
43    Ok(Pinned {
44        size: response.body.len() as u64,
45        sha256: sha256_hex(&response.body),
46    })
47}