use std::io::Read;
use color_eyre::eyre::eyre;
use super::{
MANIFEST_SIZE_CAP,
Manifest,
ManifestSource,
Sha256Hex,
invalid,
unavailable_manifest,
};
use crate::{
error::{BootstrapErrorKind, BootstrapResult},
extensions::{
LOG_TARGET,
extension_error,
http::{http_get, redact_url},
},
};
pub(in crate::extensions) fn load(source: &ManifestSource) -> BootstrapResult<Manifest> {
let (bytes, pinned) = match source {
ManifestSource::Path { path, sha256 } => (read_path(path)?, sha256.as_ref()),
ManifestSource::Url { url, sha256 } => (fetch_url(url)?, Some(sha256)),
};
if let Some(expected) = pinned {
verify_digest(&bytes, expected, source)?;
}
let manifest = Manifest::parse(&bytes)?;
log_loaded(source, bytes.len(), pinned.is_some(), &manifest);
Ok(manifest)
}
fn log_loaded(source: &ManifestSource, bytes: usize, pinned: bool, manifest: &Manifest) {
tracing::info!(
target: LOG_TARGET,
location = %source.location(),
bytes,
pinned,
release = %manifest.release,
extensions = manifest.extensions.len(),
"loaded extension manifest"
);
}
fn read_path(path: &camino::Utf8Path) -> BootstrapResult<Vec<u8>> {
let file = std::fs::File::open(path)
.map_err(|err| unavailable_manifest(eyre!("cannot open manifest at {path}: {err}")))?;
let mut bytes = Vec::new();
file.take(MANIFEST_SIZE_CAP + 1)
.read_to_end(&mut bytes)
.map_err(|err| unavailable_manifest(eyre!("cannot read manifest at {path}: {err}")))?;
check_size(bytes, path.as_str())
}
fn fetch_url(url: &str) -> BootstrapResult<Vec<u8>> {
let mut bytes = Vec::new();
http_get(url, MANIFEST_SIZE_CAP, &mut bytes).map_err(|err| {
unavailable_manifest(eyre!(
"cannot fetch manifest from {}: {err}",
redact_url(url)
))
})?;
check_size(bytes, &redact_url(url))
}
fn check_size(bytes: Vec<u8>, location: &str) -> BootstrapResult<Vec<u8>> {
if bytes.len() as u64 > MANIFEST_SIZE_CAP {
return Err(invalid(eyre!(
"manifest at {location} exceeds {MANIFEST_SIZE_CAP} bytes"
)));
}
Ok(bytes)
}
fn verify_digest(
bytes: &[u8],
expected: &Sha256Hex,
source: &ManifestSource,
) -> BootstrapResult<()> {
let actual = Sha256Hex::of_bytes(bytes);
if &actual == expected {
return Ok(());
}
Err(extension_error(
BootstrapErrorKind::ExtensionManifestDigestMismatch,
eyre!(
"manifest at {} hashes to {actual} but PG_EXTENSIONS_MANIFEST_SHA256 pins {expected}",
source.location()
),
))
}