server/index-rows/src/npm.rs
//! An npm tarball's own `package.json` and its SSRI integrity (moved here from server-lib's
//! `promote::npm`, lane HOL-PERF-2): the scan worker reads them at intake and answers them as
//! an [`crate::IndexRow::Npm`]; the packument is rendered from the row.
use std::io::Read as _;
use base64::Engine as _;
use serde_json::Value;
/// The most of a `package.json` the door reads.
pub const MAX_MANIFEST_BYTES: u64 = 1 << 20;
/// The most a tarball may unpack to while the door looks for its `package.json`.
pub const MAX_UNPACKED_BYTES: u64 = 512 << 20;
/// The most tar members the door walks looking for it.
pub const MAX_MEMBERS: usize = 100_000;
/// SSRI `sha512-<base64>` of `bytes`.
pub fn integrity_of(bytes: &[u8]) -> String {
use sha2::Digest as _;
format!(
"sha512-{}",
base64::engine::general_purpose::STANDARD.encode(sha2::Sha512::digest(bytes))
)
}
/// **The tarball's own `package.json`**: the top-level directory's (`package/` as `npm pack`
/// lays it), gunzipped and untarred with bounds on what is read.
pub fn read_manifest(tgz: &[u8]) -> Result<Value, String> {
crate::counted();
let gz = flate2::read::GzDecoder::new(tgz).take(MAX_UNPACKED_BYTES);
let mut archive = tar::Archive::new(gz);
let entries = archive
.entries()
.map_err(|e| format!("the tarball is not a gzipped tar: {e}"))?;
for (n, entry) in entries.enumerate() {
if n >= MAX_MEMBERS {
break;
}
let entry = entry.map_err(|e| format!("the tarball is not a gzipped tar: {e}"))?;
let path = entry
.path()
.map_err(|e| format!("a tar member's path: {e}"))?
.into_owned();
let parts: Vec<_> = path.components().collect();
if parts.len() == 2 && parts[1].as_os_str() == "package.json" {
if entry.header().size().unwrap_or(u64::MAX) > MAX_MANIFEST_BYTES {
return Err("the tarball's package.json is larger than 1 MiB".into());
}
let mut text = String::new();
entry
.take(MAX_MANIFEST_BYTES)
.read_to_string(&mut text)
.map_err(|e| format!("the tarball's package.json: {e}"))?;
let v: Value = serde_json::from_str(&text)
.map_err(|e| format!("the tarball's package.json is not JSON: {e}"))?;
return if v.is_object() {
Ok(v)
} else {
Err("the tarball's package.json is not an object".into())
};
}
}
Err("the tarball carries no top-level package.json".into())
}