use serde_json::{Map, Value};
use url::Url;
use crate::npm::document::{PackageDocument, VersionEntry, prune_time};
use crate::policy::Ecosystem;
use crate::store::rows::ReferenceId;
const ABBREVIATED_FIELDS: &[&str] = &[
"name",
"version",
"dist",
"dependencies",
"optionalDependencies",
"devDependencies",
"bundleDependencies",
"peerDependencies",
"peerDependenciesMeta",
"acceptDependencies",
"directories",
"bin",
"engines",
"cpu",
"os",
"libc",
"funding",
"deprecated",
"hasInstallScript",
"_hasShrinkwrap",
];
pub const FULL_CONTENT_TYPE: &str = "application/json";
pub const ABBREVIATED_CONTENT_TYPE: &str = "application/vnd.npm.install-v1+json; charset=utf-8";
pub fn full(
document: &PackageDocument,
kept: &[&VersionEntry],
tags: &Map<String, Value>,
public_url: &Url,
) -> Vec<u8> {
let mut out = document.rest.clone();
out.insert("name".to_owned(), Value::from(document.name.clone()));
out.insert("dist-tags".to_owned(), Value::Object(tags.clone()));
out.insert(
"versions".to_owned(),
Value::Object(rewritten_versions(kept, public_url)),
);
out.insert(
"time".to_owned(),
Value::Object(prune_time(&document.time, |version| {
kept.iter().any(|entry| entry.version() == version)
})),
);
serialise(&Value::Object(out))
}
pub fn abbreviated(
document: &PackageDocument,
kept: &[&VersionEntry],
tags: &Map<String, Value>,
public_url: &Url,
) -> Vec<u8> {
let mut versions = Map::new();
for entry in kept {
let mut record = rewritten_record(entry, public_url);
if let Value::Object(object) = &mut record {
object.retain(|key, _| ABBREVIATED_FIELDS.contains(&key.as_str()));
}
versions.insert(entry.version().to_owned(), record);
}
let mut out = Map::new();
out.insert("name".to_owned(), Value::from(document.name.clone()));
out.insert("dist-tags".to_owned(), Value::Object(tags.clone()));
if let Some(modified) = document.time.get("modified") {
out.insert("modified".to_owned(), modified.clone());
}
out.insert("versions".to_owned(), Value::Object(versions));
serialise(&Value::Object(out))
}
pub fn single_version(entry: &VersionEntry, public_url: &Url) -> Vec<u8> {
serialise(&rewritten_record(entry, public_url))
}
fn rewritten_versions(kept: &[&VersionEntry], public_url: &Url) -> Map<String, Value> {
kept.iter()
.map(|entry| {
(
entry.version().to_owned(),
rewritten_record(entry, public_url),
)
})
.collect()
}
fn rewritten_record(entry: &VersionEntry, public_url: &Url) -> Value {
let mut record = entry.record.clone();
if let Some(dist) = record.get_mut("dist").and_then(Value::as_object_mut) {
dist.insert(
"tarball".to_owned(),
Value::from(artifact_url(
public_url,
entry.reference.ecosystem,
&entry.id,
&entry.reference.filename,
)),
);
}
record
}
pub fn artifact_url(
public_url: &Url,
ecosystem: Ecosystem,
id: &ReferenceId,
filename: &str,
) -> String {
let mut url = public_url.clone();
if let Ok(mut path) = url.path_segments_mut() {
path.pop_if_empty();
path.push(ecosystem.as_tag());
path.push("artifacts");
path.push(&id.to_hex());
path.push(filename);
}
url.to_string()
}
fn serialise(value: &Value) -> Vec<u8> {
serde_json::to_vec(value).unwrap_or_else(|_| b"{}".to_vec())
}
#[cfg(test)]
mod tests {
use super::*;
const DOCUMENT: &str = r##"{
"name": "widget",
"readme": "# widget",
"dist-tags": {"latest": "1.0.0"},
"versions": {
"1.0.0": {
"name": "widget", "version": "1.0.0",
"dependencies": {"left-pad": "^1.3.0"},
"peerDependencies": {"react": ">=16.8.0 <19.0.0"},
"engines": {"node": ">=18"},
"_npmUser": {"name": "someone"},
"dist": {
"tarball": "https://npm.invalid/widget/-/widget-1.0.0.tgz",
"shasum": "0123456789abcdef0123456789abcdef01234567"
}
},
"0.9.0": {
"name": "widget", "version": "0.9.0",
"dist": {"tarball": "https://npm.invalid/widget/-/widget-0.9.0.tgz"}
}
},
"time": {
"created": "2026-01-01T00:00:00.000Z",
"modified": "2026-01-03T00:00:00.000Z",
"0.9.0": "2026-01-01T00:00:00.000Z",
"1.0.0": "2026-01-02T00:00:00.000Z"
}
}"##;
fn public_url() -> Url {
Url::parse("https://packages.example.org").expect("a public URL")
}
fn rendered_full(keep: &[&str]) -> Value {
let document = PackageDocument::parse(DOCUMENT.as_bytes()).expect("a valid document");
let entries = document.entries(100).expect("the entries");
let kept: Vec<&VersionEntry> = entries
.iter()
.filter(|entry| keep.contains(&entry.version()))
.collect();
let tags = Map::new();
serde_json::from_slice(&full(&document, &kept, &tags, &public_url()))
.expect("the rendered document parses")
}
#[test]
fn an_excluded_version_leaves_both_the_version_map_and_the_time_map() {
let rendered = rendered_full(&["1.0.0"]);
assert!(rendered["versions"].get("0.9.0").is_none());
assert!(rendered["time"].get("0.9.0").is_none());
assert!(rendered["time"].get("1.0.0").is_some());
assert!(
rendered["time"].get("created").is_some() && rendered["time"].get("modified").is_some(),
"created and modified are not versions and are not pruned"
);
assert_eq!(
rendered["readme"],
Value::from("# widget"),
"a field filtering does not touch is carried through"
);
}
#[test]
fn only_the_tarball_is_edited() {
let rendered = rendered_full(&["1.0.0"]);
let version = &rendered["versions"]["1.0.0"];
assert_eq!(version["dependencies"]["left-pad"], Value::from("^1.3.0"));
assert_eq!(
version["peerDependencies"]["react"],
Value::from(">=16.8.0 <19.0.0")
);
assert_eq!(version["engines"]["node"], Value::from(">=18"));
assert_eq!(
version["dist"]["shasum"],
Value::from("0123456789abcdef0123456789abcdef01234567"),
"the upstream integrity field is preserved beside the rewritten URL"
);
let tarball = version["dist"]["tarball"].as_str().expect("a tarball URL");
assert!(
tarball.starts_with("https://packages.example.org/npm/artifacts/"),
"the tarball points here, not upstream: {tarball}"
);
assert!(tarball.ends_with("/widget-1.0.0.tgz"));
}
#[test]
fn the_abbreviated_form_drops_only_fields_outside_the_documented_set() {
let document = PackageDocument::parse(DOCUMENT.as_bytes()).expect("a valid document");
let entries = document.entries(100).expect("the entries");
let kept: Vec<&VersionEntry> = entries.iter().collect();
let rendered: Value =
serde_json::from_slice(&abbreviated(&document, &kept, &Map::new(), &public_url()))
.expect("the abbreviated document parses");
let version = &rendered["versions"]["1.0.0"];
assert_eq!(version["dependencies"]["left-pad"], Value::from("^1.3.0"));
assert!(version.get("_npmUser").is_none());
assert!(rendered.get("readme").is_none());
assert_eq!(
rendered["modified"],
Value::from("2026-01-03T00:00:00.000Z")
);
}
#[test]
fn an_untrusted_filename_cannot_escape_the_artifact_path() {
let id = ReferenceId::parse_hex(&"ab".repeat(32)).expect("a reference id");
for filename in [
"../../etc/passwd",
"a/b",
"x?y=1",
"x#y",
"//evil.invalid/x",
] {
let url = artifact_url(&public_url(), Ecosystem::Npm, &id, filename);
let parsed = Url::parse(&url).expect("the built URL parses");
assert_eq!(parsed.host_str(), Some("packages.example.org"));
assert!(parsed.query().is_none() && parsed.fragment().is_none());
assert_eq!(
parsed.path_segments().map(Iterator::count),
Some(4),
"the ecosystem root, `artifacts`, the id, and exactly one filename \
segment: {url}"
);
}
}
}