Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

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())
}