server/index-rows/src/crates.rs
//! A `.crate`'s own `Cargo.toml` as the sparse index states it, and the dependency versions it
//! pins (lane HOL-CARGO-PUBLISH-DOOR). The scan worker reads them at intake and answers them as
//! an [`crate::IndexRow::Crate`]: the cargo door renders its index from the row, and the
//! vulnerability scan matches the pinned dependencies, so the server never opens a crate.
//!
//! `cargo package` writes `<name>-<version>/Cargo.toml` normalised: every dependency with its
//! requirement, kind and target, a renamed one with `package`, a crates.io one with
//! `registry-index`, the binary targets as `[[bin]]`. A binary crate also carries the
//! `Cargo.lock` `cargo install --locked` builds with.
use std::io::Read as _;
use serde_json::{json, Value};
/// The most of a `Cargo.toml` or `Cargo.lock` read out of a crate.
pub const MAX_MANIFEST_BYTES: u64 = 1 << 20;
/// The most a crate may unpack to while its manifest is looked for.
pub const MAX_UNPACKED_BYTES: u64 = 512 << 20;
/// The most tar members walked looking for it.
pub const MAX_MEMBERS: usize = 100_000;
/// What a crate says about itself.
#[derive(Debug, Clone, PartialEq)]
pub struct CrateRead {
pub name: String,
pub version: String,
/// The sparse index line without `cksum` and `yanked` (the sealed bytes and the door's
/// yank record add them).
pub line: Value,
/// The dependency versions the crate pins: `(name, version, where)`.
pub pinned: Vec<crate::Pinned>,
}
/// A requirement that admits one version only (`=1.6.0`), that version.
/// The crates.io index, in the spellings cargo writes (SEC-REVIEW 2026-11-01 P11).
const CRATES_IO_INDEXES: &[&str] = &[
"https://github.com/rust-lang/crates.io-index",
"sparse+https://index.crates.io",
];
pub fn exact_version(req: &str) -> Option<&str> {
let v = req.trim().strip_prefix('=')?.trim();
let core = v.split(['-', '+']).next().unwrap_or(v);
let parts: Vec<&str> = core.split('.').collect();
(parts.len() == 3
&& parts
.iter()
.all(|p| !p.is_empty() && p.chars().all(|c| c.is_ascii_digit()))
&& !v.contains([',', ' ', '*', '<', '>', '~', '^']))
.then_some(v)
}
/// **Read a `.crate`**: its `<name>-<version>/Cargo.toml` (and a binary crate's `Cargo.lock`),
/// gunzipped and untarred with bounds on what is read.
pub fn read(bytes: &[u8]) -> Result<CrateRead, String> {
crate::counted();
let gz = flate2::read::GzDecoder::new(bytes).take(MAX_UNPACKED_BYTES);
let mut archive = tar::Archive::new(gz);
let entries = archive
.entries()
.map_err(|e| format!("the crate is not a gzipped tar: {e}"))?;
let mut top: Option<String> = None;
let mut manifest: Option<String> = None;
let mut lock: Option<String> = None;
let mut has_main = false;
for (n, entry) in entries.enumerate() {
if n >= MAX_MEMBERS {
break;
}
let entry = entry.map_err(|e| format!("the crate is not a gzipped tar: {e}"))?;
let path = entry
.path()
.map_err(|e| format!("a tar member's path: {e}"))?
.to_string_lossy()
.replace('\\', "/");
let Some((dir, rest)) = path.split_once('/') else {
continue;
};
match &top {
None => top = Some(dir.to_string()),
Some(t) if t != dir => {
return Err(format!(
"the crate holds two top-level directories ({t}, {dir})"
))
}
Some(_) => {}
}
let small = |mut entry: tar::Entry<'_, _>, what: &str| -> Result<String, String> {
if entry.header().size().unwrap_or(u64::MAX) > MAX_MANIFEST_BYTES {
return Err(format!("the crate's {what} is larger than 1 MiB"));
}
let mut text = String::new();
(&mut entry)
.take(MAX_MANIFEST_BYTES)
.read_to_string(&mut text)
.map_err(|e| format!("the crate's {what}: {e}"))?;
Ok(text)
};
match rest {
"Cargo.toml" => manifest = Some(small(entry, "Cargo.toml")?),
"Cargo.lock" => lock = Some(small(entry, "Cargo.lock")?),
"src/main.rs" => has_main = true,
r if r.starts_with("src/bin/") && r.ends_with(".rs") => has_main = true,
_ => {}
}
}
let top = top.ok_or("the crate is empty")?;
let text = manifest.ok_or_else(|| format!("the crate carries no {top}/Cargo.toml"))?;
let m: toml::Table =
toml::from_str(&text).map_err(|e| format!("{top}/Cargo.toml does not parse: {e}"))?;
let pkg = m
.get("package")
.and_then(|p| p.as_table())
.ok_or_else(|| format!("{top}/Cargo.toml has no [package]"))?;
let name = pkg.get("name").and_then(|v| v.as_str()).unwrap_or("");
let version = pkg.get("version").and_then(|v| v.as_str()).unwrap_or("");
if name.is_empty() || version.is_empty() {
return Err(format!(
"{top}/Cargo.toml names no package name and version"
));
}
if !top.eq_ignore_ascii_case(&format!("{name}-{version}")) {
return Err(format!(
"{top}/Cargo.toml names {name} {version}, not the crate's directory {top}"
));
}
let mut deps = Vec::new();
let mut pinned: Vec<crate::Pinned> = Vec::new();
// SEC-REVIEW 2026-11-01 P11: a dependency naming another registry (`registry-index`, or a
// `registry` cargo resolved to one) would make every build of this crate fetch that
// dependency from there, past Holger: the crate gets no line.
let mut foreign: Vec<String> = Vec::new();
let mut add = |table: Option<&toml::Value>, kind: &str, target: Option<&str>| {
for (key, spec) in table.and_then(|t| t.as_table()).into_iter().flatten() {
let (req, t) = match spec {
toml::Value::String(v) => (v.clone(), None),
toml::Value::Table(t) => (
t.get("version")
.and_then(|v| v.as_str())
.unwrap_or("*")
.to_string(),
Some(t),
),
_ => continue,
};
let get_bool = |k: &str, d: bool| {
t.and_then(|t| t.get(k))
.and_then(|v| v.as_bool())
.unwrap_or(d)
};
let features: Vec<String> = t
.and_then(|t| t.get("features"))
.and_then(|v| v.as_array())
.map(|a| {
a.iter()
.filter_map(|f| f.as_str().map(str::to_string))
.collect()
})
.unwrap_or_default();
let default_features = get_bool("default-features", get_bool("default_features", true));
let package = t.and_then(|t| t.get("package")).and_then(|v| v.as_str());
let mut d = json!({
"name": key, "req": req, "features": features, "optional": get_bool("optional", false),
"default_features": default_features, "target": target, "kind": kind,
});
if let Some(p) = package {
d["package"] = Value::String(p.to_string());
}
if let Some(r) = t.and_then(|t| t.get("registry")) {
foreign.push(format!("`{key}` (registry {r})"));
continue;
}
if let Some(r) = t
.and_then(|t| t.get("registry-index"))
.and_then(|v| v.as_str())
{
// crates.io is the one other registry a crate published here may name: cargo
// writes its index for every crates.io dependency of a crate published to an
// alternative registry, and a client's `[source.crates-io] replace-with` routes
// it to this Holger's pull-through.
if !CRATES_IO_INDEXES.contains(&r.trim_end_matches('/')) {
foreign.push(format!("`{key}` ({r})"));
continue;
}
d["registry"] = Value::String(r.to_string());
}
deps.push(d);
// A dev-dependency never reaches a user of the crate.
if kind != "dev" {
if let Some(v) = exact_version(&req) {
pinned.push(crate::Pinned {
name: package.unwrap_or(key).to_string(),
version: v.to_string(),
from: "Cargo.toml".into(),
});
}
}
}
};
for (section, kind) in [
("dependencies", "normal"),
("dev-dependencies", "dev"),
("build-dependencies", "build"),
] {
add(m.get(section), kind, None);
}
for (target, t) in m
.get("target")
.and_then(|t| t.as_table())
.into_iter()
.flatten()
{
for (section, kind) in [
("dependencies", "normal"),
("dev-dependencies", "dev"),
("build-dependencies", "build"),
] {
add(t.get(section), kind, Some(target));
}
}
if !foreign.is_empty() {
return Err(format!(
"{top}/Cargo.toml depends on another registry: {}; a crate served here resolves its \
dependencies from this registry only",
foreign.join(", ")
));
}
let mut features = serde_json::Map::new();
let mut v2 = false;
for (f, list) in m
.get("features")
.and_then(|t| t.as_table())
.into_iter()
.flatten()
{
let list: Vec<Value> = list
.as_array()
.map(|a| {
a.iter()
.filter_map(|x| x.as_str())
.inspect(|x| v2 |= x.starts_with("dep:") || x.contains("?/"))
.map(|x| Value::String(x.to_string()))
.collect()
})
.unwrap_or_default();
features.insert(f.clone(), Value::Array(list));
}
let mut line = json!({ "name": name, "vers": version, "deps": deps, "features": features });
if v2 {
line["v"] = Value::from(2);
}
if let Some(l) = pkg.get("links").and_then(|v| v.as_str()) {
line["links"] = Value::String(l.to_string());
}
if let Some(r) = pkg.get("rust-version").and_then(|v| v.as_str()) {
line["rust_version"] = Value::String(r.to_string());
}
// A binary crate's lock file is what `cargo install --locked` builds: every registry
// package in it is a version this crate ships with.
let binary = has_main || m.get("bin").is_some();
if let (true, Some(lock)) = (binary, lock) {
if let Ok(l) = toml::from_str::<toml::Table>(&lock) {
for p in l
.get("package")
.and_then(|p| p.as_array())
.into_iter()
.flatten()
.filter_map(|p| p.as_table())
{
let s = |k: &str| p.get(k).and_then(|v| v.as_str()).unwrap_or("");
let from_registry =
s("source").starts_with("registry+") || s("source").starts_with("sparse+");
if from_registry && !s("name").is_empty() && !s("version").is_empty() {
pinned.push(crate::Pinned {
name: s("name").to_string(),
version: s("version").to_string(),
from: "Cargo.lock".into(),
});
}
}
}
}
pinned.sort_by(|a, b| (&a.name, &a.version).cmp(&(&b.name, &b.version)));
pinned.dedup_by(|a, b| a.name == b.name && a.version == b.version);
Ok(CrateRead {
name: name.to_string(),
version: version.to_string(),
line,
pinned,
})
}
/// **A crate's index line** with `cksum` the sha256 of `bytes` and `yanked: false`: what a
/// serve-only source derives for a sealed crate (server-lib `pull_intake::crates`).
pub fn line_from_crate(bytes: &[u8], name: &str, version: &str) -> Result<String, String> {
use sha2::Digest as _;
let r = read(bytes)?;
if !r.name.eq_ignore_ascii_case(name) || r.version != version {
return Err(format!(
"{name}-{version}.crate's Cargo.toml names {} {}, not {name} {version}",
r.name, r.version
));
}
let mut line = r.line;
line["cksum"] = Value::String(
sha2::Sha256::digest(bytes)
.iter()
.map(|b| format!("{b:02x}"))
.collect(),
);
line["yanked"] = Value::Bool(false);
Ok(line.to_string())
}