| use cargo_deny::{ |
| advisories::{self, cfg}, |
| field_eq, func_name, |
| test_utils::{self as tu}, |
| Krates, |
| }; |
| |
| struct TestCtx { |
| dbs: advisories::DbSet, |
| krates: Krates, |
| } |
| |
| fn load() -> TestCtx { |
| static ONCE: parking_lot::Once = parking_lot::Once::new(); |
| |
| ONCE.call_once(|| { |
| let mut cargo = std::process::Command::new("cargo"); |
| cargo.args([ |
| "fetch", |
| "--manifest-path", |
| "examples/06_advisories/Cargo.toml", |
| ]); |
| assert!( |
| cargo.status().expect("failed to run cargo fetch").success(), |
| "failed to fetch crates" |
| ); |
| }); |
| |
| let md: krates::cm::Metadata = serde_json::from_str( |
| &std::fs::read_to_string("tests/test_data/advisories/06_advisories.json").unwrap(), |
| ) |
| .unwrap(); |
| |
| let krates: Krates = krates::Builder::new() |
| .build_with_metadata(md, krates::NoneFilter) |
| .unwrap(); |
| |
| let db = { |
| advisories::DbSet::load( |
| "tests/advisory-db".into(), |
| vec![], |
| advisories::Fetch::Disallow(time::Duration::days(100)), |
| ) |
| .unwrap() |
| }; |
| |
| TestCtx { dbs: db, krates } |
| } |
| |
| fn iter_notes(diag: &serde_json::Value) -> Option<impl Iterator<Item = &str>> { |
| diag.pointer("/fields/notes") |
| .and_then(|notes| notes.as_array()) |
| .map(|array| array.iter().filter_map(|s| s.as_str())) |
| } |
| |
| fn find_by_code<'a>(diags: &'a [serde_json::Value], code: &str) -> Option<&'a serde_json::Value> { |
| diags.iter().find(|v| match iter_notes(v) { |
| Some(mut notes) => notes.any(|note| note.contains(code)), |
| None => false, |
| }) |
| } |
| |
| /// Validates we emit diagnostics when a vulnerability advisory is detected |
| #[test] |
| fn detects_vulnerabilities() { |
| let TestCtx { dbs, krates } = load(); |
| |
| let cfg = tu::Config::new("vulnerability = 'deny'"); |
| |
| let diags = |
| tu::gather_diagnostics::<cfg::Config, _, _>(&krates, func_name!(), cfg, |ctx, _, tx, _| { |
| advisories::check( |
| ctx, |
| &dbs, |
| Option::<advisories::NoneReporter>::None, |
| None, |
| tx, |
| ); |
| }); |
| |
| let diag = find_by_code(&diags, "RUSTSEC-2019-0001").unwrap(); |
| |
| insta::assert_json_snapshot!(diag); |
| } |
| |
| /// Validates we emit diagnostics when an unmaintained advisory is detected |
| #[test] |
| fn detects_unmaintained() { |
| let TestCtx { dbs, krates } = load(); |
| |
| let cfg = tu::Config::new("unmaintained = 'warn'"); |
| |
| let diags = |
| tu::gather_diagnostics::<cfg::Config, _, _>(&krates, func_name!(), cfg, |ctx, _, tx, _| { |
| advisories::check( |
| ctx, |
| &dbs, |
| Option::<advisories::NoneReporter>::None, |
| None, |
| tx, |
| ); |
| }); |
| |
| let unmaintained_diag = find_by_code(&diags, "RUSTSEC-2016-0004").unwrap(); |
| insta::assert_json_snapshot!(unmaintained_diag); |
| } |
| |
| /// Validates we emit diagnostics when an unsound advisory is detected |
| #[test] |
| fn detects_unsound() { |
| let TestCtx { dbs, krates } = load(); |
| |
| let cfg = tu::Config::new("unsound = 'warn'"); |
| |
| let diags = |
| tu::gather_diagnostics::<cfg::Config, _, _>(&krates, func_name!(), cfg, |ctx, _, tx, _| { |
| advisories::check( |
| ctx, |
| &dbs, |
| Option::<advisories::NoneReporter>::None, |
| None, |
| tx, |
| ); |
| }); |
| |
| let unsound_diag = find_by_code(&diags, "RUSTSEC-2019-0036").unwrap(); |
| insta::assert_json_snapshot!(unsound_diag); |
| } |
| |
| /// Validates that advisories that are ignored still have diagnostics emitted for |
| /// them, but with 'note' severity |
| #[test] |
| fn downgrades_lint_levels() { |
| let TestCtx { dbs, krates } = load(); |
| |
| let cfg = tu::Config::new( |
| r#" |
| unmaintained = "warn" |
| ignore = [ |
| "RUSTSEC-2016-0004", |
| { id = "RUSTSEC-2019-0001", reason = "this is a test" }, |
| ] |
| "#, |
| ); |
| |
| let diags = |
| tu::gather_diagnostics::<cfg::Config, _, _>(&krates, func_name!(), cfg, |ctx, _, tx, _| { |
| advisories::check( |
| ctx, |
| &dbs, |
| Option::<advisories::NoneReporter>::None, |
| None, |
| tx, |
| ); |
| }); |
| |
| let downgraded = [ |
| find_by_code(&diags, "RUSTSEC-2016-0004").unwrap(), |
| find_by_code(&diags, "RUSTSEC-2019-0001").unwrap(), |
| ]; |
| |
| insta::assert_json_snapshot!(downgraded); |
| |
| let ignored: Vec<_> = diags |
| .into_iter() |
| .filter(|v| { |
| v.pointer("/fields/code") |
| .and_then(|s| s.as_str()) |
| .map_or(false, |s| s == "advisory-ignored") |
| }) |
| .collect(); |
| |
| insta::assert_json_snapshot!(ignored); |
| } |
| |
| /// Validates we can detect yanked crates from sparse, git, and |
| /// non crates.io registries |
| #[test] |
| fn detects_yanked() { |
| // This crate has really light dependencies that _should_ still exercise |
| // the yank checking without taking more than a couple of seconds to download |
| // even though we always do it in a fresh temporary directory |
| let td = temp_dir(); |
| let cargo_home = td.path(); |
| |
| let mut cmd = krates::Cmd::new(); |
| |
| // Note we need to set the current directory as cargo has a bug/design flaw |
| // where .cargo/config.toml is only searched from the current working directory |
| // not the location of the root manifest. which is....really annoying |
| cmd.current_dir("examples/12_yank_check") |
| .lock_opts(krates::LockOptions { |
| frozen: false, |
| locked: true, |
| offline: false, |
| }); |
| |
| let mut cmd: krates::cm::MetadataCommand = cmd.into(); |
| cmd.env("CARGO_HOME", cargo_home); |
| |
| let krates: Krates = krates::Builder::new() |
| .build(cmd, krates::NoneFilter) |
| .unwrap(); |
| |
| let indices = advisories::Indices::load(&krates, cargo_home.to_owned().try_into().unwrap()); |
| let dbs = advisories::DbSet { dbs: Vec::new() }; |
| |
| { |
| let cfg = |
| tu::Config::new("yanked = 'deny'\nunmaintained = 'allow'\nvulnerability = 'allow'"); |
| |
| let indices = advisories::Indices { |
| indices: Vec::new(), |
| cache: indices.cache.clone(), |
| }; |
| |
| let diags = tu::gather_diagnostics::<cfg::Config, _, _>( |
| &krates, |
| func_name!(), |
| cfg, |
| |ctx, _, tx, _| { |
| advisories::check( |
| ctx, |
| &dbs, |
| Option::<advisories::NoneReporter>::None, |
| Some(indices), |
| tx, |
| ); |
| }, |
| ); |
| |
| let diags: Vec<_> = diags |
| .into_iter() |
| .filter(|v| { |
| v.pointer("/fields/message") |
| .and_then(|v| v.as_str()) |
| .map_or(false, |v| v.starts_with("detected yanked crate")) |
| }) |
| .collect(); |
| |
| insta::assert_json_snapshot!(diags); |
| } |
| |
| { |
| let cfg = tu::Config::new( |
| r#" |
| yanked = "deny" |
| ignore = [ |
| # This crate is in the graph, but we're ignoring it |
| { crate = "[email protected]", reason = "a new version has not been released yet" }, |
| # This crate is not in the graph, so we should get a warning about it |
| "boop", |
| ] |
| unmaintained = "allow" |
| vulnerability = "allow" |
| "#, |
| ); |
| |
| let diags = tu::gather_diagnostics::<cfg::Config, _, _>( |
| &krates, |
| func_name!(), |
| cfg, |
| |ctx, _, tx, _| { |
| advisories::check( |
| ctx, |
| &dbs, |
| Option::<advisories::NoneReporter>::None, |
| Some(indices), |
| tx, |
| ); |
| }, |
| ); |
| |
| let diags: Vec<_> = diags |
| .into_iter() |
| .filter(|v| { |
| v.pointer("/fields/message") |
| .and_then(|v| v.as_str()) |
| .map_or(false, |v| { |
| v.starts_with("detected yanked crate") || v.starts_with("yanked crate") |
| }) |
| }) |
| .collect(); |
| |
| insta::assert_json_snapshot!(diags); |
| } |
| } |
| |
| /// Validates that if we fail to load 1 or more indices, all the crates sourced |
| /// to that index will emit an diagnostic that they can't be checked |
| #[test] |
| fn warns_on_index_failures() { |
| let TestCtx { dbs, krates } = load(); |
| |
| let cfg = tu::Config::new("yanked = 'deny'\nunmaintained = 'allow'\nvulnerability = 'allow'"); |
| |
| let source = cargo_deny::Source::crates_io(false); |
| |
| let mut cache = std::collections::BTreeMap::new(); |
| |
| for krate in krates.krates() { |
| cache.insert( |
| (krate.name.as_str(), &source), |
| advisories::Entry::Error("this path is valid but we pretend it is non-utf8".into()), |
| ); |
| } |
| |
| let indices = advisories::Indices { |
| indices: vec![( |
| &source, |
| Err(tame_index::Error::NonUtf8Path( |
| "this path is valid but we pretend it is non-utf8".into(), |
| )), |
| )], |
| cache, |
| }; |
| |
| let diags = |
| tu::gather_diagnostics::<cfg::Config, _, _>(&krates, func_name!(), cfg, |ctx, _, tx, _| { |
| advisories::check( |
| ctx, |
| &dbs, |
| Option::<advisories::NoneReporter>::None, |
| Some(indices), |
| tx, |
| ); |
| }); |
| |
| // This is the number of crates sourced to crates.io |
| assert_eq!( |
| diags |
| .into_iter() |
| .filter(|v| { field_eq!(v, "/fields/message", "unable to check for yanked crates") }) |
| .count(), |
| 193 |
| ); |
| } |
| |
| /// Validates that we emit a warning if a crate in the graph _does_ match an |
| /// advisory, however that advisory has been withdrawn <https://github.com/rustsec/advisory-db/pull/942> |
| #[test] |
| fn warns_on_ignored_and_withdrawn() { |
| let TestCtx { dbs, krates } = load(); |
| |
| let cfg = tu::Config::new( |
| "yanked = 'deny'\nunmaintained = 'deny'\nvulnerability = 'deny'\nignore = ['RUSTSEC-2020-0053']", |
| ); |
| |
| let diags = |
| tu::gather_diagnostics::<cfg::Config, _, _>(&krates, func_name!(), cfg, |ctx, _, tx, _| { |
| advisories::check( |
| ctx, |
| &dbs, |
| Option::<advisories::NoneReporter>::None, |
| None, |
| tx, |
| ); |
| }); |
| |
| insta::assert_json_snapshot!(diags |
| .iter() |
| .find(|diag| field_eq!(diag, "/fields/code", "advisory-not-detected")) |
| .unwrap()); |
| } |
| |
| #[inline] |
| fn temp_dir() -> tempfile::TempDir { |
| tempfile::tempdir_in(env!("CARGO_TARGET_TMPDIR")).unwrap() |
| } |
| |
| #[inline] |
| fn to_path(td: &tempfile::TempDir) -> Option<&cargo_deny::Path> { |
| Some(cargo_deny::Path::from_path(td.path()).unwrap()) |
| } |
| |
| /// Validates that stale advisory databases result in an error |
| #[test] |
| fn fails_on_stale_advisory_database() { |
| assert!(advisories::DbSet::load( |
| "tests/advisory-db".into(), |
| vec![], |
| advisories::Fetch::Disallow(time::Duration::seconds(0)), |
| ) |
| .unwrap_err() |
| .to_string() |
| .contains("repository is stale")); |
| } |
| |
| use advisories::Fetch; |
| |
| const TEST_DB_URL: &str = "https://github.com/EmbarkStudios/test-advisory-db"; |
| const TEST_DB_PATH: &str = "tests/advisory-db/github.com-c373669cccc50ac0"; |
| const GIT_PATH: &str = "github.com-c373669cccc50ac0/.git"; |
| const GIT_SUB_PATH: &str = ".git/modules/tests/advisory-db/github.com-c373669cccc50ac0"; |
| |
| /// Expected HEAD without fetch |
| const EXPECTED_ONE: &str = "1f44d565d81692a44b8c7af8a80f587e19757f8c"; |
| const EXPECTED_ONE_ID: &str = "BOOP-2023-0001"; |
| const EXPECTED_ONE_DATE: &str = "2023-06-30"; |
| /// Expected remote HEAD for <https://github.com/EmbarkStudios/test-advisory-db> |
| const EXPECTED_TWO: &str = "c84d73b086cc762f6a2b8ed794d47171a52781a3"; |
| const EXPECTED_TWO_ID: &str = "BOOP-2023-0002"; |
| const EXPECTED_TWO_DATE: &str = "2023-07-10"; |
| |
| fn do_open(td: &tempfile::TempDir, f: Fetch) -> advisories::AdvisoryDb { |
| let mut db_set = advisories::DbSet::load( |
| to_path(td).unwrap().to_owned(), |
| vec![TEST_DB_URL.parse().unwrap()], |
| f, |
| ) |
| .unwrap(); |
| |
| db_set.dbs.pop().unwrap() |
| } |
| |
| fn validate(adb: &advisories::AdvisoryDb, rev: &str, ids: &[(&str, &str)]) { |
| let repo = gix::open(&adb.path).expect("failed to open repo"); |
| assert_eq!(repo.head_commit().unwrap().id.to_hex().to_string(), rev); |
| |
| for (id, date) in ids { |
| let adv = adb.db.get(&id.parse().unwrap()).expect("unable to find id"); |
| assert_eq!(adv.date().as_str(), *date); |
| } |
| |
| assert!( |
| (time::OffsetDateTime::now_utc() - adb.fetch_time) < std::time::Duration::from_secs(60) |
| ); |
| } |
| |
| /// Validates we can clone an advisory database with gix |
| #[test] |
| fn clones_with_gix() { |
| let td = temp_dir(); |
| let db = do_open(&td, Fetch::Allow); |
| |
| validate( |
| &db, |
| EXPECTED_TWO, |
| &[ |
| (EXPECTED_ONE_ID, EXPECTED_ONE_DATE), |
| (EXPECTED_TWO_ID, EXPECTED_TWO_DATE), |
| ], |
| ); |
| } |
| |
| /// Validates we can clone an advisory database with git |
| #[test] |
| fn clones_with_git() { |
| let td = temp_dir(); |
| let db = do_open(&td, Fetch::AllowWithGitCli); |
| |
| validate( |
| &db, |
| EXPECTED_TWO, |
| &[ |
| (EXPECTED_ONE_ID, EXPECTED_ONE_DATE), |
| (EXPECTED_TWO_ID, EXPECTED_TWO_DATE), |
| ], |
| ); |
| } |
| |
| fn validate_fetch(fetch: Fetch) { |
| let td = temp_dir(); |
| |
| fs_extra::copy_items( |
| &[TEST_DB_PATH], |
| td.path(), |
| &fs_extra::dir::CopyOptions::default(), |
| ) |
| .expect("failed to copy"); |
| |
| let git_path = td.path().join(GIT_PATH); |
| std::fs::remove_file(&git_path).expect("unable to remove .git file"); |
| |
| fs_extra::copy_items( |
| &[GIT_SUB_PATH], |
| &git_path, |
| &fs_extra::dir::CopyOptions { |
| copy_inside: true, |
| ..Default::default() |
| }, |
| ) |
| .expect("failed to copy"); |
| |
| // We need to overwrite the config file in the git directory, otherwise |
| // mutations will actually affect the working tree rather than the actual |
| // temp location we've copied the submodule into |
| std::fs::write( |
| git_path.join("config"), |
| r#" |
| [core] |
| repositoryformatversion = 0 |
| filemode = true |
| bare = false |
| logallrefupdates = true |
| [remote "origin"] |
| url = https://github.com/EmbarkStudios/test-advisory-db |
| fetch = +refs/heads/*:refs/remotes/origin/* |
| [branch "main"] |
| remote = origin |
| merge = refs/heads/main |
| "#, |
| ) |
| .expect("failed to write config"); |
| |
| let db = do_open(&td, Fetch::Disallow(time::Duration::days(10000))); |
| validate(&db, EXPECTED_ONE, &[(EXPECTED_ONE_ID, EXPECTED_ONE_DATE)]); |
| |
| let db = do_open(&td, fetch); |
| validate( |
| &db, |
| EXPECTED_TWO, |
| &[ |
| (EXPECTED_ONE_ID, EXPECTED_ONE_DATE), |
| (EXPECTED_TWO_ID, EXPECTED_TWO_DATE), |
| ], |
| ); |
| } |
| |
| /// Validates we can fetch advisory db updates with gix |
| #[test] |
| fn fetches_with_gix() { |
| if std::env::var_os("CI").is_some() && cfg!(target_os = "macos") { |
| println!("consistently times out, so tired"); |
| return; |
| } |
| |
| validate_fetch(Fetch::Allow); |
| } |
| |
| /// Validates we can fetch advisory db updates with git |
| #[test] |
| fn fetches_with_git() { |
| if std::env::var_os("CI").is_some() && cfg!(target_os = "macos") { |
| println!("consistently times out, so tired"); |
| return; |
| } |
| |
| validate_fetch(Fetch::AllowWithGitCli); |
| } |
| |
| /// Validates that we can detect source replacement and can still perform yank |
| /// checking |
| #[test] |
| fn crates_io_source_replacement() { |
| use rayon::prelude::*; |
| |
| // Create a local registry in a temp dir that we use a source replacement |
| // for crates.io |
| let lrd = temp_dir(); |
| { |
| use tame_index::{external::reqwest, index::local}; |
| |
| let sparse = tame_index::index::RemoteSparseIndex::new( |
| tame_index::SparseIndex::new(tame_index::IndexLocation::new( |
| tame_index::IndexUrl::CratesIoSparse, |
| )) |
| .unwrap(), |
| reqwest::blocking::Client::new(), |
| ); |
| |
| // Use a separate even more temporary cargo home for the gathering of the |
| // crates we want to write to the temp local registry, so that we don't |
| // pollute the cargo home used in the actual test |
| let temp_cargo_home = temp_dir(); |
| |
| let mut cmd = krates::Cmd::new(); |
| |
| // Note we need to set the current directory as cargo has a bug/design flaw |
| // where .cargo/config.toml is only searched from the current working directory |
| // not the location of the root manifest. which is....really annoying |
| cmd.current_dir("examples/12_yank_check") |
| .lock_opts(krates::LockOptions { |
| frozen: false, |
| locked: true, |
| offline: false, |
| }); |
| |
| let mut cmd: krates::cm::MetadataCommand = cmd.into(); |
| cmd.env("CARGO_HOME", temp_cargo_home.path()); |
| |
| let krates: Krates = krates::Builder::new() |
| .build(cmd, krates::NoneFilter) |
| .unwrap(); |
| |
| struct IndexPkg { |
| ik: tame_index::IndexKrate, |
| version: semver::Version, |
| } |
| |
| let lock = &tame_index::utils::flock::FileLock::unlocked(); |
| |
| let index_krates: Vec<_> = krates |
| .krates() |
| .filter_map(|k| { |
| if k.source.as_ref().map_or(true, |s| !s.is_crates_io()) { |
| return None; |
| } |
| Some(IndexPkg { |
| ik: sparse |
| .cached_krate(k.name.as_str().try_into().unwrap(), lock) |
| .unwrap() |
| .unwrap(), |
| version: k.version.clone(), |
| }) |
| }) |
| .collect(); |
| |
| let client = |
| local::builder::Client::build(reqwest::blocking::ClientBuilder::new()).unwrap(); |
| |
| let lrb = local::LocalRegistryBuilder::create(to_path(&lrd).unwrap().to_owned()).unwrap(); |
| let config = sparse.index.index_config().unwrap(); |
| |
| index_krates.into_par_iter().for_each(|ip| { |
| let iv = ip |
| .ik |
| .versions |
| .iter() |
| .find(|iv| iv.version.parse::<cargo_deny::Version>().unwrap() == ip.version) |
| .unwrap(); |
| let vk = local::ValidKrate::download(&client, &config, iv).unwrap(); |
| |
| lrb.insert(&ip.ik, &[vk]).unwrap(); |
| }); |
| |
| let _lr = lrb.finalize(true).unwrap(); |
| } |
| |
| // Copy the package to a new temp dir so that we can mutate the config.toml |
| // to use our new local registry |
| let pkg_dir = temp_dir(); |
| { |
| fs_extra::copy_items( |
| &["examples/12_yank_check"], |
| pkg_dir.path(), |
| &Default::default(), |
| ) |
| .expect("failed to copy"); |
| |
| let config_path = pkg_dir.path().join("12_yank_check/.cargo/config.toml"); |
| let mut cfg = |
| std::fs::read_to_string(&config_path).expect("failed to read .cargo/config.toml"); |
| |
| cfg.push_str("\n[source.temp-local-registry]\n"); |
| use std::fmt::Write; |
| writeln!(&mut cfg, "local-registry = \"{}\"", to_path(&lrd).unwrap()).unwrap(); |
| |
| cfg.push_str("\n[source.crates-io]\n"); |
| cfg.push_str("replace-with = \"temp-local-registry\""); |
| |
| std::fs::write(config_path, cfg).expect("failed to write .cargo/config.toml"); |
| } |
| |
| // This crate has really light dependencies that _should_ still exercise |
| // the yank checking without taking more than a couple of seconds to download |
| // even though we always do it in a fresh temporary directory |
| let td = temp_dir(); |
| let cargo_home = td.path(); |
| |
| let mut cmd = krates::Cmd::new(); |
| |
| // Note we need to set the current directory as cargo has a bug/design flaw |
| // where .cargo/config.toml is only searched from the current working directory |
| // not the location of the root manifest. which is....really annoying |
| cmd.current_dir(pkg_dir.path().join("12_yank_check")) |
| .lock_opts(krates::LockOptions { |
| frozen: false, |
| locked: true, |
| offline: false, |
| }); |
| |
| let mut cmd: krates::cm::MetadataCommand = cmd.into(); |
| cmd.env("CARGO_HOME", cargo_home); |
| |
| let cargo_home: camino::Utf8PathBuf = cargo_home.to_owned().try_into().unwrap(); |
| |
| let mut kb = krates::Builder::new(); |
| cargo_deny::krates_with_index( |
| &mut kb, |
| Some(to_path(&pkg_dir).unwrap().join("12_yank_check")), |
| Some(cargo_home.clone()), |
| ) |
| .unwrap(); |
| |
| let krates: Krates = kb.build(cmd, krates::NoneFilter).unwrap(); |
| |
| let indices = advisories::Indices::load(&krates, cargo_home.clone()); |
| |
| let cfg = tu::Config::new("yanked = 'deny'\nunmaintained = 'allow'\nvulnerability = 'allow'"); |
| |
| let dbs = advisories::DbSet { dbs: Vec::new() }; |
| |
| let diags = |
| tu::gather_diagnostics::<cfg::Config, _, _>(&krates, func_name!(), cfg, |ctx, _, tx, _| { |
| advisories::check( |
| ctx, |
| &dbs, |
| Option::<advisories::NoneReporter>::None, |
| Some(indices), |
| tx, |
| ); |
| }); |
| |
| let diags: Vec<_> = diags |
| .into_iter() |
| .filter(|v| { |
| v.pointer("/fields/message") |
| .and_then(|v| v.as_str()) |
| .map_or(false, |v| v.starts_with("detected yanked crate")) |
| }) |
| .collect(); |
| |
| insta::assert_json_snapshot!(diags); |
| |
| // Now that we've verified we can perform the yank checks against valid indices, go |
| // in and corrupt/remove the index entries and perform the check again to ensure we |
| // give good error messages to the user |
| |
| { |
| let index = tame_index::index::ComboIndexCache::new( |
| tame_index::IndexLocation::new( |
| tame_index::IndexUrl::crates_io( |
| Some(to_path(&pkg_dir).unwrap().join("12_yank_check")), |
| Some(&cargo_home), |
| None, |
| ) |
| .unwrap(), |
| ) |
| .with_root(Some(cargo_home.clone())), |
| ) |
| .unwrap(); |
| |
| // Nuke spdx entirely |
| let spath = index.cache_path("spdx".try_into().unwrap()); |
| std::fs::remove_file(spath).unwrap(); |
| |
| // Note we also need to nuke the spdx crate file otherwise the local registry |
| // validation will fail, we don't care about this, we are screwing it up on purpose |
| std::fs::remove_file(lrd.path().join("spdx-0.3.1.crate")).unwrap(); |
| |
| // Remove the specific version of smallvec pinned by the lockfile |
| { |
| let spath = index.cache_path("smallvec".try_into().unwrap()); |
| let json = std::fs::read_to_string(&spath).unwrap(); |
| |
| let mut file = std::fs::File::create(spath).unwrap(); |
| for line in json.lines() { |
| use std::io::Write as _; |
| if line.contains(r#","vers":"1.6.1","#) { |
| continue; |
| } |
| assert_eq!( |
| file.write_vectored(&[ |
| std::io::IoSlice::new(line.as_bytes()), |
| std::io::IoSlice::new(b"\n"), |
| ]) |
| .unwrap(), |
| line.len() + 1 |
| ); |
| } |
| |
| std::fs::remove_file(lrd.path().join("smallvec-1.6.1.crate")).unwrap(); |
| } |
| } |
| |
| // Change the version of the cache entry to a too old version that tame-index doesn't support |
| { |
| let index = tame_index::index::ComboIndexCache::new( |
| tame_index::IndexLocation::new( |
| "https://github.com/EmbarkStudios/cargo-test-index".into(), |
| ) |
| .with_root(Some(cargo_home.clone())), |
| ) |
| .unwrap(); |
| |
| let spath = index.cache_path("crate-two".try_into().unwrap()); |
| let mut sc = std::fs::read(&spath).unwrap(); |
| sc[0] = tame_index::index::cache::CURRENT_CACHE_VERSION /* 3 */ - 2; |
| std::fs::write(spath, sc).unwrap(); |
| } |
| |
| // Corrupt the entry by making the etag string invalid utf8 |
| { |
| let index = tame_index::index::ComboIndexCache::new( |
| tame_index::IndexLocation::new( |
| "sparse+https://cargo.cloudsmith.io/embark/deny/".into(), |
| ) |
| .with_root(Some(cargo_home.clone())), |
| ) |
| .unwrap(); |
| |
| let spath = index.cache_path("crate-one".try_into().unwrap()); |
| let mut sc = std::fs::read(&spath).unwrap(); |
| sc[11] = 0xc0; |
| sc[12] = 0x80; |
| std::fs::write(spath, sc).unwrap(); |
| } |
| |
| let indices = advisories::Indices::load(&krates, cargo_home.clone()); |
| |
| let cfg = tu::Config::new("yanked = 'deny'\nunmaintained = 'allow'\nvulnerability = 'allow'"); |
| |
| let diags = |
| tu::gather_diagnostics::<cfg::Config, _, _>(&krates, func_name!(), cfg, |ctx, _, tx, _| { |
| advisories::check( |
| ctx, |
| &dbs, |
| Option::<advisories::NoneReporter>::None, |
| Some(indices), |
| tx, |
| ); |
| }); |
| |
| let diags: Vec<_> = diags |
| .into_iter() |
| .filter_map(|v| { |
| v.pointer("/fields/notes/0") |
| .and_then(|v| v.as_str()) |
| .map(|n| { |
| ( |
| v.pointer("/fields/graphs/0/Krate/name") |
| .and_then(|v| v.as_str()) |
| .unwrap() |
| .to_owned(), |
| n.replace(cargo_home.as_str(), "$TEMP_LOCAL"), |
| ) |
| }) |
| }) |
| .collect(); |
| |
| insta::assert_json_snapshot!(diags); |
| } |