diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7a9cc6bb..4521a107 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -35,7 +35,7 @@ jobs: name: Test strategy: matrix: - os: [ubuntu-22.04, windows-2022] + os: [ubuntu-22.04] runs-on: ${{ matrix.os }} steps: # We need to disable conversion to CRLF line endings on windows because it's @@ -99,7 +99,7 @@ jobs: # Also, build and run with musl, this lets us ensure that # musl still works, which is important for the linux binaries # we release, but wasn't exercised until now - run: cargo install --path . --debug --target ${{ matrix.target }} --features standalone + run: cargo install --path . --debug --target ${{ matrix.target }} - name: self check run: cargo deny -L debug --all-features --locked check - name: check external users @@ -112,7 +112,7 @@ jobs: steps: - uses: actions/checkout@v3 - env: - version: "0.4.28" + version: "0.4.32" run: | set -e curl -L https://github.com/rust-lang-nursery/mdBook/releases/download/v${version}/mdbook-v${version}-x86_64-unknown-linux-gnu.tar.gz | tar xzf - @@ -134,19 +134,6 @@ jobs: - name: cargo publish run: cargo publish --dry-run - msrv-check: - name: Minimum Stable Rust Version Check - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: "1.65.0" - - uses: Swatinem/rust-cache@v2 - - run: cargo fetch - - name: cargo check - run: cargo check --all-targets - release: name: Release #needs: [test, self, doc-book] diff --git a/.gitignore b/.gitignore index 1579d142..70a1a6f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /target **/*.rs.bk -/examples/06_advisories/target +/examples/**/target +/tests/test_data/**/target scripts/check *.snap.new diff --git a/.gitmodules b/.gitmodules index 2700cfbd..318ebd73 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "tests/advisory-db/github.com-2f857891b7f43c59"] - path = tests/advisory-db/github.com-2f857891b7f43c59 + path = tests/advisory-db/github.com-a946fc29ac602819 url = https://github.com/rustsec/advisory-db +[submodule "tests/advisory-db/github.com-c373669cccc50ac0"] + path = tests/advisory-db/github.com-c373669cccc50ac0 + url = https://github.com/EmbarkStudios/test-advisory-db diff --git a/Cargo.lock b/Cargo.lock index aad51a38..359e1602 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,72 +2,108 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] [[package]] name = "anstream" -version = "0.2.6" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" dependencies = [ "anstyle", "anstyle-parse", + "anstyle-query", "anstyle-wincon", - "concolor-override", - "concolor-query", + "colorchoice", "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "0.3.5" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" [[package]] name = "anstyle-parse" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "anstyle-wincon" -version = "0.2.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ "anstyle", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" + +[[package]] +name = "arc-swap" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" [[package]] name = "arrayvec" -version = "0.5.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "askalono" @@ -88,6 +124,30 @@ dependencies = [ "zstd", ] +[[package]] +name = "async-compression" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b74f44609f0f91493e3082d3734d98497e094777144380ea4db9f9905dd5b6" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-trait" +version = "0.1.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.27", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -95,22 +155,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] -name = "base16ct" -version = "0.2.0" +name = "backtrace" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] [[package]] name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64ct" -version = "1.6.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bitflags" @@ -119,13 +182,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "bitmaps" -version = "2.1.0" +name = "bitflags" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" -dependencies = [ - "typenum", -] +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" [[package]] name = "bitvec" @@ -150,21 +210,29 @@ dependencies = [ [[package]] name = "bstr" -version = "1.4.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" +checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" dependencies = [ "memchr", - "once_cell", "regex-automata", "serde", ] +[[package]] +name = "btoi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad" +dependencies = [ + "num-traits", +] + [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "byteorder" @@ -178,86 +246,13 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -[[package]] -name = "bytesize" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38fcc2979eff34a4b84e1cf9a1e3da42a7d44b3b690a40cdcb23e3d556cfb2e5" - [[package]] name = "camino" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo" -version = "0.69.1" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ecf1ab9246253733b30352b5194b4c3a7038a1c7729e1a18b3ba00e479c4d54" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" dependencies = [ - "anyhow", - "base64", - "bytesize", - "cargo-platform", - "cargo-util", - "clap", - "crates-io", - "curl", - "curl-sys", - "env_logger", - "filetime", - "flate2", - "fwdansi", - "git2", - "git2-curl", - "glob", - "hex 0.4.3", - "hmac", - "home", - "http-auth", - "humantime", - "ignore", - "im-rc", - "indexmap", - "is-terminal", - "itertools", - "jobserver", - "lazy_static", - "lazycell", - "libc", - "libgit2-sys", - "log", - "memchr", - "opener", - "openssl", - "os_info", - "pasetors", - "pathdiff", - "percent-encoding", - "rustc-workspace-hack", - "rustfix", - "semver", "serde", - "serde-value", - "serde_ignored", - "serde_json", - "sha1", - "shell-escape", - "strip-ansi-escapes", - "tar", - "tempfile", - "termcolor", - "time", - "toml_edit 0.15.0", - "unicode-width", - "unicode-xid", - "url", - "walkdir", - "winapi", ] [[package]] @@ -267,20 +262,20 @@ dependencies = [ "anyhow", "askalono", "bitvec", - "cargo", + "camino", "clap", "codespan", "codespan-reporting", - "crates-index", "crossbeam", "fern", - "git2", + "fs_extra", + "gix", "home", "insta", - "is-terminal", "krates", "log", "nu-ansi-term", + "parking_lot", "rayon", "rustsec", "semver", @@ -289,61 +284,40 @@ dependencies = [ "smallvec", "spdx", "strum", + "tame-index", "tempfile", "time", - "toml 0.7.3", + "toml", "twox-hash", "url", ] [[package]] name = "cargo-lock" -version = "8.0.3" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031718ddb8f78aa5def78a09e90defe30151d1f6c672f937af4dd916429ed996" +checksum = "e11c675378efb449ed3ce8de78d75d0d80542fc98487c26aba28eb3b82feac72" dependencies = [ "semver", "serde", - "toml 0.5.11", + "toml", "url", ] [[package]] name = "cargo-platform" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" dependencies = [ "serde", ] -[[package]] -name = "cargo-util" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e0cd00582e110eb8d99de768521d36fce9e24a286babf3cea68824ae09948f" -dependencies = [ - "anyhow", - "core-foundation", - "crypto-hash", - "filetime", - "hex 0.4.3", - "jobserver", - "libc", - "log", - "miow", - "same-file", - "shell-escape", - "tempfile", - "walkdir", - "winapi", -] - [[package]] name = "cargo_metadata" -version = "0.15.3" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07" +checksum = "e7daec1a2a2129eeba1644b220b4647ec537b0b5d4bfd6876fcc5a540056b592" dependencies = [ "camino", "cargo-platform", @@ -364,9 +338,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.0" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732ffa2b10f89dd59538349855f3eaf4032e909361ac4917a52fb43cc70fccf0" +checksum = "215c0072ecc28f92eeb0eea38ba63ddfcb65c2828c46311d646f1a3ff5f9841c" dependencies = [ "smallvec", "target-lexicon", @@ -380,9 +354,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.2.1" +version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" +checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" dependencies = [ "clap_builder", "clap_derive", @@ -391,34 +365,39 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.1" +version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" dependencies = [ "anstream", "anstyle", - "bitflags", "clap_lex", "strsim", ] [[package]] name = "clap_derive" -version = "4.2.0" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.27", ] [[package]] name = "clap_lex" -version = "0.4.1" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "clru" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" +checksum = "b8191fa7302e03607ff0e237d4246cc043ff5b3cb9409d995172ba3bea16b807" [[package]] name = "codespan" @@ -440,126 +419,32 @@ dependencies = [ ] [[package]] -name = "combine" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "commoncrypto" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d056a8586ba25a1e4d61cb090900e495952c7886786fc55f909ab2f819b69007" -dependencies = [ - "commoncrypto-sys", -] - -[[package]] -name = "commoncrypto-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fed34f46747aa73dfaa578069fd8279d2818ade2b55f38f22a9401c7f4083e2" -dependencies = [ - "libc", -] - -[[package]] -name = "concolor-override" +name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" - -[[package]] -name = "concolor-query" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" -dependencies = [ - "windows-sys 0.45.0", -] +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "console" -version = "0.15.5" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", "libc", - "windows-sys 0.42.0", -] - -[[package]] -name = "const-oid" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" - -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", + "windows-sys 0.45.0", ] -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - [[package]] name = "cpufeatures" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] -[[package]] -name = "crates-index" -version = "0.19.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51ddd986d8b0405750d3da55a36cfa5ddad74a6dbf8826dec1cae40bf1218bd4" -dependencies = [ - "git2", - "hex 0.4.3", - "home", - "memchr", - "num_cpus", - "rayon", - "rustc-hash", - "semver", - "serde", - "serde_derive", - "serde_json", - "smol_str", - "toml 0.7.3", -] - -[[package]] -name = "crates-io" -version = "0.35.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2dfb6077da60207264ab2eb0e3734f02e0a0c50c347b32c728e42c6fbbf7e2e" -dependencies = [ - "anyhow", - "curl", - "percent-encoding", - "serde", - "serde_json", - "url", -] - [[package]] name = "crc32fast" version = "1.3.2" @@ -606,9 +491,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if", @@ -629,25 +514,13 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] -[[package]] -name = "crypto-bigint" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2538c4e68e52548bacb3e83ac549f903d44f011ac9d5abb5e132e67d0808f7" -dependencies = [ - "generic-array", - "rand_core", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" version = "0.1.6" @@ -659,367 +532,975 @@ dependencies = [ ] [[package]] -name = "crypto-hash" -version = "0.3.4" +name = "cvss" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a77162240fd97248d19a564a565eb563a3f592b386e4136fb300909e67dddca" +checksum = "7ec6a2f799b0e3103192800872de17ee1d39fe0c598628277b9b012f09b4010f" dependencies = [ - "commoncrypto", - "hex 0.3.2", - "openssl", - "winapi", + "serde", ] [[package]] -name = "ct-codecs" -version = "1.1.1" +name = "data-encoding" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] -name = "curl" -version = "0.4.44" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2", - "winapi", + "block-buffer", + "crypto-common", ] [[package]] -name = "curl-sys" -version = "0.4.61+curl-8.0.1" +name = "dunce" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d05c10f541ae6f3bc5b3d923c20001f47db7d5f0b2bc6ad16490133842db79" -dependencies = [ - "cc", - "libc", - "libnghttp2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi", -] +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] -name = "cvss" -version = "2.0.0" +name = "either" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec6a2f799b0e3103192800872de17ee1d39fe0c598628277b9b012f09b4010f" -dependencies = [ - "serde", -] +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] -name = "der" -version = "0.7.1" +name = "encode_unicode" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc906908ea6458456e5eaa160a9c08543ec3d1e6f71e2235cedd660cb65f9df0" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] -name = "digest" -version = "0.10.6" +name = "encoding_rs" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ - "block-buffer", - "crypto-common", - "subtle", + "cfg-if", ] [[package]] -name = "ecdsa" -version = "0.16.2" +name = "enum-as-inner" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644d3b8674a5fc5b929ae435bca85c2323d85ccb013a5509c2ac9ee11a6284ba" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "ed25519-compact" -version = "2.0.4" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3d382e8464107391c8706b4c14b087808ecb909f6c15c34114bc42e53a9e4c" -dependencies = [ - "getrandom", -] +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] [[package]] -name = "elliptic-curve" -version = "0.13.2" +name = "errno-dragonfly" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea5a92946e8614bb585254898bb7dd1ddad241ace60c52149e3765e34cc039d" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "hkdf", - "pem-rfc7468", - "pkcs8", - "rand_core", - "sec1", - "subtle", - "zeroize", + "cc", + "libc", ] [[package]] -name = "encode_unicode" -version = "0.3.6" +name = "fastrand" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] -name = "env_logger" -version = "0.10.0" +name = "fern" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" dependencies = [ - "humantime", - "is-terminal", "log", - "regex", - "termcolor", ] [[package]] -name = "errno" -version = "0.3.0" +name = "filetime" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" dependencies = [ - "errno-dragonfly", + "cfg-if", "libc", - "windows-sys 0.45.0", + "redox_syscall 0.2.16", + "windows-sys 0.48.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "fixedbitset" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ - "cc", + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs-err" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.27", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", "libc", + "wasi", ] [[package]] -name = "fastrand" -version = "1.9.0" +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + +[[package]] +name = "gix" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "275b1bfa0d6f6ed31a2e2e878a4539f4994eac8840546283ab3aebbd8fcaa42d" +dependencies = [ + "gix-actor", + "gix-attributes", + "gix-commitgraph", + "gix-config", + "gix-credentials", + "gix-date", + "gix-diff", + "gix-discover", + "gix-features", + "gix-filter", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-hashtable", + "gix-ignore", + "gix-index", + "gix-lock", + "gix-mailmap", + "gix-negotiate", + "gix-object", + "gix-odb", + "gix-pack", + "gix-path", + "gix-prompt", + "gix-protocol", + "gix-ref", + "gix-refspec", + "gix-revision", + "gix-sec", + "gix-tempfile", + "gix-trace", + "gix-transport", + "gix-traverse", + "gix-url", + "gix-utils", + "gix-validate", + "gix-worktree", + "log", + "once_cell", + "reqwest", + "signal-hook", + "smallvec", + "thiserror", + "unicode-normalization", +] + +[[package]] +name = "gix-actor" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7606482631d56cc6bfba3394ae42fc64927635024298befbb7923b6144774e8" +dependencies = [ + "bstr", + "btoi", + "gix-date", + "itoa", + "nom", + "thiserror", +] + +[[package]] +name = "gix-attributes" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63a134a674e39e238bd273326a9815296cc71f867ad5466518da71392cff98ce" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-quote", + "kstring", + "log", + "smallvec", + "thiserror", + "unicode-bom", +] + +[[package]] +name = "gix-bitmap" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +checksum = "0aa8bbde7551a9e3e783a2871f53bbb0f50aac7a77db5680c8709f69e8ce724f" dependencies = [ - "instant", + "thiserror", +] + +[[package]] +name = "gix-chunk" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b42ea64420f7994000130328f3c7a2038f639120518870436d31b8bde704493" +dependencies = [ + "thiserror", +] + +[[package]] +name = "gix-command" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2783ad148fb16bf9cfd46423706ba552a62a4d4a18fda5dd07648eb0228862dd" +dependencies = [ + "bstr", +] + +[[package]] +name = "gix-commitgraph" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4c2d5ce99eba59fe9477a9e3037b0e1d0266d53925cc4b322bc06c566589b99" +dependencies = [ + "bstr", + "gix-chunk", + "gix-features", + "gix-hash", + "memmap2", + "thiserror", +] + +[[package]] +name = "gix-config" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b51e83bd9d08118e0ff06ea14be953c418b4c056e57d93c8103e777584e48b0a" +dependencies = [ + "bstr", + "gix-config-value", + "gix-features", + "gix-glob", + "gix-path", + "gix-ref", + "gix-sec", + "log", + "memchr", + "nom", + "once_cell", + "smallvec", + "thiserror", + "unicode-bom", +] + +[[package]] +name = "gix-config-value" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e874f41437441c02991dcea76990b9058fadfc54b02ab4dd06ab2218af43897" +dependencies = [ + "bitflags 2.3.3", + "bstr", + "gix-path", + "libc", + "thiserror", +] + +[[package]] +name = "gix-credentials" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307d91ec5f7c8e9bfaa217fe30c2e0099101cbe83dbed27a222dbb6def38725f" +dependencies = [ + "bstr", + "gix-command", + "gix-config-value", + "gix-path", + "gix-prompt", + "gix-sec", + "gix-url", + "thiserror", +] + +[[package]] +name = "gix-date" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56b0312dba1ad003d9b8c502bed52fbcf106f8de3a9a26bfa7b45642a6f94b72" +dependencies = [ + "bstr", + "itoa", + "thiserror", + "time", +] + +[[package]] +name = "gix-diff" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49d7a9a9ed5ec3428c3061da45d0fc5f50b3c07b91ea4e7ec4959668f25f6c" +dependencies = [ + "gix-hash", + "gix-object", + "imara-diff", + "thiserror", +] + +[[package]] +name = "gix-discover" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "041480eb03d8aa0894d9b73d25d182d51bc4d0ea8925a6ee0c971262bbc7715e" +dependencies = [ + "bstr", + "dunce", + "gix-hash", + "gix-path", + "gix-ref", + "gix-sec", + "thiserror", +] + +[[package]] +name = "gix-features" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882695cccf38da4c3cc7ee687bdb412cf25e37932d7f8f2c306112ea712449f1" +dependencies = [ + "bytes", + "crc32fast", + "crossbeam-channel", + "flate2", + "gix-hash", + "gix-trace", + "jwalk", + "libc", + "once_cell", + "parking_lot", + "prodash", + "sha1_smol", + "thiserror", + "walkdir", +] + +[[package]] +name = "gix-filter" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4d4d61f2ab07de4612f8e078d7f1a443c7ab5c40f382784c8eacdf0fd172b9" +dependencies = [ + "bstr", + "encoding_rs", + "gix-attributes", + "gix-command", + "gix-hash", + "gix-object", + "gix-packetline-blocking", + "gix-path", + "gix-quote", + "gix-trace", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-fs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d5b6e9d34a2c61ea4a02bbca94c409ab6dbbca1348cbb67298cd7fed8758761" +dependencies = [ + "gix-features", +] + +[[package]] +name = "gix-glob" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c79b881a18d89a75876ba277476d5a2bad5b19f03759c7a07e0808dfe08212" +dependencies = [ + "bitflags 2.3.3", + "bstr", + "gix-features", + "gix-path", +] + +[[package]] +name = "gix-hash" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b422ff2ad9a0628baaad6da468cf05385bf3f5ab495ad5a33cce99b9f41092f" +dependencies = [ + "hex", + "thiserror", +] + +[[package]] +name = "gix-hashtable" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385f4ce6ecf3692d313ca3aa9bd3b3d8490de53368d6d94bedff3af8b6d9c58d" +dependencies = [ + "gix-hash", + "hashbrown 0.14.0", + "parking_lot", +] + +[[package]] +name = "gix-ignore" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88b95ceb3bc45abcab6eb55ef4e0053e58b4df0712d3f9aec7d0ca990952603" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "unicode-bom", +] + +[[package]] +name = "gix-index" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732f61ec71576bd443a3c24f4716dc7eac180d8929e7bb8603c7310161507106" +dependencies = [ + "bitflags 2.3.3", + "bstr", + "btoi", + "filetime", + "gix-bitmap", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-traverse", + "itoa", + "memmap2", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-lock" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e82ec23c8a281f91044bf3ed126063b91b59f9c9340bf0ae746f385cc85a6fa" +dependencies = [ + "gix-tempfile", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-mailmap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc0dbbf35d29639770af68d7ff55924d83786c8924b0e6a1766af1a98b7d58b" +dependencies = [ + "bstr", + "gix-actor", + "gix-date", + "thiserror", +] + +[[package]] +name = "gix-negotiate" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce0061b7ae867e830c77b1ecfc5875f0d042aebb3d7e6014d04fd86ca6c71d59" +dependencies = [ + "bitflags 2.3.3", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-object", + "gix-revwalk", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-object" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f18b688854af4440695b943e705877f94171325b8bcacaee2d898ecf2766d2" +dependencies = [ + "bstr", + "btoi", + "gix-actor", + "gix-date", + "gix-features", + "gix-hash", + "gix-validate", + "hex", + "itoa", + "nom", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-odb" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dc2129d25313b594adfb5a71f2a617aaa2c3c4cfd3b2943823692db12fbc1db" +dependencies = [ + "arc-swap", + "gix-date", + "gix-features", + "gix-hash", + "gix-object", + "gix-pack", + "gix-path", + "gix-quote", + "parking_lot", + "tempfile", + "thiserror", +] + +[[package]] +name = "gix-pack" +version = "0.40.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f029a4dce9ac91da35c968c3abdcae573b3e52c123be86cbab3011599de533" +dependencies = [ + "clru", + "gix-chunk", + "gix-diff", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-traverse", + "memmap2", + "parking_lot", + "smallvec", + "thiserror", + "uluru", +] + +[[package]] +name = "gix-packetline" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb532b34627186a9a2705a360f64f6a8feb34c42344b127f9f230687d85358bd" +dependencies = [ + "bstr", + "hex", + "thiserror", ] [[package]] -name = "fern" -version = "0.6.2" +name = "gix-packetline-blocking" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" +checksum = "20276373def40fc3be7a86d09e1bb607d33dd6bf83e3504e83cd594e51438667" dependencies = [ - "log", + "bstr", + "hex", + "thiserror", ] [[package]] -name = "ff" -version = "0.13.0" +name = "gix-path" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "18609c8cbec8508ea97c64938c33cd305b75dfc04a78d0c3b78b8b3fd618a77c" dependencies = [ - "rand_core", - "subtle", + "bstr", + "gix-trace", + "home", + "once_cell", + "thiserror", ] [[package]] -name = "fiat-crypto" -version = "0.1.20" +name = "gix-prompt" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +checksum = "2f755e8eb83ee9a06642a8fbd3009b033db2b5bd774f3aaf3de0b07f9b6ebdc5" +dependencies = [ + "gix-command", + "gix-config-value", + "parking_lot", + "rustix", + "thiserror", +] [[package]] -name = "filetime" -version = "0.2.20" +name = "gix-protocol" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" +checksum = "0f8cf8b48ad5510a6ea3c8b529f51fd0f31009a2e46579f3a0ed917669035170" dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.2.16", - "windows-sys 0.45.0", + "bstr", + "btoi", + "gix-credentials", + "gix-date", + "gix-features", + "gix-hash", + "gix-transport", + "maybe-async", + "nom", + "thiserror", ] [[package]] -name = "fixedbitset" -version = "0.4.2" +name = "gix-quote" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "dfd80d3d0c733508df9449b1d3795da36083807e31d851d7d61d29af13bd4b0a" +dependencies = [ + "bstr", + "btoi", + "thiserror", +] [[package]] -name = "flate2" -version = "1.0.25" +name = "gix-ref" +version = "0.33.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "c16776eab78f4a918464064fa9c0f640014e8f1c3f9d1d366e929251c7193b2c" dependencies = [ - "crc32fast", - "libz-sys", - "miniz_oxide", + "gix-actor", + "gix-date", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-validate", + "memmap2", + "nom", + "thiserror", ] [[package]] -name = "fnv" -version = "1.0.7" +name = "gix-refspec" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "d19a02bf740b326d6c082a7d6f754ebe56eef900986c5e91be7cf000df9ea18d" +dependencies = [ + "bstr", + "gix-hash", + "gix-revision", + "gix-validate", + "smallvec", + "thiserror", +] [[package]] -name = "foreign-types" -version = "0.3.2" +name = "gix-revision" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "38a13500890435e3b9e7746bceda248646bfc69e259210884c98e29bb7a1aa6f" dependencies = [ - "foreign-types-shared", + "bstr", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "thiserror", ] [[package]] -name = "foreign-types-shared" -version = "0.1.1" +name = "gix-revwalk" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "71d4cbaf3cfbfde2b81b5ee8b469aff42c34693ce0fe17fc3c244d5085307f2c" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec", + "thiserror", +] [[package]] -name = "form_urlencoded" -version = "1.1.0" +name = "gix-sec" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "9615cbd6b456898aeb942cd75e5810c382fbfc48dbbff2fa23ebd2d33dcbe9c7" dependencies = [ - "percent-encoding", + "bitflags 2.3.3", + "gix-path", + "libc", + "windows", ] [[package]] -name = "fs-err" -version = "2.9.0" +name = "gix-tempfile" +version = "7.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" +checksum = "fa28d567848cec8fdd77d36ad4f5f78ecfaba7d78f647d4f63c8ae1a2cec7243" +dependencies = [ + "gix-fs", + "libc", + "once_cell", + "parking_lot", + "signal-hook", + "signal-hook-registry", + "tempfile", +] [[package]] -name = "funty" -version = "2.0.0" +name = "gix-trace" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +checksum = "96b6d623a1152c3facb79067d6e2ecdae48130030cf27d6eb21109f13bd7b836" [[package]] -name = "fwdansi" -version = "1.1.0" +name = "gix-transport" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c1f5787fe85505d1f7777268db5103d80a7a374d2316a7ce262e57baf8f208" +checksum = "99fffe5a95a522200ad47b97c19c9b0c204fc415dffa4a993d727394e6d59ef8" dependencies = [ - "memchr", - "termcolor", + "base64", + "bstr", + "gix-command", + "gix-credentials", + "gix-features", + "gix-packetline", + "gix-quote", + "gix-sec", + "gix-url", + "reqwest", + "thiserror", ] [[package]] -name = "generic-array" -version = "0.14.7" +name = "gix-traverse" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "e12e0fe428394226c37dd686ad64b09a04b569fe157d638b125b4a4c1e7e2df0" dependencies = [ - "typenum", - "version_check", - "zeroize", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "smallvec", + "thiserror", ] [[package]] -name = "getrandom" -version = "0.2.8" +name = "gix-url" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "4411bdbd1d46b35ae50e84c191660d437f89974e4236627785024be0b577170a" dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", + "bstr", + "gix-features", + "gix-path", + "home", + "thiserror", + "url", ] [[package]] -name = "git2" -version = "0.16.0" +name = "gix-utils" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be36bc9e0546df253c0cc41fd0af34f5e92845ad8509462ec76672fac6997f5b" +checksum = "b85d89dc728613e26e0ed952a19583744e7f5240fcd4aa30d6c824ffd8b52f0f" dependencies = [ - "bitflags", - "libc", - "libgit2-sys", - "log", - "openssl-probe", - "openssl-sys", - "url", + "fastrand", ] [[package]] -name = "git2-curl" -version = "0.17.0" +name = "gix-validate" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7577f4e6341ba7c90d883511130a45b956c274ba5f4d205d9f9da990f654cd33" +checksum = "ba9b3737b2cef3dcd014633485f0034b0f1a931ee54aeb7d8f87f177f3c89040" dependencies = [ - "curl", - "git2", - "log", - "url", + "bstr", + "thiserror", ] [[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "globset" -version = "0.4.10" +name = "gix-worktree" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" +checksum = "8fd60b32d15287aef77e4fb1955627e0db1f13e40bf9a3481100223a51791f5f" dependencies = [ - "aho-corasick", "bstr", - "fnv", - "log", - "regex", + "filetime", + "gix-attributes", + "gix-features", + "gix-filter", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", + "io-close", + "thiserror", ] [[package]] -name = "group" -version = "0.13.0" +name = "h2" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" dependencies = [ - "ff", - "rand_core", - "subtle", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 1.9.3", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] @@ -1029,122 +1510,150 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] -name = "heck" -version = "0.4.1" +name = "hashbrown" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" [[package]] -name = "hermit-abi" -version = "0.2.6" +name = "heck" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - -[[package]] -name = "hex" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "serde", + "windows-sys 0.48.0", ] [[package]] -name = "hkdf" -version = "0.12.3" +name = "hostname" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ - "hmac", + "libc", + "match_cfg", + "winapi", ] [[package]] -name = "hmac" -version = "0.12.1" +name = "http" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ - "digest", + "bytes", + "fnv", + "itoa", ] [[package]] -name = "home" -version = "0.5.4" +name = "http-body" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "winapi", + "bytes", + "http", + "pin-project-lite", ] [[package]] -name = "http-auth" -version = "0.1.8" +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5430cacd7a1f9a02fbeb350dfc81a0e5ed42d81f3398cb0ba184017f85bdcfbc" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ - "memchr", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.9", + "tokio", + "tower-service", + "tracing", + "want", ] [[package]] -name = "humantime" -version = "2.1.0" +name = "hyper-rustls" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] [[package]] name = "idna" -version = "0.3.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ + "matches", "unicode-bidi", "unicode-normalization", ] [[package]] -name = "ignore" -version = "0.4.20" +name = "idna" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ - "globset", - "lazy_static", - "log", - "memchr", - "regex", - "same-file", - "thread_local", - "walkdir", - "winapi-util", + "unicode-bidi", + "unicode-normalization", ] [[package]] -name = "im-rc" -version = "15.1.0" +name = "imara-diff" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" +checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8" dependencies = [ - "bitmaps", - "rand_core", - "rand_xoshiro", - "sized-chunks", - "typenum", - "version_check", + "ahash", + "hashbrown 0.12.3", ] [[package]] @@ -1154,14 +1663,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", ] [[package]] name = "insta" -version = "1.29.0" +version = "1.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a28d25139df397cbca21408bb742cf6837e04cdbebf1b07b760caf971d6a972" +checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a" dependencies = [ "console", "lazy_static", @@ -1172,51 +1691,49 @@ dependencies = [ ] [[package]] -name = "instant" -version = "0.1.12" +name = "io-close" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" dependencies = [ - "cfg-if", + "libc", + "winapi", ] [[package]] -name = "io-lifetimes" -version = "1.0.9" +name = "ipconfig" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.45.0", + "socket2 0.5.3", + "widestring", + "windows-sys 0.48.0", + "winreg 0.50.0", ] [[package]] -name = "is-terminal" -version = "0.4.6" +name = "ipnet" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" -dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys 0.45.0", -] +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] -name = "itertools" -version = "0.10.5" +name = "is-terminal" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "either", + "hermit-abi", + "rustix", + "windows-sys 0.48.0", ] [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "jobserver" @@ -1229,24 +1746,34 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2735847566356cd2179a2a38264839308f7079fa96e6bd5a42d740460e003c56" +dependencies = [ + "crossbeam", + "rayon", +] + [[package]] name = "krates" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3173dd882cbc450aff5dd932f13a61feb952bf4636741c502f67f9fa27090214" +checksum = "ce26ee40ae43555ff346b9807d4c85e86d8e39c26e19da2d9894829f073b341a" dependencies = [ "cargo_metadata", "cfg-expr", - "crates-index", "petgraph", "semver", + "tame-index", ] [[package]] @@ -1265,68 +1792,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "libc" -version = "0.2.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" - -[[package]] -name = "libgit2-sys" -version = "0.14.1+1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a07fb2692bc3593bda59de45a502bb3071659f2c515e28c71e728306b038e17" -dependencies = [ - "cc", - "libc", - "libssh2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", -] - -[[package]] -name = "libnghttp2-sys" -version = "0.1.7+1.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "libssh2-sys" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-sys" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1334,240 +1805,210 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.3.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" +checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" [[package]] -name = "log" -version = "0.4.17" +name = "lock_api" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ - "cfg-if", + "autocfg", + "scopeguard", ] [[package]] -name = "memchr" -version = "2.5.0" +name = "log" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] -name = "memoffset" -version = "0.8.0" +name = "lru-cache" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" dependencies = [ - "autocfg", + "linked-hash-map", ] [[package]] -name = "miniz_oxide" -version = "0.6.2" +name = "match_cfg" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] -name = "miow" -version = "0.5.0" +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "maybe-async" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ffbca2f655e33c08be35d87278e5b18b89550a37dbd598c20db92f6a471123" +checksum = "0f1b8c13cb1f814b634a96b2c725449fe7ed464a7b8781de8688be5ffbd3f305" dependencies = [ - "windows-sys 0.42.0", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "nu-ansi-term" -version = "0.47.0" +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memmap2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df031e117bca634c262e9bd3173776844b6c17a90b3741c9163663b4385af76" +checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" dependencies = [ - "windows-sys 0.45.0", + "libc", ] [[package]] -name = "num-traits" -version = "0.2.15" +name = "memoffset" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] -name = "num_cpus" -version = "1.15.0" +name = "mime" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" -dependencies = [ - "hermit-abi 0.2.6", - "libc", -] +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] -name = "once_cell" -version = "1.17.1" +name = "minimal-lexical" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] -name = "opener" -version = "0.5.2" +name = "miniz_oxide" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c15678e37254c15bd2f092314abb4e51d7fdde05c2021279c12631b54f005" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ - "bstr", - "winapi", + "adler", ] [[package]] -name = "openssl" -version = "0.10.49" +name = "mio" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d2f106ab837a24e03672c59b1239669a0596406ff657c3c0835b6b7f0f35a33" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", "libc", - "once_cell", - "openssl-macros", - "openssl-sys", + "wasi", + "windows-sys 0.48.0", ] [[package]] -name = "openssl-macros" -version = "0.1.1" +name = "nom" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.13", + "memchr", + "minimal-lexical", ] [[package]] -name = "openssl-probe" -version = "0.1.5" +name = "nu-ansi-term" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68" +dependencies = [ + "windows-sys 0.48.0", +] [[package]] -name = "openssl-src" -version = "111.25.2+1.1.1t" +name = "num-traits" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320708a054ad9b3bf314688b5db87cf4d6683d64cfc835e2337924ae62bf4431" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ - "cc", + "autocfg", ] [[package]] -name = "openssl-sys" -version = "0.9.84" +name = "num_cpus" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a20eace9dc2d82904039cb76dcf50fb1a0bba071cfd1629720b5d6f1ddba0fa" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "cc", + "hermit-abi", "libc", - "openssl-src", - "pkg-config", - "vcpkg", ] [[package]] -name = "ordered-float" -version = "2.10.0" +name = "num_threads" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ - "num-traits", + "libc", ] [[package]] -name = "orion" -version = "0.17.4" +name = "object" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbe74a766292f94f7e69db5a7bf010eadd944f24186c463fe578a7e637582066" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" dependencies = [ - "fiat-crypto", - "subtle", - "zeroize", + "memchr", ] [[package]] -name = "os_info" -version = "3.7.0" +name = "once_cell" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" -dependencies = [ - "log", - "serde", - "winapi", -] +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] -name = "p384" -version = "0.13.0" +name = "parking_lot" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", + "lock_api", + "parking_lot_core", ] [[package]] -name = "pasetors" -version = "0.6.6" +name = "parking_lot_core" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824bf633b85dc1dece2eb07161627ba5d90a951597cd5dbf8d85f4d82b7aea69" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ - "ct-codecs", - "ed25519-compact", - "getrandom", - "orion", - "p384", - "rand_core", - "regex", - "serde", - "serde_json", - "sha2", - "subtle", - "time", - "zeroize", + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "smallvec", + "windows-targets 0.48.1", ] [[package]] name = "paste" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" - -[[package]] -name = "pathdiff" -version = "0.2.1" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "petgraph" @@ -1576,24 +2017,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 1.9.3", ] [[package]] -name = "pkcs8" -version = "0.10.1" +name = "pin-project-lite" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d2820d87d2b008616e5c27212dd9e0e694fb4c6b522de06094106813328cb49" -dependencies = [ - "der", - "spki", -] +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "platforms" @@ -1605,28 +2048,37 @@ dependencies = [ ] [[package]] -name = "primeorder" -version = "0.13.0" +name = "ppv-lite86" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7613fdcc0831c10060fa69833ea8fa2caa94b6456f51e25356a885b530a2e3d0" -dependencies = [ - "elliptic-curve", -] +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] +[[package]] +name = "prodash" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c236e70b7f9b9ea00d33c69f63ec1ae6e9ae96118923cd37bd4e9c7396f0b107" + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" -version = "1.0.26" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -1638,23 +2090,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] -name = "rand_core" -version = "0.6.4" +name = "rand" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "getrandom", + "libc", + "rand_chacha", + "rand_core", ] [[package]] -name = "rand_xoshiro" -version = "0.6.0" +name = "rand_chacha" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ + "ppv-lite86", "rand_core", ] +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rayon" version = "1.7.0" @@ -1683,7 +2147,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1692,47 +2156,110 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.7.3" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" dependencies = [ "aho-corasick", "memchr", + "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.1.10" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] -name = "rfc6979" -version = "0.4.0" +name = "reqwest" +version = "0.11.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +dependencies = [ + "async-compression", + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls", + "tokio-util", + "tower-service", + "trust-dns-resolver", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg 0.10.1", +] + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", +] + +[[package]] +name = "ring" +version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ - "hmac", - "subtle", + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", ] [[package]] name = "rmp" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" +checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" dependencies = [ "byteorder", "num-traits", @@ -1751,48 +2278,60 @@ dependencies = [ ] [[package]] -name = "rustc-hash" -version = "1.1.0" +name = "rustc-demangle" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] -name = "rustc-workspace-hack" -version = "1.0.0" +name = "rustix" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb" +checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +dependencies = [ + "bitflags 2.3.3", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] [[package]] -name = "rustfix" -version = "0.6.1" +name = "rustls" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd2853d9e26988467753bd9912c3a126f642d05d229a4b53f5752ee36c56481" +checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" dependencies = [ - "anyhow", "log", - "serde", - "serde_json", + "ring", + "rustls-webpki", + "sct", ] [[package]] -name = "rustix" -version = "0.37.7" +name = "rustls-pemfile" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys 0.45.0", + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e" +dependencies = [ + "ring", + "untrusted", ] [[package]] name = "rustsec" -version = "0.26.5" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252facd5756861013b3dbad84b0568036a4310014311f064e56b214ccc4364db" +checksum = "cabcfea780f9b2f75bc2eaae946621fa42cb18189f41cda4dee608ea73a9c397" dependencies = [ "cargo-lock", "cvss", @@ -1801,21 +2340,21 @@ dependencies = [ "semver", "serde", "thiserror", - "toml 0.7.3", + "toml", "url", ] [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" @@ -1826,88 +2365,56 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" -dependencies = [ - "windows-sys 0.42.0", -] - [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "sec1" -version = "0.7.1" +name = "sct" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48518a2b5775ba8ca5b46596aae011caa431e6ce7e4a67ead66d92f08884220e" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", + "ring", + "untrusted", ] [[package]] name = "semver" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.159" +version = "1.0.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b" dependencies = [ "serde_derive", ] -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float", - "serde", -] - [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", -] - -[[package]] -name = "serde_ignored" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94eb4a4087ba8bdf14a9208ac44fddbf55c01a6195f7edfc511ddaff6cae45a6" -dependencies = [ - "serde", + "syn 2.0.27", ] [[package]] name = "serde_json" -version = "1.0.95" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" dependencies = [ "itoa", "ryu", @@ -1916,29 +2423,36 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" dependencies = [ "serde", ] [[package]] -name = "sha1" -version = "0.10.5" +name = "serde_urlencoded" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ - "cfg-if", - "cpufeatures", - "digest", + "form_urlencoded", + "itoa", + "ryu", + "serde", ] +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", @@ -1946,19 +2460,22 @@ dependencies = [ ] [[package]] -name = "shell-escape" -version = "0.1.5" +name = "signal-hook" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] [[package]] -name = "signature" -version = "2.0.0" +name = "signal-hook-registry" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ - "digest", - "rand_core", + "libc", ] [[package]] @@ -1968,58 +2485,63 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" [[package]] -name = "sized-chunks" -version = "0.6.5" +name = "slab" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ - "bitmaps", - "typenum", + "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "smol_str" -version = "0.1.24" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" dependencies = [ "serde", ] [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" dependencies = [ "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] name = "spdx" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52dd48832ddda0d79ca6062064d530680e24c5ee85ba1d9fae41f102b2d9f34f" +checksum = "b19b32ed6d899ab23174302ff105c1577e45a06b08d4fe0a9dd13ce804bbbf71" dependencies = [ "smallvec", ] [[package]] -name = "spki" -version = "0.7.0" +name = "spin" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0445c905640145c7ea8c1993555957f65e7c46d0535b91ba501bc9bfc85522f" -dependencies = [ - "base64ct", - "der", -] +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "static_assertions" @@ -2027,15 +2549,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strip-ansi-escapes" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8" -dependencies = [ - "vte", -] - [[package]] name = "strsim" version = "0.10.0" @@ -2044,32 +2557,26 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.24.3" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "6069ca09d878a33f883cc06aaa9718ede171841d3832450354410b718b097232" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 1.0.109", + "syn 2.0.27", ] -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - [[package]] name = "syn" version = "1.0.109" @@ -2083,9 +2590,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" dependencies = [ "proc-macro2", "quote", @@ -2093,38 +2600,51 @@ dependencies = [ ] [[package]] -name = "tap" -version = "1.0.1" +name = "tame-index" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +checksum = "bc228c36e24e0e5d03623ebc2dc2a66b45f7b840dd53429d92fd632c157d7ee3" +dependencies = [ + "bytes", + "camino", + "gix", + "home", + "http", + "memchr", + "reqwest", + "semver", + "serde", + "serde_json", + "sha2", + "smol_str", + "thiserror", + "toml", + "twox-hash", +] [[package]] -name = "tar" -version = "0.4.38" +name = "tap" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" -dependencies = [ - "filetime", - "libc", -] +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.6" +version = "0.12.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae9980cab1db3fceee2f6c6f643d5d8de2997c58ee8d25fb0cc8a9e9e7348e5" +checksum = "1d2faeef5759ab89935255b1a4cd98e0baf99d1085e37d36599c625dac49ae8e" [[package]] name = "tempfile" -version = "3.5.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" dependencies = [ "cfg-if", "fastrand", "redox_syscall 0.3.5", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -2138,41 +2658,33 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", -] - -[[package]] -name = "thread_local" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" -dependencies = [ - "cfg-if", - "once_cell", + "syn 2.0.27", ] [[package]] name = "time" -version = "0.3.20" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" dependencies = [ "itoa", + "libc", + "num_threads", "serde", "time-core", "time-macros", @@ -2180,15 +2692,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" dependencies = [ "time-core", ] @@ -2209,71 +2721,169 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] -name = "toml" -version = "0.5.11" +name = "tokio" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" dependencies = [ - "serde", + "autocfg", + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2 0.4.9", + "windows-sys 0.48.0", ] [[package]] -name = "toml" -version = "0.7.3" +name = "tokio-rustls" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "serde", - "serde_spanned", - "toml_datetime 0.6.1", - "toml_edit 0.19.8", + "rustls", + "tokio", ] [[package]] -name = "toml_datetime" -version = "0.5.1" +name = "tokio-util" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ - "serde", + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", ] [[package]] -name = "toml_datetime" -version = "0.6.1" +name = "toml" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" dependencies = [ "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", ] [[package]] -name = "toml_edit" -version = "0.15.0" +name = "toml_datetime" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1541ba70885967e662f69d31ab3aeca7b1aaecfcd58679590b893e9239c3646" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ - "combine", - "indexmap", - "itertools", - "kstring", "serde", - "toml_datetime 0.5.1", ] [[package]] name = "toml_edit" -version = "0.19.8" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap", + "indexmap 2.0.0", "serde", "serde_spanned", - "toml_datetime 0.6.1", + "toml_datetime", "winnow", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.27", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "trust-dns-proto" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.2.3", + "ipnet", + "lazy_static", + "rand", + "smallvec", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" +dependencies = [ + "cfg-if", + "futures-util", + "ipconfig", + "lazy_static", + "lru-cache", + "parking_lot", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "tracing", + "trust-dns-proto", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "twox-hash" version = "1.6.3" @@ -2290,17 +2900,32 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "uluru" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794a32261a1f5eb6a4462c81b59cec87b5c27d5deea7dd1ac8fc781c41d226db" +dependencies = [ + "arrayvec", +] + [[package]] name = "unicode-bidi" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +[[package]] +name = "unicode-bom" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98e90c70c9f0d4d1ee6d0a7d04aa06cb9bbd53d8cfbdd62a0269a7c2eb640552" + [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -2318,19 +2943,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] -name = "unicode-xid" -version = "0.2.4" +name = "untrusted" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", - "idna", + "idna 0.4.0", "percent-encoding", "serde", ] @@ -2341,39 +2966,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "vte" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" -dependencies = [ - "arrayvec", - "utf8parse", - "vte_generate_state_changes", -] - -[[package]] -name = "vte_generate_state_changes" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" -dependencies = [ - "proc-macro2", - "quote", -] - [[package]] name = "walkdir" version = "2.3.3" @@ -2384,6 +2982,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2392,9 +2999,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2402,24 +3009,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.27", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2427,22 +3046,57 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.27", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "widestring" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" [[package]] name = "winapi" @@ -2476,18 +3130,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.42.0" +name = "windows" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows-targets 0.48.1", ] [[package]] @@ -2496,7 +3144,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.1", ] [[package]] @@ -2505,13 +3162,28 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] @@ -2520,51 +3192,112 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "winnow" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +checksum = "25b5872fa2e10bd067ae946f927e726d7d603eaeb6e02fa6a350e0722d2b8c11" dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wyz" version = "0.5.1" @@ -2583,12 +3316,6 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "zeroize" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" - [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" @@ -2610,9 +3337,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.7+zstd.1.5.4" +version = "2.0.8+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94509c3ba2fe55294d752b79842c530ccfab760192521df74a081a78d2b3c7f5" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" dependencies = [ "cc", "libc", diff --git a/Cargo.toml b/Cargo.toml index b640422a..d7cab18f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ homepage = "https://github.com/EmbarkStudios/cargo-deny" categories = ["development-tools::cargo-plugins"] keywords = ["cargo", "license", "spdx", "ci", "advisories"] exclude = ["examples/", ".github/", "tests"] -rust-version = "1.65.0" +rust-version = "1.70.0" [badges] maintenance = { status = "actively-developed" } @@ -25,58 +25,50 @@ name = "cargo-deny" path = "src/cargo-deny/main.rs" [features] -default = ["vendored-openssl", "vendored-libgit2"] +#default = ["vendored-openssl", "vendored-libgit2"] # Allows the use of a vendored version openssl when compiling libgit, which allows # us to compile static executables (eg musl) and avoid system dependencies -vendored-openssl = [ - "cargo?/vendored-openssl", - "crates-index/vendored-openssl", - "git2/vendored-openssl", -] -vendored-libgit2 = ["cargo?/vendored-libgit2", "git2/vendored-libgit2"] +#vendored-openssl = ["cargo?/vendored-openssl", "git2/vendored-openssl"] +#vendored-libgit2 = ["cargo?/vendored-libgit2", "git2/vendored-libgit2"] # Allows embedding cargo as a library so that we can run in minimal (eg container) # environments that don't need to have cargo/rust installed on them for cargo-deny # to still function -standalone = ["cargo"] +#standalone = ["cargo"] [dependencies] # Output coloring -nu-ansi-term = "0.47" +nu-ansi-term = "0.49" # Easy errors anyhow = "1.0" # Used for detecting the license type of a file askalono = "0.4" -# Used to detect if an output stream is a TTY to control default coloring -is-terminal = "0.4.6" # Used to track various things during check runs bitvec = { version = "1.0", features = ["alloc"] } +# Much nicer paths +camino = "1.1" # Allows us to do eg cargo metadata operations without relying on an external cargo -cargo = { version = "0.69", optional = true } +#cargo = { version = "0.71", optional = true } # Argument parsing, kept aligned with cargo clap = { version = "4.0", features = ["derive", "env"] } # Used for diagnostic reporting codespan = "0.11" codespan-reporting = "0.11" -# Fetching and reading of crates.io (or other indices) -crates-index = { version = "0.19", default-features = false, features = [ - "parallel", -] } # Brrrrr crossbeam = "0.8" # Logging utilities fern = "0.6" -# We directly interact with git when doing index operations eg during fix -git2 = "0.16" # We need to figure out HOME/CARGO_HOME in some cases home = "0.5" # Provides graphs on top of cargo_metadata -krates = { version = "0.13", features = ["prefer-index", "targets"] } +krates = { version = "0.14", features = ["prefer-index", "targets"] } # Log macros log = "0.4" +# Nicer sync primitives +parking_lot = "0.12" # Moar brrrr rayon = "1.4" # Used for interacting with advisory databases -rustsec = { version = "0.26", default-features = false } +rustsec = { version = "0.27", default-features = false } # Parsing and checking of versions/version requirements semver = "1.0" # Gee what could it be @@ -87,7 +79,9 @@ smallvec = "1.9" # Used for parsing and checking SPDX license expressions spdx = "0.10" # Lazy -strum = { version = "0.24", features = ["derive"] } +strum = { version = "0.25", features = ["derive"] } +# Index retrieval and querying +tame-index = { version = "0.2", features = ["git", "sparse"] } # Timestamp emission time = { version = "0.3", default-features = false, features = [ "formatting", @@ -100,8 +94,27 @@ twox-hash = { version = "1.5", default-features = false } # Url parsing/manipulation url = "2.1" +# We clone/fetch advisory databases +[dependencies.gix] +version = "0.50" +default-features = false +features = [ + "blocking-http-transport-reqwest-rust-tls", + "blocking-network-client", + "max-performance-safe", +] + [dev-dependencies] +# Folder copying +fs_extra = "1.3" # Snapshot testing insta = { version = "1.21", features = ["json"] } +tame-index = { version = "0.2", features = ["local-builder"] } # We use this for creating fake crate directories for crawling license files on disk tempfile = "3.1.0" + +[profile.dev.package.insta] +opt-level = 3 + +[profile.dev.package.similar] +opt-level = 3 diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000..da6189cb --- /dev/null +++ b/clippy.toml @@ -0,0 +1,19 @@ +msrv = "1.70.0" + +disallowed-types = [ + { path = "std::sync::Mutex", reason = "Use the faster & simpler non-poisonable primitives in `parking_lot` instead" }, + { path = "std::sync::RwLock", reason = "Use the faster & simpler non-poisonable primitives in `parking_lot` instead" }, + { path = "std::sync::Condvar", reason = "Use the faster & simpler non-poisonable primitives in `parking_lot` instead" }, + { path = "std::sync::Once", reason = "Use the faster & simpler non-poisonable primitives in `parking_lot` instead" }, + + { path = "std::sync::mpsc::Sender", reason = "Use the non-legacy and faster `crossbeam_channel` crate instead of `std::sync::mpsc`" }, + { path = "std::sync::mpsc::SyncSender", reason = "Use the non-legacy and faster `crossbeam_channel` crate instead of `std::sync::mpsc`" }, + { path = "std::sync::mpsc::Receiver", reason = "Use the non-legacy and faster `crossbeam_channel` crate instead of `std::sync::mpsc`" }, + + { path = "std::path::Path", reason = "Use cargo_deny::Path" }, + { path = "std::path::PathBuf", reason = "Use cargo_deny::PathBuf" }, + + { path = "std::collections::LinkedList", reason = "LinkedList as a slow and almost never needed" }, + + { path = "ring::digest::SHA1_FOR_LEGACY_USE_ONLY", reason = "SHA-1 is cryptographically broken, and we are building new code so should not use it" }, +] diff --git a/deny.toml b/deny.toml index 0982b3b9..237a42fb 100644 --- a/deny.toml +++ b/deny.toml @@ -17,33 +17,43 @@ ignore = [ # rmp-serde used by askalono for the cache files, these are always utf-8 so # the advisory is not relevant "RUSTSEC-2022-0092", - # This is kind of relevant since cargo-deny can do git operations via SSH - # from cargo, but cargo needs to update before we can - "RUSTSEC-2023-0003", ] [bans] multiple-versions = "deny" -deny = [] +deny = [ + # We use gix + { name = "git2" }, + # NEVER AGAIN + { name = "openssl" }, + { name = "openssl-sys" }, + { name = "libssh2-sys" }, +] skip = [ - # cargo depends on two versions of these crates - { name = "hex", version = "=0.3.2" }, - # cargo-lock uses this older version - { name = "toml", version = "=0.5.11" }, + # indexmap + imara-diff + { name = "hashbrown", version = "=0.12.3" }, + # trust-dns-proto uses an old version + { name = "idna", version = "=0.2.3" }, + # petgraph + h2 use an old version + { name = "indexmap", version = "=1.9.3" }, + # hyper/tokio uses an old version + { name = "socket2", version = "=0.4.9" }, + # strum_macros + maybe-async + { name = "syn", version = "=1.0.109" }, + # reqwest uses an old version + { name = "winreg", version = "=0.10.1" }, ] skip-tree = [ # Sigh - { name = "windows-sys", version = "=0.42" }, - # cargo uses an old version - { name = "toml_edit", version = "=0.15" }, + { name = "windows-sys", version = "<=0.45" }, ] [sources] unknown-registry = "deny" unknown-git = "deny" -[sources.allow-org] -github = ["EmbarkStudios"] +# [sources.allow-org] +# github = ["EmbarkStudios"] [licenses] unlicensed = "deny" @@ -51,10 +61,40 @@ allow-osi-fsf-free = "neither" copyleft = "deny" # We want really high confidence when inferring licenses from text confidence-threshold = 0.93 -allow = ["Apache-2.0", "Apache-2.0 WITH LLVM-exception", "MIT", "MPL-2.0"] - +allow = [ + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "MIT", + "MPL-2.0", + "BSD-3-Clause", + "ISC", +] exceptions = [ { allow = ["Zlib"], name = "tinyvec" }, { allow = ["Unicode-DFS-2016"], name = "unicode-ident" }, - { allow = ["BSD-3-Clause"], name = "subtle" }, + { allow = ["OpenSSL"], name = "ring" }, ] + +# Sigh +[[licenses.clarify]] +name = "ring" +# SPDX considers OpenSSL to encompass both the OpenSSL and SSLeay licenses +# https://spdx.org/licenses/OpenSSL.html +# ISC - Both BoringSSL and ring use this for their new files +# MIT - "Files in third_party/ have their own licenses, as described therein. The MIT +# license, for third_party/fiat, which, unlike other third_party directories, is +# compiled into non-test libraries, is included below." +# OpenSSL - Obviously +expression = "ISC AND MIT AND OpenSSL" +license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] + +[[licenses.clarify]] +name = "webpki" +expression = "ISC" +license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] + +# Actually "ISC-style" +[[licenses.clarify]] +name = "rustls-webpki" +expression = "ISC" +license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] diff --git a/examples/06_advisories/Cargo.lock b/examples/06_advisories/Cargo.lock index c9a47fcd..f65adb57 100644 --- a/examples/06_advisories/Cargo.lock +++ b/examples/06_advisories/Cargo.lock @@ -28,7 +28,6 @@ dependencies = [ "failure", "lettre", "libusb", - "spdx", "trust-dns-resolver", ] @@ -1378,17 +1377,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "spdx" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d3449a25089fd6f99b089e62c385d5d4adeaf3defedc35a67d540e7df44c5fb" -dependencies = [ - "lazy_static 1.4.0", - "regex", - "smallvec", -] - [[package]] name = "string_cache" version = "0.6.2" diff --git a/examples/06_advisories/Cargo.toml b/examples/06_advisories/Cargo.toml index f36e03c5..e6f0d925 100644 --- a/examples/06_advisories/Cargo.toml +++ b/examples/06_advisories/Cargo.toml @@ -28,9 +28,6 @@ failure = "=0.1.8" # https://github.com/RustSec/advisory-db/blob/5b35b71cf74eed58696aeeb5a764a9f0a66fe7ba/crates/libusb/RUSTSEC-2016-0004.toml libusb = "0.3.0" -# This version of spdx has been yanked -spdx = "=0.3.1" - # The advisory applies to 0.10.0-alpha.1 >= && < 0.10.0-alpha.4 # https://github.com/RustSec/advisory-db/blob/c71cfec8c3fe313c9445a9ab0ae9b7faedda850a/crates/lettre/RUSTSEC-2020-0069.md lettre = "0.10.0-alpha.3" diff --git a/examples/12_yank_check/.cargo/config.toml b/examples/12_yank_check/.cargo/config.toml new file mode 100644 index 00000000..44a4519a --- /dev/null +++ b/examples/12_yank_check/.cargo/config.toml @@ -0,0 +1,3 @@ +[registries] +embark-deny = { index = "https://github.com/EmbarkStudios/cargo-test-index" } +embark-deny-sparse = { index = "sparse+https://cargo.cloudsmith.io/embark/deny/" } diff --git a/examples/12_yank_check/Cargo.lock b/examples/12_yank_check/Cargo.lock new file mode 100644 index 00000000..ab4d8ba8 --- /dev/null +++ b/examples/12_yank_check/Cargo.lock @@ -0,0 +1,62 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "crate-one" +version = "0.1.0" +source = "sparse+https://cargo.cloudsmith.io/embark/deny/" +checksum = "a12b64b5462f14585186b282beaa160bd9b1bbcce9cf42fcace3ba20dc7ef88d" + +[[package]] +name = "crate-two" +version = "0.1.0" +source = "registry+https://github.com/EmbarkStudios/cargo-test-index" +checksum = "d506c511495d1a0ded8cf08599097406a64afdf5b120281f628aa297ce1a0815" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "spdx" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d3449a25089fd6f99b089e62c385d5d4adeaf3defedc35a67d540e7df44c5fb" +dependencies = [ + "lazy_static", + "regex", + "smallvec", +] + +[[package]] +name = "yank-check" +version = "0.1.0" +dependencies = [ + "crate-one", + "crate-two", + "spdx", +] diff --git a/examples/12_yank_check/Cargo.toml b/examples/12_yank_check/Cargo.toml new file mode 100644 index 00000000..16ee9056 --- /dev/null +++ b/examples/12_yank_check/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "yank-check" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +# This crate has exactly one dependency and no transitive dependencies, we just +# use it to have a crate from crates.io to test that it works in combination with +# another registry +spdx = "=0.3.1" + +# This version is yanked +crate-two = { version = "=0.1.0", registry = "embark-deny" } +# This one is not +crate-one = { version = "=0.1.0", registry = "embark-deny-sparse" } diff --git a/examples/12_yank_check/src/lib.rs b/examples/12_yank_check/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/12_yank_check/src/lib.rs @@ -0,0 +1 @@ + diff --git a/src/advisories.rs b/src/advisories.rs index 875b0890..e82c8ec7 100644 --- a/src/advisories.rs +++ b/src/advisories.rs @@ -1,11 +1,13 @@ pub mod cfg; -mod diags; +pub(crate) mod diags; mod helpers; use crate::{diag, LintLevel}; pub use diags::Code; -use helpers::*; -pub use helpers::{DbSet, Fetch, Report}; +pub use helpers::{ + db::{AdvisoryDb, DbSet, Fetch, Id, Report}, + index::Indices, +}; pub trait AuditReporter { fn report(&mut self, report: serde_json::Value); @@ -32,6 +34,7 @@ pub fn check( ctx: crate::CheckCtx<'_, cfg::ValidConfig>, advisory_dbs: &DbSet, audit_compatible_reporter: Option, + indices: Option>, sink: S, ) where R: AuditReporter, @@ -43,24 +46,26 @@ pub fn check( let (report, yanked) = rayon::join( || Report::generate(advisory_dbs, ctx.krates, emit_audit_compatible_reports), || { - let indices = Indices::load(ctx.krates, ctx.cfg.crates_io_git_fallback); - - let yanked: Vec<_> = ctx - .krates - .krates() - .filter_map(|package| match indices.is_yanked(package) { - Ok(is_yanked) => { - if is_yanked { - Some((package, None)) - } else { - None + if let Some(indices) = indices { + let yanked: Vec<_> = ctx + .krates + .krates() + .filter_map(|package| match indices.is_yanked(package) { + Ok(is_yanked) => { + if is_yanked { + Some((package, None)) + } else { + None + } } - } - Err(err) => Some((package, Some(err))), - }) - .collect(); + Err(err) => Some((package, Some(err))), + }) + .collect(); - yanked + yanked + } else { + Vec::new() + } }, ); diff --git a/src/advisories/cfg.rs b/src/advisories/cfg.rs index 545610a1..6725143f 100644 --- a/src/advisories/cfg.rs +++ b/src/advisories/cfg.rs @@ -1,24 +1,26 @@ use crate::{ diag::{Diagnostic, FileId, Label}, - LintLevel, Spanned, + LintLevel, PathBuf, Spanned, }; use rustsec::advisory; use serde::Deserialize; -use std::path::PathBuf; use url::Url; #[allow(clippy::reversed_empty_ranges)] -fn yanked() -> Spanned { +const fn yanked() -> Spanned { Spanned::new(LintLevel::Warn, 0..0) } +#[allow(clippy::reversed_empty_ranges)] +fn ninety_days() -> Spanned { + Spanned::new("P90D".to_owned(), 0..0) +} + #[derive(Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct Config { /// Path to the root directory where advisory databases are stored (default: $CARGO_HOME/advisory-dbs) pub db_path: Option, - /// URL to the advisory database's git repo (default: https://github.com/RustSec/advisory-db) - pub db_url: Option>, /// List of urls to git repositories of different advisory databases. #[serde(default)] pub db_urls: Vec>, @@ -45,18 +47,26 @@ pub struct Config { /// Vulnerabilities with explicit CVSS info which have a severity below /// this threshold will be ignored. pub severity_threshold: Option, - /// Use the git executable to fetch advisory database + /// Use the git executable to fetch advisory database rather than gitoxide pub git_fetch_with_cli: Option, - /// If this is specified we fallback to retrieving the crates.io git index - /// if we are unable to open the crates.io sparse index - pub crates_io_git_fallback: Option, + /// If set to true, the local crates indices are not checked for yanked crates + #[serde(default)] + pub disable_yank_checking: bool, + /// The maximum duration, in RFC3339 format, that an advisory database is + /// allowed to not have been updated. This only applies when fetching advisory + /// databases has been disabled. Defaults to 90 days. + /// + /// Note that if fractional units are used in the format string they must + /// use the '.' separator instead of ',' which is used by some locales and + /// supported in the RFC3339 format, but not by this implementation + #[serde(default = "ninety_days")] + pub maximum_db_staleness: Spanned, } impl Default for Config { fn default() -> Self { Self { db_path: None, - db_url: None, db_urls: Vec::new(), ignore: Vec::new(), vulnerability: LintLevel::Deny, @@ -66,7 +76,8 @@ impl Default for Config { notice: LintLevel::Warn, severity_threshold: None, git_fetch_with_cli: None, - crates_io_git_fallback: None, + disable_yank_checking: false, + maximum_db_staleness: ninety_days(), } } } @@ -90,21 +101,6 @@ impl crate::cfg::UnvalidatedConfig for Config { }) .collect(); - if let Some(db_url) = self.db_url { - diags.push( - Diagnostic::warning() - .with_message("'db_url' is deprecated, use 'db_urls' instead") - .with_labels(vec![Label::primary(cfg_file, db_url.span.clone())]), - ); - - match crate::cfg::parse_url(cfg_file, db_url) { - Ok(url) => db_urls.push(url), - Err(diag) => { - diags.push(diag); - } - } - } - db_urls.sort(); // Warn about duplicates before removing them so the user can cleanup their config @@ -136,6 +132,23 @@ impl crate::cfg::UnvalidatedConfig for Config { } } + let maximum_db_staleness = match parse_rfc3339_duration(&self.maximum_db_staleness.value) { + Ok(mds) => mds, + Err(err) => { + diags.push( + Diagnostic::error() + .with_message("failed to parse RFC3339 duration") + .with_labels(vec![Label::secondary( + cfg_file, + self.maximum_db_staleness.span.clone(), + )]) + .with_notes(vec![err.to_string()]), + ); + // Use the 90 days default as a fallback + time::Duration::seconds_f64(90. * 24. * 60. * 60. * 60.) + } + }; + ValidConfig { file_id: cfg_file, db_path: self.db_path, @@ -148,7 +161,8 @@ impl crate::cfg::UnvalidatedConfig for Config { notice: self.notice, severity_threshold: self.severity_threshold, git_fetch_with_cli: self.git_fetch_with_cli.unwrap_or_default(), - crates_io_git_fallback: self.crates_io_git_fallback.unwrap_or(true), + disable_yank_checking: self.disable_yank_checking, + maximum_db_staleness, } } } @@ -167,14 +181,168 @@ pub struct ValidConfig { pub notice: LintLevel, pub severity_threshold: Option, pub git_fetch_with_cli: bool, - pub crates_io_git_fallback: bool, + pub disable_yank_checking: bool, + pub maximum_db_staleness: time::Duration, +} + +/// We need to implement this ourselves since time doesn't support it +/// +/// +/// ```text +/// dur-second = 1*DIGIT "S" +/// dur-minute = 1*DIGIT "M" [dur-second] +/// dur-hour = 1*DIGIT "H" [dur-minute] +/// dur-time = "T" (dur-hour / dur-minute / dur-second) +/// dur-day = 1*DIGIT "D" +/// dur-week = 1*DIGIT "W" +/// dur-month = 1*DIGIT "M" [dur-day] +/// dur-year = 1*DIGIT "Y" [dur-month] +/// dur-date = (dur-day / dur-month / dur-year) [dur-time] +/// +/// duration = "P" (dur-date / dur-time / dur-week) +/// ``` +fn parse_rfc3339_duration(value: &str) -> anyhow::Result { + use anyhow::Context as _; + + let mut value = value + .strip_prefix('P') + .context("duration requires 'P' prefix")?; + + // The units that are allowed in the format, in the exact order they must be + // in, ie it is invalid to specify a unit that is lower in this order than + // one that has already been parsed + const UNITS: &[(char, f64)] = &[ + ('D', 24. * 60. * 60.), + // We calculate the length of the month by just getting the mean of all + // the months, and use 28.25 for February + ('M', 30.43 * 24. * 60. * 60.), + // Years we just use the standard 365 days and ignore leap years + ('Y', 365. * 24. * 60. * 60.), + ('W', 7. * 24. * 60. * 60.), + ('H', 60. * 60.), + ('M', 60.), + ('S', 1.), + ('W', 7. * 24. * 60. * 60.), + ]; + + // Validate the string only contains valid characters to simplify the rest + // of the function + for c in value.chars() { + if c == ',' { + anyhow::bail!("'{c}' is valid in the RFC-3339 duration format but not supported by this implementation, use '.' instead"); + } + + if c != '.' && c != 'T' && !c.is_ascii_digit() && !UNITS.iter().any(|(uc, _)| c == *uc) { + anyhow::bail!("'{c}' is not valid in the RFC-3339 duration format"); + } + } + + #[derive(Copy, Clone, PartialEq, PartialOrd)] + enum Unit { + Empty, + Day, + Month, + Year, + Time, + Hour, + Minute, + Second, + Week, + } + + impl Unit { + #[inline] + fn from(c: char, is_time: bool) -> Self { + match c { + 'D' => Self::Day, + 'T' => Self::Time, + 'H' => Self::Hour, + 'M' => { + if is_time { + Self::Minute + } else { + Self::Month + } + } + 'S' => Self::Second, + 'Y' => Self::Year, + 'W' => Self::Week, + other => unreachable!("'{other}' should be impossible"), + } + } + } + + let mut duration = time::Duration::new(0, 0); + + // The format requires that the units are in a specific order, but each + // unit is optional + let mut last_unit = Unit::Empty; + let mut last_unitc = '_'; + let mut supplied_units = 0; + // According to the spec, the T is required before any hour/minute/second units + // are allowed + let mut is_time = false; + + while !value.is_empty() { + let unit_index = value + .find(|c: char| c.is_ascii_uppercase()) + .context("unit not specified")?; + + let unitc = value.as_bytes()[unit_index] as char; + let unit = Unit::from(unitc, is_time); + + anyhow::ensure!( + unit > last_unit, + "unit '{unitc}' cannot follow '{last_unitc}'" + ); + + if unit == Unit::Time { + anyhow::ensure!( + unit_index == 0, + "unit not specified for value '{}'", + &value[..unit_index] + ); + is_time = true; + } else { + anyhow::ensure!(unit_index != 0, "value not specified for '{unitc}'"); + + let uvs = &value[..unit_index]; + let unit_value: f64 = uvs + .parse() + .with_context(|| "failed to parse value '{uvs}' for unit '{unit}'")?; + + supplied_units += 1; + + anyhow::ensure!( + !matches!(unit, Unit::Hour | Unit::Minute | Unit::Second) || is_time, + "'{unitc}' must be preceded with 'T'" + ); + + // This would be nicer if 'M' couldn't mean both months and minutes :p + let block = if is_time { &UNITS[4..] } else { &UNITS[..4] }; + let unit_to_seconds = block + .iter() + .find_map(|(c, uts)| (*c == unitc).then_some(*uts)) + .unwrap(); + + duration += time::Duration::checked_seconds_f64(unit_value * unit_to_seconds) + .with_context(|| format!("value '{unit_value}' for '{unitc}' is out of range"))?; + } + + last_unitc = unitc; + last_unit = unit; + value = &value[unit_index + 1..]; + } + + anyhow::ensure!(supplied_units > 0, "must supply at least one time unit"); + + Ok(duration) } #[cfg(test)] mod test { - use super::*; + use super::{parse_rfc3339_duration as dur_parse, *}; use crate::cfg::{test::*, Fake, UnvalidatedConfig}; - use std::borrow::Cow; #[test] fn deserializes_advisories_cfg() { @@ -198,8 +366,8 @@ mod test { assert!(validated .db_path .iter() - .map(|dp| dp.to_string_lossy()) - .eq(vec![Cow::Borrowed("~/.cargo/advisory-dbs")])); + .map(|dp| dp.as_str()) + .eq(vec!["~/.cargo/advisory-dbs"])); assert!(validated.db_urls.iter().eq(vec![&Url::parse( "https://github.com/RustSec/advisory-db" ) @@ -221,4 +389,78 @@ mod test { Some(rustsec::advisory::Severity::Medium) ); } + + /// Validates we reject invalid formats, or at least ones we don't support + #[test] + fn rejects_invalid_durations() { + const FAILURES: &[&str] = &[ + "no-P", // Format requires 'P' at the beginning + "P", "PT", // Empty duration, must specify at least _one_ unit + "P1H3", "P2TH3", // Number without unit specified + "PT1HM", // Unit without number specified + "PT1M3H", "P3Y1M", "P2W1Y", "PT2W1H", // Units in an invalid order + "P5H", "P5S", // Time units must be preceded by T + // We don't accept ',' as a decimal separator even though it is allowed in the spec + "PT1,5S", + ]; + + let failures: String = FAILURES + .iter() + .map(|bad| format!("{:?}\n", dur_parse(bad))) + .collect(); + + insta::assert_snapshot!(failures); + } + + /// Validates we can parse many durations. + /// + /// Note the values were copied from + /// but at least according to the grammar in the RFC...many were actually invalid :p + #[test] + fn parses_valid_durations() { + const DAY: f64 = 24. * 60. * 60.; + const MONTH: f64 = 30.43 * DAY; + const TABLE: &[(&str, f64)] = &[ + ("P1Y", 365. * DAY), + ("P1.5Y", 365. * 1.5 * DAY), + ("P1M", MONTH), + ("P2W", 7. * 2. * DAY), + ("P3D", 3. * DAY), + ("PT4H", 4. * 60. * 60.), + ("PT2M", 2. * 60.), + ("PT8S", 8.), + ("PT8.5S", 8.5), + ("P3M1Y", 3. * MONTH + 365. * DAY), + ("P5D1Y", 5. * DAY + 365. * DAY), + ("P3D4M1Y", 3. * DAY + 4. * MONTH + 365. * DAY), + ( + "P2D3M1YT3H2M1S", + 2. * DAY + 3. * MONTH + 365. * DAY + 3. * 60. * 60. + 2. * 60. + 1., + ), + ("P2DT4H", 2. * DAY + 4. * 60. * 60.), + ("P2MT0.5M", 2. * MONTH + 0.5 * 60.), + ("P5DT1.6M", 5. * DAY + 60. * 1.6), + ("P1.5W", 7. * 1.5 * DAY), + ("P3D1.5W", 3. * DAY + 7. * 1.5 * DAY), + ("P2DT3.002S", 2. * DAY + 3.002), + ("P2DT3.02003S", 2. * DAY + 3.02003), + ("P2DT4H3M2.6S", 2. * DAY + 4. * 60. * 60. + 3. * 60. + 2.6), + ("PT3H2M1.1S", 3. * 60. * 60. + 2. * 60. + 1.1), + ]; + + for (dur, secs) in TABLE { + match dur_parse(dur) { + Ok(parsed) => { + assert_eq!( + parsed, + time::Duration::seconds_f64(*secs), + "unexpected duration for '{dur}'" + ); + } + Err(err) => { + panic!("failed to parse '{dur}': {err:#}"); + } + } + } + } } diff --git a/src/advisories/diags.rs b/src/advisories/diags.rs index 525e2de0..eada9035 100644 --- a/src/advisories/diags.rs +++ b/src/advisories/diags.rs @@ -23,6 +23,7 @@ pub enum Code { Unsound, Yanked, IndexFailure, + IndexCacheLoadFailure, AdvisoryNotDetected, UnknownAdvisory, } @@ -213,6 +214,17 @@ impl<'a> crate::CheckCtx<'a, super::cfg::ValidConfig> { pack } + pub fn diag_for_index_load_failure(&self, error: impl std::fmt::Display) -> Pack { + ( + Check::Advisories, + Diagnostic::new(Severity::Error) + .with_message("failed to load index cache") + .with_code(Code::IndexCacheLoadFailure) + .with_notes(vec![error.to_string()]), + ) + .into() + } + pub(crate) fn diag_for_advisory_not_encountered( &self, not_hit: &crate::cfg::Spanned, diff --git a/src/advisories/helpers.rs b/src/advisories/helpers.rs index 86dc3cba..d3c3c6cd 100644 --- a/src/advisories/helpers.rs +++ b/src/advisories/helpers.rs @@ -1,968 +1,2 @@ -use crate::{Krate, Krates}; -use anyhow::{Context, Error}; -use log::{debug, info}; -use rayon::prelude::*; -pub use rustsec::{advisory::Id, Database, Lockfile, Vulnerability}; -use std::path::{Path, PathBuf}; -use url::Url; - -// The default, official, rustsec advisory database -const DEFAULT_URL: &str = "https://github.com/RustSec/advisory-db"; - -/// Whether the database will be fetched or not -#[derive(Copy, Clone)] -pub enum Fetch { - Allow, - AllowWithGitCli, - Disallow, -} - -/// A collection of [`Database`]s that is used to query advisories -/// in many different databases. -/// -/// [`Database`]: https://docs.rs/rustsec/0.25.0/rustsec/database/struct.Database.html -pub struct DbSet { - dbs: Vec<(Url, Database)>, -} - -impl DbSet { - pub fn load( - root: Option>, - mut urls: Vec, - fetch: Fetch, - ) -> Result { - let root_db_path = match root { - Some(root) => { - let user_root = root.as_ref(); - if user_root.starts_with("~") { - if let Some(home) = home::home_dir() { - home.join(user_root.strip_prefix("~").unwrap()) - } else { - log::warn!( - "unable to resolve path '{}', falling back to the default advisory path", - user_root.display() - ); - - // This would only succeed of CARGO_HOME was explicitly set - home::cargo_home() - .context("failed to resolve CARGO_HOME")? - .join("advisory-dbs") - } - } else { - user_root.to_owned() - } - } - None => home::cargo_home() - .context("failed to resolve CARGO_HOME")? - .join("advisory-dbs"), - }; - - if urls.is_empty() { - info!( - "No advisory database configured, falling back to default '{}'", - DEFAULT_URL - ); - urls.push(Url::parse(DEFAULT_URL).unwrap()); - } - - use rayon::prelude::*; - let mut dbs = Vec::with_capacity(urls.len()); - urls.into_par_iter() - .map(|url| load_db(&url, root_db_path.clone(), fetch).map(|db| (url, db))) - .collect_into_vec(&mut dbs); - - Ok(Self { - dbs: dbs.into_iter().collect::, _>>()?, - }) - } - - #[inline] - pub fn iter(&self) -> impl Iterator { - self.dbs.iter() - } - - #[inline] - pub fn has_advisory(&self, id: &Id) -> bool { - self.dbs.iter().any(|db| db.1.get(id).is_some()) - } -} - -/// Converts a full url, eg , into -/// the root directory name where cargo itself will fetch it on disk -pub(crate) fn url_to_local_dir(url: &str) -> Result<(String, String), Error> { - fn to_hex(num: u64) -> String { - const CHARS: &[u8] = b"0123456789abcdef"; - - // Note that cargo does this as well so that the hex strings are - // the same on big endian as well - let bytes = num.to_le_bytes(); - - let mut output = String::with_capacity(16); - - for byte in bytes { - output.push(CHARS[(byte >> 4) as usize] as char); - output.push(CHARS[(byte & 0xf) as usize] as char); - } - - output - } - - #[allow(deprecated)] - fn hash_u64(url: &str, kind: u64) -> u64 { - use std::hash::{Hash, Hasher, SipHasher}; - - let mut hasher = SipHasher::new_with_keys(0, 0); - kind.hash(&mut hasher); - url.hash(&mut hasher); - hasher.finish() - } - - const KIND_GIT_INDEX: u64 = 2; - const KIND_SPARSE_INDEX: u64 = 3; - - let mut is_sparse = false; - - // Ensure we have a registry or bare url - let (url, scheme_ind) = { - let scheme_ind = url - .find("://") - .with_context(|| format!("'{}' is not a valid url", url))?; - - let scheme_str = &url[..scheme_ind]; - if scheme_str == "sparse+https" { - is_sparse = true; - (url, scheme_ind) - } else if let Some(ind) = scheme_str.find('+') { - if &scheme_str[..ind] != "registry" { - anyhow::bail!("'{}' is not a valid registry url", url); - } - - (&url[ind + 1..], scheme_ind - ind - 1) - } else { - (url, scheme_ind) - } - }; - - // Could use the Url crate for this, but it's simple enough and we don't - // need to deal with every possible url (I hope...) - let host = match url[scheme_ind + 3..].find('/') { - Some(end) => &url[scheme_ind + 3..scheme_ind + 3 + end], - None => &url[scheme_ind + 3..], - }; - - // cargo special cases github.com for reasons, so do the same - let mut canonical = if host == "github.com" { - url.to_lowercase() - } else { - url.to_owned() - }; - - // Chop off any query params/fragments - if let Some(hash) = canonical.rfind('#') { - canonical.truncate(hash); - } - - if let Some(query) = canonical.rfind('?') { - canonical.truncate(query); - } - - let ident = to_hex(hash_u64( - &canonical, - if is_sparse { - KIND_SPARSE_INDEX - } else { - KIND_GIT_INDEX - }, - )); - - if canonical.ends_with('/') { - canonical.pop(); - } - - if host == "github.com" && canonical.ends_with(".git") { - canonical.truncate(canonical.len() - 4); - } - - Ok((format!("{host}-{ident}"), canonical)) -} - -/// Convert an advisory url to a directory underneath a specified root -fn url_to_path(mut db_path: PathBuf, url: &Url) -> Result { - let (ident, _) = url_to_local_dir(url.as_str())?; - db_path.push(ident); - - Ok(db_path) -} - -fn load_db(db_url: &Url, root_db_path: PathBuf, fetch: Fetch) -> Result { - let db_path = url_to_path(root_db_path, db_url)?; - - match fetch { - Fetch::Allow => { - debug!("Fetching advisory database from '{db_url}'"); - fetch_via_git(db_url, &db_path) - .with_context(|| format!("failed to fetch advisory database {db_url}"))?; - } - Fetch::AllowWithGitCli => { - debug!("Fetching advisory database with git cli from '{db_url}'"); - - fetch_via_cli(db_url.as_str(), &db_path) - .with_context(|| format!("failed to fetch advisory database {db_url} with cli"))?; - } - Fetch::Disallow => { - debug!("Opening advisory database at '{}'", db_path.display()); - } - } - - // Verify that the repository is actually valid - git2::Repository::open(&db_path).context("failed to open advisory database")?; - - debug!("loading advisory database from {}", db_path.display()); - - let res = Database::open(&db_path).context("failed to load advisory database"); - - debug!( - "finished loading advisory database from {}", - db_path.display() - ); - - res -} - -fn fetch_via_git(url: &Url, db_path: &Path) -> Result<(), Error> { - anyhow::ensure!( - url.scheme() == "https" || url.scheme() == "ssh", - "expected '{}' to be an `https` or `ssh` url", - url - ); - - // Ensure the parent directory chain is created, git2 won't do it for us - { - let parent = db_path - .parent() - .with_context(|| format!("invalid directory: {}", db_path.display()))?; - - if !parent.is_dir() { - std::fs::create_dir_all(parent)?; - } - } - - // Avoid libgit2 errors in the case the directory exists but is - // otherwise empty. - // - // See: https://github.com/RustSec/cargo-audit/issues/32 - if db_path.is_dir() && std::fs::read_dir(db_path)?.next().is_none() { - std::fs::remove_dir(db_path)?; - } - - /// Ref for the `main` branch in the local repository - const LOCAL_REF: &str = "refs/heads/main"; - - /// Ref for the `main` branch in the remote repository - const REMOTE_REF: &str = "refs/remotes/origin/main"; - - let git_config = git2::Config::new()?; - - with_authentication(url.as_str(), &git_config, |f| { - let mut callbacks = git2::RemoteCallbacks::new(); - callbacks.credentials(f); - - let mut proxy_opts = git2::ProxyOptions::new(); - proxy_opts.auto(); - - let mut fetch_opts = git2::FetchOptions::new(); - fetch_opts.remote_callbacks(callbacks); - fetch_opts.proxy_options(proxy_opts); - - if db_path.exists() { - let repo = git2::Repository::open(db_path)?; - let refspec = format!("{LOCAL_REF}:{REMOTE_REF}"); - - // Fetch remote packfiles and update tips - let mut remote = repo.remote_anonymous(url.as_str())?; - remote.fetch(&[refspec.as_str()], Some(&mut fetch_opts), None)?; - - // Get the current remote tip (as an updated local reference) - let remote_main_ref = repo.find_reference(REMOTE_REF)?; - let remote_target = remote_main_ref.target().unwrap(); - - // Set the local main ref to match the remote - match repo.find_reference(LOCAL_REF) { - Ok(mut local_main_ref) => { - local_main_ref.set_target( - remote_target, - &format!("moving `main` to {REMOTE_REF}: {remote_target}"), - )?; - } - Err(e) if e.code() == git2::ErrorCode::NotFound => { - anyhow::bail!("unable to find reference '{LOCAL_REF}'"); - } - Err(e) => { - return Err(e.into()); - } - }; - } else { - git2::build::RepoBuilder::new() - .fetch_options(fetch_opts) - .clone(url.as_str(), db_path)?; - } - - Ok(()) - })?; - - let repo = git2::Repository::open(db_path).context("failed to open repository")?; - - // Retrieve the HEAD commit - let head = repo.head()?; - - let oid = head - .target() - .with_context(|| format!("no ref target for '{}'", db_path.display()))?; - - let commit_object = repo.find_object(oid, Some(git2::ObjectType::Commit))?; - let commit = commit_object - .as_commit() - .context("HEAD OID was not a reference to a commit")?; - - // Reset the state of the repository to the latest commit - repo.reset(&commit_object, git2::ResetType::Hard, None)?; - - let timestamp = time::OffsetDateTime::from_unix_timestamp(commit.time().seconds()) - .context("commit timestamp is invalid")?; - - // 90 days - const MINIMUM_FRESHNESS: time::Duration = time::Duration::seconds(90 * 24 * 60 * 60); - - // Ensure that the upstream repository hasn't gone stale, ie, they've - // configured cargo-deny to not fetch the remote database(s), but they've - // failed to update the databases manuallly - anyhow::ensure!( - timestamp - > time::OffsetDateTime::now_utc() - .checked_sub(MINIMUM_FRESHNESS) - .expect("this should never happen"), - "repository is stale (last commit: {})", - timestamp - ); - - Ok(()) -} - -fn fetch_via_cli(url: &str, db_path: &Path) -> Result<(), Error> { - use std::{fs, process::Command}; - - if let Some(parent) = db_path.parent() { - if !parent.is_dir() { - fs::create_dir_all(parent).with_context(|| { - format!( - "failed to create advisory database directory {}", - parent.display() - ) - })?; - } - } else { - anyhow::bail!("invalid directory: {}", db_path.display()); - } - - fn capture(mut cmd: Command) -> Result { - cmd.stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()); - - let output = cmd - .spawn() - .context("failed to spawn git")? - .wait_with_output() - .context("failed to wait on git output")?; - - if output.status.success() { - String::from_utf8(output.stdout) - .or_else(|_err| Ok("git command succeeded but gave non-utf8 output".to_owned())) - } else { - String::from_utf8(output.stderr) - .map_err(|_err| anyhow::anyhow!("git command failed and gave non-utf8 output")) - } - } - - if db_path.exists() { - // make sure db_path is clean - let mut cmd = Command::new("git"); - cmd.arg("reset").arg("--hard").current_dir(db_path); - - // We don't fail if we can't reset since it _may_ still be possible to - // clone - match capture(cmd) { - Ok(_reset) => log::debug!("reset {url}"), - Err(err) => log::error!("failed to reset {url}: {err}"), - } - - // pull latest changes - let mut cmd = Command::new("git"); - cmd.arg("pull").current_dir(db_path); - - capture(cmd).context("failed to pull latest changes")?; - log::debug!("pulled {url}"); - } else { - // clone repository - let mut cmd = Command::new("git"); - cmd.arg("clone").arg(url).arg(db_path); - - capture(cmd).context("failed to clone")?; - log::debug!("cloned {url}"); - } - - Ok(()) -} - -/// Prepare the authentication callbacks for cloning a git repository. -/// -/// The main purpose of this function is to construct the "authentication -/// callback" which is used to clone a repository. This callback will attempt to -/// find the right authentication on the system (without user input) and will -/// guide libgit2 in doing so. -/// -/// The callback is provided `allowed` types of credentials, and we try to do as -/// much as possible based on that: -/// -/// * Prioritize SSH keys from the local ssh agent as they're likely the most -/// reliable. The username here is prioritized from the credential -/// callback, then from whatever is configured in git itself, and finally -/// we fall back to the generic user of `git`. -/// -/// * If a username/password is allowed, then we fallback to git2-rs's -/// implementation of the credential helper. This is what is configured -/// with `credential.helper` in git, and is the interface for the macOS -/// keychain, for example. -/// -/// * After the above two have failed, we just kinda grapple attempting to -/// return *something*. -/// -/// If any form of authentication fails, libgit2 will repeatedly ask us for -/// credentials until we give it a reason to not do so. To ensure we don't -/// just sit here looping forever we keep track of authentications we've -/// attempted and we don't try the same ones again. -pub fn with_authentication(url: &str, cfg: &git2::Config, mut f: F) -> Result -where - F: FnMut(&mut git2::Credentials<'_>) -> Result, -{ - let mut cred_helper = git2::CredentialHelper::new(url); - cred_helper.config(cfg); - - let mut ssh_username_requested = false; - let mut cred_helper_bad = None; - let mut ssh_agent_attempts = Vec::new(); - let mut any_attempts = false; - let mut tried_sshkey = false; - - let mut res = f(&mut |url, username, allowed| { - any_attempts = true; - // libgit2's "USERNAME" authentication actually means that it's just - // asking us for a username to keep going. This is currently only really - // used for SSH authentication and isn't really an authentication type. - // The logic currently looks like: - // - // let user = ...; - // if (user.is_null()) - // user = callback(USERNAME, null, ...); - // - // callback(SSH_KEY, user, ...) - // - // So if we're being called here then we know that (a) we're using ssh - // authentication and (b) no username was specified in the URL that - // we're trying to clone. We need to guess an appropriate username here, - // but that may involve a few attempts. Unfortunately we can't switch - // usernames during one authentication session with libgit2, so to - // handle this we bail out of this authentication session after setting - // the flag `ssh_username_requested`, and then we handle this below. - if allowed.contains(git2::CredentialType::USERNAME) { - debug_assert!(username.is_none()); - ssh_username_requested = true; - return Err(git2::Error::from_str("gonna try usernames later")); - } - - // An "SSH_KEY" authentication indicates that we need some sort of SSH - // authentication. This can currently either come from the ssh-agent - // process or from a raw in-memory SSH key. Cargo only supports using - // ssh-agent currently. - // - // If we get called with this then the only way that should be possible - // is if a username is specified in the URL itself (e.g., `username` is - // Some), hence the unwrap() here. We try custom usernames down below. - if allowed.contains(git2::CredentialType::SSH_KEY) && !tried_sshkey { - // If ssh-agent authentication fails, libgit2 will keep - // calling this callback asking for other authentication - // methods to try. Make sure we only try ssh-agent once, - // to avoid looping forever. - tried_sshkey = true; - let username = username.unwrap(); - debug_assert!(!ssh_username_requested); - ssh_agent_attempts.push(username.to_string()); - return git2::Cred::ssh_key_from_agent(username); - } - - // Sometimes libgit2 will ask for a username/password in plaintext. This - // is where Cargo would have an interactive prompt if we supported it, - // but we currently don't! Right now the only way we support fetching a - // plaintext password is through the `credential.helper` support, so - // fetch that here. - // - // If ssh-agent authentication fails, libgit2 will keep calling this - // callback asking for other authentication methods to try. Check - // cred_helper_bad to make sure we only try the git credential helper - // once, to avoid looping forever. - if allowed.contains(git2::CredentialType::USER_PASS_PLAINTEXT) && cred_helper_bad.is_none() - { - let r = git2::Cred::credential_helper(cfg, url, username); - cred_helper_bad = Some(r.is_err()); - return r; - } - - // I'm... not sure what the DEFAULT kind of authentication is, but seems - // easy to support? - if allowed.contains(git2::CredentialType::DEFAULT) { - return git2::Cred::default(); - } - - // Whelp, we tried our best - Err(git2::Error::from_str("no authentication available")) - }); - - // Ok, so if it looks like we're going to be doing ssh authentication, we - // want to try a few different usernames as one wasn't specified in the URL - // for us to use. In order, we'll try: - // - // * A credential helper's username for this URL, if available. - // * This account's username. - // * "git" - // - // We have to restart the authentication session each time (due to - // constraints in libssh2 I guess? maybe this is inherent to ssh?), so we - // call our callback, `f`, in a loop here. - if ssh_username_requested { - debug_assert!(res.is_err()); - let mut attempts = vec!["git".to_owned()]; - if let Ok(s) = std::env::var("USER").or_else(|_| std::env::var("USERNAME")) { - attempts.push(s); - } - if let Some(s) = &cred_helper.username { - attempts.push(s.clone()); - } - - while let Some(s) = attempts.pop() { - // We should get `USERNAME` first, where we just return our attempt, - // and then after that we should get `SSH_KEY`. If the first attempt - // fails we'll get called again, but we don't have another option so - // we bail out. - let mut attempts = 0; - res = f(&mut |_url, username, allowed| { - if allowed.contains(git2::CredentialType::USERNAME) { - return git2::Cred::username(&s); - } - if allowed.contains(git2::CredentialType::SSH_KEY) { - debug_assert_eq!(Some(&s[..]), username); - attempts += 1; - if attempts == 1 { - ssh_agent_attempts.push(s.clone()); - return git2::Cred::ssh_key_from_agent(&s); - } - } - Err(git2::Error::from_str("no authentication available")) - }); - - // If we made two attempts then that means: - // - // 1. A username was requested, we returned `s`. - // 2. An ssh key was requested, we returned to look up `s` in the - // ssh agent. - // 3. For whatever reason that lookup failed, so we were asked again - // for another mode of authentication. - // - // Essentially, if `attempts == 2` then in theory the only error was - // that this username failed to authenticate (e.g., no other network - // errors happened). Otherwise something else is funny so we bail - // out. - if attempts != 2 { - break; - } - } - } - - if res.is_ok() || !any_attempts { - return res.map_err(From::from); - } - - // In the case of an authentication failure (where we tried something) then - // we try to give a more helpful error message about precisely what we - // tried. - let res = res.map_err(|_e| { - let mut msg = "failed to authenticate when downloading repository".to_owned(); - if !ssh_agent_attempts.is_empty() { - let names = ssh_agent_attempts - .iter() - .map(|s| format!("`{s}`")) - .collect::>() - .join(", "); - - use std::fmt::Write; - let _ = write!( - &mut msg, - "\nattempted ssh-agent authentication, but none of the usernames {names} succeeded", - ); - } - if let Some(failed_cred_helper) = cred_helper_bad { - if failed_cred_helper { - msg.push_str( - "\nattempted to find username/password via \ - git's `credential.helper` support, but failed", - ); - } else { - msg.push_str( - "\nattempted to find username/password via \ - `credential.helper`, but maybe the found \ - credentials were incorrect", - ); - } - } - - anyhow::anyhow!(msg) - })?; - - Ok(res) -} - -pub(super) enum Index { - Git(crates_index::Index), - Http(crates_index::SparseIndex), -} - -pub(super) struct Indices<'k> { - indices: Vec<(&'k crate::Source, Option)>, -} - -impl<'k> Indices<'k> { - pub(super) fn load(krates: &'k Krates, crates_io_git_fallback: bool) -> Self { - let mut indices = Vec::<(&crate::Source, Option)>::new(); - - // As of Rust 1.68, the sparse index is stable, but not the default and - // must be manually enabled by users via .config/cargo.toml or env. This - // doesn't actually change the source for crates.io packages, so we detect - // if it's being used by checking the manifest paths - if let Some(ksrc) = krates.krates().find_map(|k| { - k.source.as_ref().filter(|_s| { - k.is_crates_io() - && k.manifest_path - .as_str() - .contains("index.crates.io-6f17d22bba15001f") - }) - }) { - // SparseIndex::from_url doesn't fail if the sparse index doesn't - // actually exist on disk :p - let index = match crates_index::SparseIndex::from_url(crate::CRATES_IO_SPARSE) - .and_then(|index| index.index_config().map(|_| index)) - { - Ok(index) => Some(Index::Http(index)), - Err(err) => { - log::error!( - "failed to load crates.io sparse index{}: {err}", - if crates_io_git_fallback { - ", falling back to git registry" - } else { - "" - } - ); - - if crates_io_git_fallback && krates.krates().any(|k| k.is_crates_io()) { - match crates_index::Index::new_cargo_default() { - Ok(i) => Some(Index::Git(i)), - Err(err) => { - log::warn!("failed to load crates.io git index: {err}"); - None - } - } - } else { - None - } - } - }; - - indices.push((ksrc, index)); - } - - for (krate, source) in krates - .krates() - .filter_map(|k| k.source.as_ref().map(|s| (k, s))) - { - if indices.iter().any(|(src, _)| *src == source) { - continue; - } - - use crate::SourceKind; - let index = match (source.kind, source.url()) { - (SourceKind::CratesIo(true) | SourceKind::Sparse, url) => { - let surl = if let Some(url) = url { - format!("sparse+{url}") - } else { - crate::CRATES_IO_SPARSE.to_owned() - }; - - match crates_index::SparseIndex::from_url(&surl) { - Ok(index) => Some(Index::Http(index)), - Err(err) => { - log::warn!("failed to load sparse index '{surl}' used by crate '{krate}': {err}"); - None - } - } - } - (SourceKind::CratesIo(false), None) => { - match crates_index::Index::new_cargo_default() { - Ok(i) => Some(Index::Git(i)), - Err(err) => { - log::warn!( - "failed to load crates.io index used by crate '{krate}': {err}" - ); - None - } - } - } - (SourceKind::Registry, Some(url)) => { - match crates_index::Index::from_url(url.as_str()) { - Ok(i) => Some(Index::Git(i)), - Err(err) => { - log::warn!( - "failed to load index '{url}' used by crate '{krate}': {err}" - ); - None - } - } - } - _ => None, - }; - - indices.push((source, index)); - } - - Self { indices } - } - - #[inline] - pub(super) fn is_yanked(&self, krate: &Krate) -> anyhow::Result { - // Ignore non-registry crates when checking, as a crate sourced - // locally or via git can have the same name as a registry package - let Some(src) = krate.source.as_ref().filter(|s| s.is_registry()) else { return Ok(false) }; - - let index = self - .indices - .iter() - .find_map(|(url, index)| index.as_ref().filter(|_i| src == *url)) - .context("failed to load source index")?; - - let index_krate = match index { - Index::Git(gindex) => gindex - .crate_(&krate.name) - .context("failed to find crate in git index")?, - Index::Http(hindex) => hindex - .crate_from_cache(&krate.name) - .context("failed to find crate in sparse index")?, - }; - - Ok(index_krate - .versions() - .iter() - .any(|kv| kv.version() == krate.version.to_string() && kv.is_yanked())) - } -} - -pub use rustsec::{Warning, WarningKind}; - -pub struct Report<'db, 'k> { - pub advisories: Vec<(&'k Krate, krates::NodeId, &'db rustsec::Advisory)>, - /// For backwards compatiblity with cargo-audit, we optionally serialize the - /// reports to JSON and output them in addition to the normal cargo-deny - /// diagnostics - pub serialized_reports: Vec, -} - -impl<'db, 'k> Report<'db, 'k> { - pub fn generate(advisory_dbs: &'db DbSet, krates: &'k Krates, serialize_reports: bool) -> Self { - let mut serialized_reports = Vec::with_capacity(if serialize_reports { - advisory_dbs.dbs.len() - } else { - 0 - }); - - // We just use rustsec::Report directly to avoid divergence with cargo-audit, - // but since we operate differently we need to do shenanigans - let fake_lockfile = serialize_reports.then(|| { - // This is really gross, but the only field is private :p - let lfi: rustsec::report::LockfileInfo = serde_json::from_value(serde_json::json!({ - "dependency-count": krates.len() - })) - .expect("check the definition of rustsec::report::LockfileInfo, it's been changed"); - - lfi - }); - - let mut advisories = Vec::new(); - - for (url, db) in advisory_dbs.iter() { - // Ugh, db exposes advisories as a slice iter which rayon doesn't have an impl for :( - let mut db_advisories: Vec<_> = db - .iter() - .par_bridge() - .filter(|advisory| { - if let Some(wdate) = &advisory.metadata.withdrawn { - log::trace!( - "ignoring advisory '{}', withdrawn {wdate}", - advisory.metadata.id - ); - return false; - } - - // TODO: Support Rust std/core advisories at some point, but - // AFAIK rustsec/cargo-audit doesn't support checking for them either - advisory - .metadata - .collection - .map_or(true, |c| c == rustsec::Collection::Crates) - }) - .flat_map(|advisory| { - krates - .krates_by_name(advisory.metadata.package.as_str()) - .par_bridge() - .filter_map(move |(nid, krate)| { - let ksrc = krate.source.as_ref()?; - - // Validate the crate's source is the same as the advisory - if !ksrc.matches_rustsec(advisory.metadata.source.as_ref()) { - return None; - } - - // Ensure the crate's version is actually affected - if !advisory.versions.is_vulnerable(&krate.version) { - return None; - } - - Some((krate, nid, advisory)) - }) - }) - .collect(); - - if let Some(lockfile) = fake_lockfile.clone() { - let mut warnings = std::collections::BTreeMap::<_, Vec>::new(); - let mut vulns = Vec::new(); - - for (krate, _nid, advisory) in &db_advisories { - let package = rustsec::package::Package { - // :( - name: krate.name.parse().unwrap(), - version: krate.version.clone(), - source: krate.source.as_ref().map(|s| s.to_rustsec()), - // TODO: Get this info from the lockfile - checksum: None, - dependencies: Vec::new(), - replace: None, - }; - - if let Some(kind) = advisory - .metadata - .informational - .as_ref() - .and_then(|i| i.warning_kind()) - { - let warning = rustsec::Warning { - kind, - package, - advisory: Some(advisory.metadata.clone()), - versions: Some(advisory.versions.clone()), - }; - - if let Some(v) = warnings.get_mut(&kind) { - v.push(warning); - } else { - warnings.insert(kind, vec![warning]); - } - } else { - // Note we don't use new here since it takes references and just clones :p - vulns.push(rustsec::Vulnerability { - advisory: advisory.metadata.clone(), - versions: advisory.versions.clone(), - affected: advisory.affected.clone(), - package, - }); - } - } - - use rustsec::advisory::Informational; - let rep = rustsec::Report { - settings: rustsec::report::Settings { - // We already prune packages we don't care about, so don't filter - // any here - target_arch: None, - target_os: None, - // We handle the severity ourselves - severity: None, - // We handle the ignoring of particular advisory ids ourselves - ignore: Vec::new(), - informational_warnings: vec![ - Informational::Notice, - Informational::Unmaintained, - Informational::Unsound, - //Informational::Other("*"), - ], - }, - lockfile, - vulnerabilities: rustsec::report::VulnerabilityInfo::new(vulns), - warnings, - }; - - match serde_json::to_value(&rep) { - Ok(val) => serialized_reports.push(val), - Err(err) => { - log::error!("Failed to serialize report for database '{url}': {err}"); - } - } - } - - advisories.append(&mut db_advisories); - } - - Self { - advisories, - serialized_reports, - } - } -} - -#[cfg(test)] -mod test { - use super::url_to_path; - use url::Url; - - #[test] - fn converts_url_to_path() { - let root_path = std::env::current_dir().unwrap(); - - { - let url = Url::parse("https://github.com/RustSec/advisory-db").unwrap(); - assert_eq!( - url_to_path(root_path.clone(), &url).unwrap(), - root_path.join("github.com-2f857891b7f43c59") - ); - } - - { - let url = Url::parse("https://bare.com").unwrap(); - assert_eq!( - url_to_path(root_path.clone(), &url).unwrap(), - root_path.join("bare.com-9c003d1ed306b28c") - ); - } - - { - let url = Url::parse("https://example.com/countries/việt nam").unwrap(); - assert_eq!( - url_to_path(root_path.clone(), &url).unwrap(), - root_path.join("example.com-1c03f84825fb7438") - ); - } - } -} +pub(super) mod db; +pub(super) mod index; diff --git a/src/advisories/helpers/db.rs b/src/advisories/helpers/db.rs new file mode 100644 index 00000000..54a371da --- /dev/null +++ b/src/advisories/helpers/db.rs @@ -0,0 +1,836 @@ +use crate::{utf8path, Krate, Krates, Path, PathBuf}; +use anyhow::Context as _; +use log::{debug, info}; +pub use rustsec::{advisory::Id, Database, Lockfile, Vulnerability}; +use std::fmt; +use url::Url; + +// The default, official, rustsec advisory database +const DEFAULT_URL: &str = "https://github.com/RustSec/advisory-db"; + +/// Whether the database will be fetched or not +#[derive(Copy, Clone)] +pub enum Fetch { + Allow, + AllowWithGitCli, + Disallow(time::Duration), +} + +pub struct AdvisoryDb { + /// Remote url of the database + pub url: Url, + /// The deserialized collection of advisories + pub db: Database, + /// The path to the backing repository + pub path: PathBuf, + /// The time of the last fetch of the db + pub fetch_time: time::OffsetDateTime, +} + +impl fmt::Debug for AdvisoryDb { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AdvisoryDb") + .field("url", &self.url) + .field("path", &self.path) + .finish() + } +} + +/// A collection of [`Database`]s that is used to query advisories +/// in many different databases. +/// +/// [`Database`]: https://docs.rs/rustsec/0.25.0/rustsec/database/struct.Database.html +#[derive(Debug)] +pub struct DbSet { + pub dbs: Vec, +} + +impl DbSet { + pub fn load( + root: Option>, + mut urls: Vec, + fetch: Fetch, + ) -> anyhow::Result { + let root_db_path = match root { + Some(root) => { + let user_root = root.as_ref(); + if let Ok(user_root) = user_root.strip_prefix("~") { + if let Some(home) = home::home_dir() { + utf8path(home.join(user_root))? + } else { + log::warn!( + "unable to resolve path '{user_root}', falling back to the default advisory path" + ); + + // This would only succeed of CARGO_HOME was explicitly set + utf8path( + home::cargo_home() + .context("failed to resolve CARGO_HOME")? + .join("advisory-dbs"), + )? + } + } else { + user_root.to_owned() + } + } + None => utf8path( + home::cargo_home() + .context("failed to resolve CARGO_HOME")? + .join("advisory-dbs"), + )?, + }; + + if urls.is_empty() { + info!("No advisory database configured, falling back to default '{DEFAULT_URL}'"); + urls.push(Url::parse(DEFAULT_URL).unwrap()); + } + + use rayon::prelude::*; + let mut dbs = Vec::with_capacity(urls.len()); + urls.into_par_iter() + .map(|url| load_db(url, root_db_path.clone(), fetch)) + .collect_into_vec(&mut dbs); + + Ok(Self { + dbs: dbs.into_iter().collect::, _>>()?, + }) + } + + #[inline] + pub fn iter(&self) -> impl Iterator { + self.dbs.iter() + } + + #[inline] + pub fn has_advisory(&self, id: &Id) -> bool { + self.dbs.iter().any(|adb| adb.db.get(id).is_some()) + } +} + +/// Convert an advisory url to a directory underneath a specified root +fn url_to_db_path(mut db_path: PathBuf, url: &Url) -> anyhow::Result { + let local_dir = tame_index::utils::url_to_local_dir(url.as_str())?; + db_path.push(local_dir.dir_name); + + Ok(db_path) +} + +fn load_db(url: Url, root_db_path: PathBuf, fetch: Fetch) -> anyhow::Result { + let db_url = &url; + let db_path = url_to_db_path(root_db_path, db_url)?; + + match fetch { + Fetch::Allow => { + debug!("Fetching advisory database from '{db_url}'"); + fetch_via_gix(db_url, &db_path) + .with_context(|| format!("failed to fetch advisory database {db_url}"))?; + } + Fetch::AllowWithGitCli => { + debug!("Fetching advisory database with git cli from '{db_url}'"); + + fetch_via_cli(db_url.as_str(), &db_path) + .with_context(|| format!("failed to fetch advisory database {db_url} with cli"))?; + } + Fetch::Disallow(_) => { + debug!("Opening advisory database at '{db_path}'"); + } + } + + // Verify that the repository is actually valid and that it is fresh + let repo = gix::open(&db_path).context("failed to open advisory database")?; + + let fetch_time = get_fetch_time(&repo)?; + + // Ensure that the upstream repository hasn't gone stale, ie, they've + // configured cargo-deny to not fetch the remote database(s), but they've + // failed to update the database manually + if let Fetch::Disallow(max_staleness) = fetch { + anyhow::ensure!( + fetch_time + > time::OffsetDateTime::now_utc() + .checked_sub(max_staleness) + .context("unable to compute oldest allowable update timestamp")?, + "repository is stale (last update: {fetch_time})" + ); + } + + debug!("loading advisory database from {db_path}"); + + let res = Database::open(db_path.as_std_path()).context("failed to load advisory database"); + + debug!("finished loading advisory database from {db_path}"); + + res.map(|db| AdvisoryDb { + url, + db, + path: db_path, + fetch_time, + }) +} + +const DIR: gix::remote::Direction = gix::remote::Direction::Fetch; + +fn get_fetch_time(repo: &gix::Repository) -> anyhow::Result { + let file_timestamp = |name: &str| -> anyhow::Result { + let path = repo.path().join(name); + let attr = + std::fs::metadata(path).with_context(|| format!("failed to get '{name}' metadata"))?; + Ok(attr + .modified() + .with_context(|| format!("failed to get '{name}' modification time"))? + .into()) + }; + + let commit_timestamp = || -> anyhow::Result { + let commit = repo.head_commit().context("failed to get HEAD commit")?; + let time = commit.time().context("failed to get HEAD commit time")?; + + // Copy what gix does, unfortunately it's not public + // + + let ts = time::OffsetDateTime::from_unix_timestamp(time.seconds) + .context("unix timestamp for HEAD was out of range")? + .to_offset( + time::UtcOffset::from_whole_seconds(time.offset) + .context("timestamp offset for HEAD was out of range")?, + ); + + Ok(ts) + }; + + let timestamp = match file_timestamp("FETCH_HEAD") { + Ok(ts) => ts, + Err(fh_err) => { + // If we can't get the mod time of the FETCH_HEAD file, fallback + // to getting the timestamp of the head commit. However, this + // is not as good as FETCH_HEAD mod time since a database could + // have been fetched within the time window, but the HEAD at that + // time was out of the time window + // + // However, to mitigate this problem, we use the HEAD time if it is + // newer than the commit time, as a fresh clone with git will NOT + // have the FETCH_HEAD, but the fresh clone will have just written + // HEAD and thus can be used as a fallback, but still defer to head + // if something weird has happened + match commit_timestamp() { + Ok(commit_ts) => { + let file_head_ts = + file_timestamp("HEAD").unwrap_or(time::OffsetDateTime::UNIX_EPOCH); + std::cmp::max(commit_ts, file_head_ts) + } + Err(hc_err) => { + return Err(hc_err).context(fh_err); + } + } + } + }; + + Ok(timestamp) +} + +fn get_remote_head( + repo: &gix::Repository, + fetch_response: &gix::remote::fetch::Outcome, +) -> anyhow::Result<(gix::ObjectId, gix::bstr::BString)> { + let remote = repo + .head() + .context("failed to get HEAD")? + .into_remote(DIR) + .map(|r| r.context("failed to get remote for HEAD")) + .or_else(|| { + repo.find_default_remote(DIR) + .map(|r| r.context("failed to find default remote")) + }) + .context("failed to find appropriate remote to fetch from")??; + + let remote_head = format!( + "refs/remotes/{}/HEAD", + remote + .name() + .map(|s| s.as_bstr()) + .context("remote name hasn't been persisted to disk")? + ); + + // Find the commit id of the remote's HEAD + let (remote_head_id, remote_ref_target) = fetch_response + .ref_map + .mappings + .iter() + .find_map(|mapping| { + let gix::remote::fetch::Source::Ref(rref) = &mapping.remote else { return None; }; + + if mapping.local.as_deref()? != remote_head.as_bytes() { + return None; + } + + let gix::protocol::handshake::Ref::Symbolic { + full_ref_name, + object, + target, + } = rref else { return None; }; + + (full_ref_name == "HEAD").then(|| (*object, target.clone())) + }) + .context("failed to locate remote HEAD")?; + + Ok((remote_head_id, remote_ref_target)) +} + +/// Perform a fetch + checkout of the latest remote HEAD -> local HEAD +/// +/// Note this function is a bit involved as, either I'm dumb and can't figure out +/// how to do it, or else gix has support for updating HEAD and checking it out +/// when doing a clone, but if you are performing a fetch on an existing repo +/// ...you have to do that all yourself, which is pretty tedious +fn fetch_and_checkout(repo: &mut gix::Repository) -> anyhow::Result<()> { + // In a normal case there will be only one remote, called origin, but try + // and be robust about it + let mut remote = repo + .head() + .context("failed to get HEAD")? + .into_remote(DIR) + .map(|r| r.context("failed to get remote for HEAD")) + .or_else(|| { + repo.find_default_remote(DIR) + .map(|r| r.context("failed to find default remote")) + }) + .context("failed to find appropriate remote to fetch from")??; + + let remote_head = format!( + "refs/remotes/{}/HEAD", + remote + .name() + .map(|s| s.as_bstr()) + .context("remote name hasn't been persisted to disk")? + ); + + remote + .replace_refspecs(Some(format!("HEAD:{remote_head}").as_str()), DIR) + .expect("valid statically known refspec"); + + // Perform the actual fetch + let fetch_response: gix::remote::fetch::Outcome = remote + .connect(DIR)? + .prepare_fetch(&mut gix::progress::Discard, Default::default()) + .context("failed to prepare fetch")? + .receive( + &mut gix::progress::Discard, + &std::sync::atomic::AtomicBool::default(), + ) + .context("failed to fetch")?; + + use gix::refs::{transaction as tx, Target}; + let (remote_head_id, _remote_ref_target) = get_remote_head(repo, &fetch_response)?; + + // In all (hopefully?) cases HEAD is a symbolic reference to + // refs/heads/ which is a peeled commit id, if that's the case + // we update it to the new commit id, otherwise we just set HEAD + // directly + use gix::head::Kind; + let edit = match repo.head()?.kind { + Kind::Symbolic(sref) => { + // Update our local HEAD to the remote HEAD + if let Target::Symbolic(name) = sref.target { + Some(tx::RefEdit { + change: tx::Change::Update { + log: tx::LogChange { + mode: tx::RefLog::AndReference, + force_create_reflog: false, + message: "".into(), + }, + expected: tx::PreviousValue::MustExist, + new: gix::refs::Target::Peeled(remote_head_id), + }, + name, + deref: true, + }) + } else { + None + } + } + Kind::Unborn(_) | Kind::Detached { .. } => None, + }; + + let edit = edit.unwrap_or_else(|| tx::RefEdit { + change: tx::Change::Update { + log: tx::LogChange { + mode: tx::RefLog::AndReference, + force_create_reflog: false, + message: "".into(), + }, + expected: tx::PreviousValue::Any, + new: gix::refs::Target::Peeled(remote_head_id), + }, + name: "HEAD".try_into().unwrap(), + deref: true, + }); + + // We're updating the reflog which requires a committer be set, which might + // not be the case, particular in a CI environment, but also would default + // the the git config for the current directory/global, which on a normal + // user machine would show the user was the one who updated the database which + // is kind of misleading, so we just override the config for this operation + let repo = { + let mut config = repo.config_snapshot_mut(); + config + .set_raw_value("committer", None, "name", "cargo-deny") + .context("failed to set committer.name")?; + // Note we _have_ to set the email as well, but luckily gix does not actually + // validate if it's a proper email or not :) + config + .set_raw_value("committer", None, "email", "") + .context("failed to set committer.email")?; + + config + .commit_auto_rollback() + .context("failed to create auto rollback")? + }; + + repo.edit_reference(edit).context("failed to update HEAD")?; + + // Sanity check that the local HEAD points to the same commit + // as the remote HEAD + anyhow::ensure!( + remote_head_id == repo.head_commit()?.id, + "failed to update HEAD to remote HEAD" + ); + + use gix::prelude::FindExt; + + // Now that we've updated HEAD, do the actual checkout + let workdir = repo + .work_dir() + .context("unable to checkout, repository is bare")?; + let root_tree = repo + .head()? + .peel_to_id_in_place() + .transpose()? + .context("unable to peel HEAD")? + .object() + .context("HEAD commit not downloaded from remote")? + .peel_to_tree() + .context("unable to peel HEAD to tree")? + .id; + + let index = gix::index::State::from_tree(&root_tree, |oid, buf| { + repo.objects.find_tree_iter(oid, buf).ok() + }) + .with_context(|| format!("failed to create index from tree '{root_tree}'"))?; + let mut index = gix::index::File::from_state(index, repo.index_path()); + + let opts = gix::worktree::checkout::Options { + destination_is_initially_empty: false, + overwrite_existing: true, + ..Default::default() + }; + + gix::worktree::checkout( + &mut index, + workdir, + { + let objects = repo.objects.clone().into_arc()?; + move |oid, buf| objects.find_blob(oid, buf) + }, + &mut gix::progress::Discard, + &mut gix::progress::Discard, + &std::sync::atomic::AtomicBool::default(), + opts, + ) + .context("failed to checkout")?; + + index + .write(Default::default()) + .context("failed to write index")?; + + // Now that we've checked out everything write FETCH_HEAD + write_fetch_head(&repo, &fetch_response)?; + + Ok(()) +} + +/// The format of `FETCH_HEAD` is a bit different from other refs, and +/// we don't write it the same as git does, as it includes the tips +/// of _all_ active remote branches, and we don't care about anything +/// except the branch with HEAD +/// +/// `\t\tbranch '' of ''` +fn write_fetch_head( + repo: &gix::Repository, + fetch: &gix::remote::fetch::Outcome, +) -> anyhow::Result<()> { + let fetch_head_path = repo.path().join("FETCH_HEAD"); + + let (remote_head_id, remote_ref_target) = get_remote_head(repo, fetch)?; + + let remote_head = remote_head_id.to_hex(); + // At least for the official rustsec repo, the default branch is 'main', so + // we default to that if the target is invalid utf8 or empty + let remote_ref_target = String::try_from(remote_ref_target).ok(); + let remote_branch_name = remote_ref_target + .as_deref() + .and_then(|s| s.rsplit('/').next()) + .unwrap_or("main"); + + let remote = repo + .head() + .context("failed to get HEAD")? + .into_remote(DIR) + .map(|r| r.context("failed to get remote for HEAD")) + .or_else(|| { + repo.find_default_remote(DIR) + .map(|r| r.context("failed to find default remote")) + }) + .context("failed to find appropriate remote to fetch from")??; + + // This _should_ be impossible if we got here... + let remote_url: String = remote + .url(DIR) + .context("fetch url is not available for remote")? + .to_bstring() + .try_into() + .context("remote url is not valid utf-8")?; + + std::fs::write( + &fetch_head_path, + format!("{remote_head}\t\tbranch '{remote_branch_name}' of {remote_url}"), + ) + .with_context(|| format!("failed to write {fetch_head_path:?}"))?; + + Ok(()) +} + +fn fetch_via_gix(url: &Url, db_path: &Path) -> anyhow::Result<()> { + anyhow::ensure!( + url.scheme() == "https" || url.scheme() == "ssh", + "expected '{}' to be an `https` or `ssh` url", + url + ); + + // Ensure the parent directory chain is created, git2 won't do it for us + { + let parent = db_path + .parent() + .with_context(|| format!("invalid directory: {db_path}"))?; + + if !parent.is_dir() { + std::fs::create_dir_all(parent)?; + } + } + + // Avoid errors in the case the directory exists but is otherwise empty. + // See: https://github.com/RustSec/cargo-audit/issues/32 + // (not sure if this is needed with gix) + if db_path.is_dir() && std::fs::read_dir(db_path)?.next().is_none() { + std::fs::remove_dir(db_path)?; + } + + let (mut repo, cloned) = gix::open(db_path) + .map(|repo| (repo, false)) + .or_else(|err| { + if matches!(err, gix::open::Error::NotARepository { .. }) { + let (mut checkout, out) = gix::prepare_clone(url.as_str(), db_path) + .context("failed to prepare clone")? + .fetch_then_checkout( + gix::progress::Discard, + &std::sync::atomic::AtomicBool::default(), + ) + .context("failed to fetch")?; + + let repo = checkout + .main_worktree( + gix::progress::Discard, + &std::sync::atomic::AtomicBool::default(), + ) + .context("failed to checkout")? + .0; + + write_fetch_head(&repo, &out)?; + + Ok((repo, true)) + } else { + Err(err).context("unable to open repository") + } + }) + .with_context(|| format!("failed to open git repository at '{db_path}'"))?; + + // If we didn't open a fresh repo we need to peform a fetch ourselves, and + // do the work of updating the HEAD to point at the latest remote HEAD, which + // gix doesn't currently do. + // + // Gix also doesn't write the FETCH_HEAD, which we rely on for staleness + // checking, so we write it ourselves to keep identical logic between gix + // and git/git2 + if !cloned { + fetch_and_checkout(&mut repo)?; + } + + Ok(()) +} + +fn fetch_via_cli(url: &str, db_path: &Path) -> anyhow::Result<()> { + use std::{fs, process::Command}; + + if let Some(parent) = db_path.parent() { + if !parent.is_dir() { + fs::create_dir_all(parent).with_context(|| { + format!("failed to create advisory database directory {parent}") + })?; + } + } else { + anyhow::bail!("invalid directory: {db_path}"); + } + + let capture = |mut cmd: Command| -> anyhow::Result { + cmd.stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + let output = cmd + .spawn() + .context("failed to spawn git")? + .wait_with_output() + .context("failed to wait on git output")?; + + if output.status.success() { + String::from_utf8(output.stdout) + .or_else(|_err| Ok("git command succeeded but gave non-utf8 output".to_owned())) + } else { + String::from_utf8(output.stderr) + .map_err(|_err| anyhow::anyhow!("git command failed and gave non-utf8 output")) + } + }; + + let run = |args: &[&str]| { + let mut cmd = Command::new("git"); + cmd.arg("-C").arg(db_path); + cmd.args(args); + + capture(cmd) + }; + + if db_path.exists() { + // make sure db_path is clean + // We don't fail if we can't reset since it _may_ still be possible to + // clone + match run(&["reset", "--hard"]) { + Ok(_reset) => log::debug!("reset {url}"), + Err(err) => log::error!("failed to reset {url}: {err}"), + } + + // pull latest changes + run(&["fetch"]).context("failed to fetch latest changes")?; + log::debug!("fetched {url}"); + + // reset to the remote HEAD + run(&["reset", "--hard", "FETCH_HEAD"]).context("failed to reset to FETCH_HEAD")?; + } else { + // clone repository + let mut cmd = Command::new("git"); + cmd.arg("clone").arg(url).arg(db_path); + + capture(cmd).context("failed to clone")?; + log::debug!("cloned {url}"); + } + + Ok(()) +} + +pub use rustsec::{Warning, WarningKind}; + +pub struct Report<'db, 'k> { + pub advisories: Vec<(&'k Krate, krates::NodeId, &'db rustsec::Advisory)>, + /// For backwards compatiblity with cargo-audit, we optionally serialize the + /// reports to JSON and output them in addition to the normal cargo-deny + /// diagnostics + pub serialized_reports: Vec, +} + +impl<'db, 'k> Report<'db, 'k> { + pub fn generate(advisory_dbs: &'db DbSet, krates: &'k Krates, serialize_reports: bool) -> Self { + let mut serialized_reports = Vec::with_capacity(if serialize_reports { + advisory_dbs.dbs.len() + } else { + 0 + }); + + // We just use rustsec::Report directly to avoid divergence with cargo-audit, + // but since we operate differently we need to do shenanigans + let fake_lockfile = serialize_reports.then(|| { + // This is really gross, but the only field is private :p + let lfi: rustsec::report::LockfileInfo = serde_json::from_value(serde_json::json!({ + "dependency-count": krates.len() + })) + .expect("check the definition of rustsec::report::LockfileInfo, it's been changed"); + + lfi + }); + + let mut advisories = Vec::new(); + use rayon::prelude::{ParallelBridge, ParallelIterator}; + + for advisory_db in advisory_dbs.iter() { + // Ugh, db exposes advisories as a slice iter which rayon doesn't have an impl for :( + let mut db_advisories: Vec<_> = advisory_db + .db + .iter() + .par_bridge() + .filter(|advisory| { + if let Some(wdate) = &advisory.metadata.withdrawn { + log::trace!( + "ignoring advisory '{}', withdrawn {wdate}", + advisory.metadata.id + ); + return false; + } + + // TODO: Support Rust std/core advisories at some point, but + // AFAIK rustsec/cargo-audit doesn't support checking for them either + advisory + .metadata + .collection + .map_or(true, |c| c == rustsec::Collection::Crates) + }) + .flat_map(|advisory| { + krates + .krates_by_name(advisory.metadata.package.as_str()) + .par_bridge() + .filter_map(move |(nid, krate)| { + let ksrc = krate.source.as_ref()?; + + // Validate the crate's source is the same as the advisory + if !ksrc.matches_rustsec(advisory.metadata.source.as_ref()) { + return None; + } + + // Ensure the crate's version is actually affected + if !advisory.versions.is_vulnerable(&krate.version) { + return None; + } + + Some((krate, nid, advisory)) + }) + }) + .collect(); + + if let Some(lockfile) = fake_lockfile.clone() { + let mut warnings = std::collections::BTreeMap::<_, Vec>::new(); + let mut vulns = Vec::new(); + + for (krate, _nid, advisory) in &db_advisories { + let package = rustsec::package::Package { + // :( + name: krate.name.parse().unwrap(), + version: krate.version.clone(), + source: krate.source.as_ref().map(|s| s.to_rustsec()), + // TODO: Get this info from the lockfile + checksum: None, + dependencies: Vec::new(), + replace: None, + }; + + if let Some(kind) = advisory + .metadata + .informational + .as_ref() + .and_then(|i| i.warning_kind()) + { + let warning = rustsec::Warning { + kind, + package, + advisory: Some(advisory.metadata.clone()), + versions: Some(advisory.versions.clone()), + }; + + if let Some(v) = warnings.get_mut(&kind) { + v.push(warning); + } else { + warnings.insert(kind, vec![warning]); + } + } else { + // Note we don't use new here since it takes references and just clones :p + vulns.push(rustsec::Vulnerability { + advisory: advisory.metadata.clone(), + versions: advisory.versions.clone(), + affected: advisory.affected.clone(), + package, + }); + } + } + + use rustsec::advisory::Informational; + let rep = rustsec::Report { + settings: rustsec::report::Settings { + // We already prune packages we don't care about, so don't filter + // any here + target_arch: None, + target_os: None, + // We handle the severity ourselves + severity: None, + // We handle the ignoring of particular advisory ids ourselves + ignore: Vec::new(), + informational_warnings: vec![ + Informational::Notice, + Informational::Unmaintained, + Informational::Unsound, + //Informational::Other("*"), + ], + }, + lockfile, + vulnerabilities: rustsec::report::VulnerabilityInfo::new(vulns), + warnings, + }; + + match serde_json::to_value(&rep) { + Ok(val) => serialized_reports.push(val), + Err(err) => { + log::error!( + "Failed to serialize report for database '{}': {err}", + advisory_db.url + ); + } + } + } + + advisories.append(&mut db_advisories); + } + + Self { + advisories, + serialized_reports, + } + } +} + +#[cfg(test)] +mod test { + use super::url_to_db_path; + use url::Url; + + #[test] + fn converts_url_to_path() { + let root_path = crate::utf8path(std::env::current_dir().unwrap()).unwrap(); + + { + let url = Url::parse("https://github.com/RustSec/advisory-db").unwrap(); + assert_eq!( + url_to_db_path(root_path.clone(), &url).unwrap(), + root_path.join("github.com-a946fc29ac602819") + ); + } + + { + let url = Url::parse("https://bare.com").unwrap(); + assert_eq!( + url_to_db_path(root_path.clone(), &url).unwrap(), + root_path.join("bare.com-9c003d1ed306b28c") + ); + } + + { + let url = Url::parse("https://example.com/countries/việt nam").unwrap(); + assert_eq!( + url_to_db_path(root_path.clone(), &url).unwrap(), + root_path.join("example.com-1c03f84825fb7438") + ); + } + } +} diff --git a/src/advisories/helpers/index.rs b/src/advisories/helpers/index.rs new file mode 100644 index 00000000..572b8df0 --- /dev/null +++ b/src/advisories/helpers/index.rs @@ -0,0 +1,100 @@ +use crate::{Krate, Krates, Source}; +use anyhow::Context as _; +use rayon::prelude::{IntoParallelIterator, ParallelIterator}; +use std::{borrow::Cow, collections::BTreeMap}; +use tame_index::{index::ComboIndexCache, Error, IndexLocation, IndexUrl}; + +pub struct Indices<'k> { + pub indices: Vec<(&'k Source, Result)>, + pub cache: BTreeMap<(&'k str, &'k Source), tame_index::IndexKrate>, +} + +impl<'k> Indices<'k> { + pub fn load(krates: &'k Krates, cargo_home: crate::PathBuf) -> Self { + let mut indices = Vec::<(&Source, Result)>::new(); + + for source in krates + .krates() + .filter_map(|k| k.source.as_ref().filter(|s| s.is_registry())) + { + if indices.iter().any(|(src, _)| *src == source) { + continue; + } + + let index_url = match source { + Source::CratesIo(_is_sparse) => IndexUrl::crates_io( + Some(krates.workspace_root().to_owned()), + Some(&cargo_home), + None, + ), + Source::Sparse(url) | Source::Registry(url) => Ok(url.as_str().into()), + Source::Git { .. } => unreachable!(), + }; + + let index = index_url.and_then(|iu| { + ComboIndexCache::new(IndexLocation::new(iu).with_root(Some(cargo_home.clone()))) + }); + + indices.push((source, index)); + } + + // Load the current entries into an in-memory cache so we can hopefully + // remove any I/O in the rest of the check + let set: std::collections::BTreeSet<_> = krates + .krates() + .filter_map(|k| { + k.source + .as_ref() + .filter(|s| s.is_registry()) + .map(|s| (k.name.as_str(), s)) + }) + .collect(); + + let cache = set + .into_par_iter() + .filter_map(|(name, src)| { + let index = indices + .iter() + .find_map(|(url, index)| index.as_ref().ok().filter(|_i| src == *url))?; + + index + .cached_krate(name.try_into().ok()?) + .ok()? + .map(|ik| ((name, src), ik)) + }) + .collect(); + + Self { indices, cache } + } + + #[inline] + pub fn is_yanked(&self, krate: &'k Krate) -> anyhow::Result { + // Ignore non-registry crates when checking, as a crate sourced + // locally or via git can have the same name as a registry package + let Some(src) = krate.source.as_ref().filter(|s| s.is_registry()) else { return Ok(false) }; + + let index_krate = if let Some(ik) = self.cache.get(&(krate.name.as_str(), src)) { + Cow::Borrowed(ik) + } else { + let index = self + .indices + .iter() + .find_map(|(url, index)| (src == *url).then_some(index.as_ref())) + .context("unable to find source index")? + .map_err(|err| anyhow::anyhow!("failed to load index: {err:#}"))?; + + let ik = index + .cached_krate(krate.name.as_str().try_into()?) + .context("failed to read crate from index cache")? + .context("unable to find crate in cache")?; + Cow::Owned(ik) + }; + + let is_yanked = index_krate + .versions + .iter() + .find_map(|kv| (kv.version == krate.version).then_some(kv.yanked)); + + Ok(is_yanked.unwrap_or_default()) + } +} diff --git a/src/advisories/snapshots/cargo_deny__advisories__cfg__test__rejects_invalid_durations.snap b/src/advisories/snapshots/cargo_deny__advisories__cfg__test__rejects_invalid_durations.snap new file mode 100644 index 00000000..e27857ef --- /dev/null +++ b/src/advisories/snapshots/cargo_deny__advisories__cfg__test__rejects_invalid_durations.snap @@ -0,0 +1,18 @@ +--- +source: src/advisories/cfg.rs +expression: failures +--- +Err(duration requires 'P' prefix) +Err(must supply at least one time unit) +Err(must supply at least one time unit) +Err('H' must be preceded with 'T') +Err(unit not specified for value '2') +Err(value not specified for 'M') +Err(unit 'H' cannot follow 'M') +Err(unit 'M' cannot follow 'Y') +Err(unit 'Y' cannot follow 'W') +Err(unit 'H' cannot follow 'W') +Err('H' must be preceded with 'T') +Err('S' must be preceded with 'T') +Err(',' is valid in the RFC-3339 duration format but not supported by this implementation, use '.' instead) + diff --git a/src/bans.rs b/src/bans.rs index ea03fe2d..b48a8abc 100644 --- a/src/bans.rs +++ b/src/bans.rs @@ -523,7 +523,7 @@ pub fn check( // determine if the feature is covered by an allowed // parent feature fn has_feature( - map: &std::collections::HashMap>, + map: &std::collections::BTreeMap>, parent: &str, feature: &str, ) -> bool { diff --git a/src/cargo-deny/check.rs b/src/cargo-deny/check.rs index 3786f674..59ef609d 100644 --- a/src/cargo-deny/check.rs +++ b/src/cargo-deny/check.rs @@ -5,12 +5,11 @@ use cargo_deny::{ diag::{ CargoSpans, Diagnostic, DiagnosticCode, DiagnosticOverrides, ErrorSink, Files, Severity, }, - licenses, sources, CheckCtx, + licenses, sources, CheckCtx, PathBuf, }; -use is_terminal::IsTerminal as _; use log::error; use serde::Deserialize; -use std::{path::PathBuf, time::Instant}; +use std::time::Instant; #[derive(clap::ValueEnum, Debug, PartialEq, Eq, Copy, Clone)] pub enum WhichCheck { @@ -155,15 +154,13 @@ impl ValidConfig { let (cfg_contents, cfg_path) = match cfg_path { Some(cfg_path) if cfg_path.exists() => ( - std::fs::read_to_string(&cfg_path).with_context(|| { - format!("failed to read config from {}", cfg_path.display()) - })?, + std::fs::read_to_string(&cfg_path) + .with_context(|| format!("failed to read config from {cfg_path}"))?, cfg_path, ), Some(cfg_path) => { log::warn!( - "config path '{}' doesn't exist, falling back to default config", - cfg_path.display() + "config path '{cfg_path}' doesn't exist, falling back to default config" ); (String::new(), cfg_path) } @@ -173,11 +170,10 @@ impl ValidConfig { } }; - let cfg: Config = toml::from_str(&cfg_contents).with_context(|| { - format!("failed to deserialize config from '{}'", cfg_path.display()) - })?; + let cfg: Config = toml::from_str(&cfg_contents) + .with_context(|| format!("failed to deserialize config from '{cfg_path}'"))?; - log::info!("using config from {}", cfg_path.display()); + log::info!("using config from {cfg_path}"); let id = files.add(&cfg_path, cfg_contents); @@ -237,10 +233,7 @@ impl ValidConfig { // While we could continue in the face of configuration errors, the user // may end up with unexpected results, so just abort so they can fix them if has_errors { - anyhow::bail!( - "failed to validate configuration file {}", - cfg_path.display() - ); + anyhow::bail!("failed to validate configuration file {cfg_path}"); } else { Ok(valid_cfg) } @@ -368,6 +361,16 @@ pub(crate) fn cmd( rayon::scope(|s| { s.spawn(|_s| { + // Always run a fetch first in a separate step so that the user can + // see what parts are actually taking time + let start = std::time::Instant::now(); + log::info!("fetching crates for {}", krate_ctx.manifest_path); + if let Err(err) = krate_ctx.fetch_krates() { + log::error!("failed to fetch crates: {err:#}"); + } else { + log::info!("fetched crates in {:?}", start.elapsed()); + } + let gathered = krate_ctx.gather_krates(targets, exclude); if let Ok(krates) = &gathered { @@ -387,7 +390,7 @@ pub(crate) fn cmd( .map(|us| us.as_ref().clone()) .collect(), if args.disable_fetch { - advisories::Fetch::Disallow + advisories::Fetch::Disallow(advisories.maximum_db_staleness) } else if advisories.git_fetch_with_cli { advisories::Fetch::AllowWithGitCli } else { @@ -413,7 +416,7 @@ pub(crate) fn cmd( let (krate_spans, cargo_spans) = krate_spans .map(|(spans, contents, raw_cargo_spans)| { - let id = files.add(krates.lock_path(), contents); + let id = files.add(krates.workspace_root().join("Cargo.lock"), contents); let mut cargo_spans = CargoSpans::new(); for (key, val) in raw_cargo_spans { @@ -472,11 +475,7 @@ pub(crate) fn cmd( args.audit_compatible_output && log_ctx.format == crate::Format::Json; let colorize = log_ctx.format == crate::Format::Human - && match log_ctx.color { - crate::Color::Auto => std::io::stderr().is_terminal(), - crate::Color::Always => true, - crate::Color::Never => false, - }; + && crate::common::should_colorize(log_ctx.color, std::io::stderr()); rayon::scope(|s| { // Asynchronously displays messages sent from the checks @@ -533,10 +532,7 @@ pub(crate) fn cmd( Ok(()) }), Err(err) => { - error!( - "unable to create directory '{}': {err}", - output_dir.display() - ); + error!("unable to create directory '{output_dir}': {err}"); Box::new(move |dup_graph: bans::DupGraph| { anyhow::bail!( @@ -594,7 +590,7 @@ pub(crate) fn cmd( } if let Some(dbset) = advisory_db_set { - let advisories_sink = ErrorSink { + let mut advisories_sink = ErrorSink { overrides, channel: tx, }; @@ -608,6 +604,38 @@ pub(crate) fn cmd( }; s.spawn(move |_| { + // We need to have all the crates when opening indices, so can't + // load them at the same time as the dbset, but meh, this should + // be very fast since we only load from cache, in parallel + let indices = if !ctx.cfg.disable_yank_checking { + // If we can't find the cargo home directory, we won't be able + // to load the cargo indices. We _could_ actually do a fetch + // into a temporary directory instead, but this almost certainly + // means that something is wrong + match tame_index::utils::cargo_home() { + Ok(cargo_home) => { + log::info!("loading index metadata for crates..."); + let start = Instant::now(); + + let indices = advisories::Indices::load(krates, cargo_home); + + log::info!( + "cached index metadata loaded in {}ms", + start.elapsed().as_millis() + ); + Some(indices) + } + Err(err) => { + advisories_sink.push(ctx.diag_for_index_load_failure(format!( + "unable to find cargo home directory: {err:#}" + ))); + None + } + } + } else { + None + }; + log::info!("checking advisories..."); let start = Instant::now(); @@ -619,7 +647,7 @@ pub(crate) fn cmd( None }; - advisories::check(ctx, &dbset, audit_reporter, advisories_sink); + advisories::check(ctx, &dbset, audit_reporter, indices, advisories_sink); log::info!("advisories checked in {}ms", start.elapsed().as_millis()); }); diff --git a/src/cargo-deny/common.rs b/src/cargo-deny/common.rs index 5aff86a2..f56a687d 100644 --- a/src/cargo-deny/common.rs +++ b/src/cargo-deny/common.rs @@ -1,9 +1,8 @@ use cargo_deny::{ diag::{self, FileId, Files, Severity}, licenses::LicenseStore, + PathBuf, }; -use is_terminal::IsTerminal; -use std::path::PathBuf; pub(crate) fn load_license_store() -> Result { log::debug!("loading license store..."); @@ -33,7 +32,7 @@ pub(crate) fn load_targets( if let krates::Target::Unknown(_) = &filter { diagnostics.push( Diagnostic::warning() - .with_message(format!("unknown target `{}` specified", triple)) + .with_message(format!("unknown target `{triple}` specified")) .with_labels(vec![ cargo_deny::diag::Label::primary( id, @@ -60,6 +59,9 @@ pub struct KrateContext { pub frozen: bool, pub locked: bool, pub offline: bool, + /// If true, allows using the crates.io git index, otherwise the sparse index + /// is assumed to be the only index + pub allow_git_index: bool, } impl KrateContext { @@ -100,15 +102,29 @@ impl KrateContext { } } + #[inline] + pub fn fetch_krates(&self) -> anyhow::Result<()> { + fetch(MetadataOptions { + no_default_features: false, + all_features: false, + features: Vec::new(), + manifest_path: self.manifest_path.clone(), + frozen: self.frozen, + locked: self.locked, + offline: self.offline, + }) + } + pub fn gather_krates( self, cfg_targets: Vec<(krates::Target, Vec)>, cfg_excludes: Vec, ) -> Result { - log::info!("gathering crates for {}", self.manifest_path.display()); + log::info!("gathering crates for {}", self.manifest_path); let start = std::time::Instant::now(); - let metadata = get_metadata(MetadataOptions { + log::debug!("gathering crate metadata"); + let metadata = Self::get_metadata(MetadataOptions { no_default_features: self.no_default_features, all_features: self.all_features, features: self.features, @@ -117,6 +133,10 @@ impl KrateContext { locked: self.locked, offline: self.offline, })?; + log::debug!( + "gathered crate metadata in {}ms", + start.elapsed().as_millis() + ); use krates::{Builder, DepKind}; @@ -149,6 +169,22 @@ impl KrateContext { ); } + // crates have been encountered whose metadata disagrees with the index + // so we use the index to fix the features in the graph + // + let gb = gb.with_crates_io_index( + // This is IMO, wrong, but follows the same behavior as cargo, which + // is to use the current working directory to find .cargo/config.toml + // files, rather than being based off of the manifest path etc, but + // this _should_ be the least surprising option + None, + // This is only really supplied in tests to isolate them from one another + None, + // When deciding the default of crates.io, we need to know the version + // of cargo, in this case it's up to the environment + None, + )?; + let graph = gb.build_with_metadata(metadata, |filtered: krates::cm::Package| { let name = filtered.name; let vers = filtered.version; @@ -170,6 +206,85 @@ impl KrateContext { Ok(graph?) } + + #[cfg(not(feature = "standalone"))] + fn get_metadata(opts: MetadataOptions) -> Result { + let mut mdc = krates::Cmd::new(); + + if opts.no_default_features { + mdc.no_default_features(); + } + + if opts.all_features { + mdc.all_features(); + } + + mdc.features(opts.features) + .manifest_path(opts.manifest_path) + .lock_opts(krates::LockOptions { + frozen: opts.frozen, + locked: opts.locked, + offline: opts.offline, + }); + + let mdc: krates::cm::MetadataCommand = mdc.into(); + Ok(mdc.exec()?) + } + + #[cfg(feature = "standalone")] + fn get_metadata(opts: MetadataOptions) -> Result { + use anyhow::Context as _; + use cargo::{core, ops, util}; + + let mut config = util::Config::default()?; + + config.configure( + 0, + true, + None, + opts.frozen, + opts.locked, + opts.offline, + &None, + &[], + &[], + )?; + + let mut manifest_path = opts.manifest_path; + + // Cargo doesn't like non-absolute paths + if !manifest_path.is_absolute() { + manifest_path = cargo_deny::utf8path( + std::env::current_dir() + .context("unable to determine current directory")? + .join(manifest_path), + )?; + } + + let features = std::rc::Rc::new( + opts.features + .into_iter() + .map(|feat| core::FeatureValue::new(util::interning::InternedString::new(&feat))) + .collect(), + ); + + let ws = core::Workspace::new(manifest_path.as_std_path(), &config)?; + let options = ops::OutputMetadataOptions { + cli_features: core::resolver::features::CliFeatures { + features, + all_features: opts.all_features, + uses_default_features: !opts.no_default_features, + }, + no_deps: false, + version: 1, + filter_platforms: vec![], + }; + + let md = ops::output_metadata(&ws, &options)?; + let md_value = serde_json::to_value(md)?; + + Ok(serde_json::from_value(md_value)?) + } } struct MetadataOptions { @@ -183,31 +298,37 @@ struct MetadataOptions { } #[cfg(not(feature = "standalone"))] -fn get_metadata(opts: MetadataOptions) -> Result { - let mut mdc = krates::Cmd::new(); - - if opts.no_default_features { - mdc.no_default_features(); +fn fetch(opts: MetadataOptions) -> anyhow::Result<()> { + use anyhow::Context as _; + let mut cargo = + std::process::Command::new(std::env::var("CARGO").unwrap_or_else(|_ve| "cargo".to_owned())); + + cargo.arg("fetch"); + cargo.arg("--manifest-path"); + cargo.arg(&opts.manifest_path); + if opts.frozen { + cargo.arg("--frozen"); } - if opts.all_features { - mdc.all_features(); + if opts.locked { + cargo.arg("--locked"); } - mdc.features(opts.features) - .manifest_path(opts.manifest_path) - .lock_opts(krates::LockOptions { - frozen: opts.frozen, - locked: opts.locked, - offline: opts.offline, - }); + if opts.offline { + cargo.arg("--offline"); + } - let mdc: krates::cm::MetadataCommand = mdc.into(); - Ok(mdc.exec()?) + cargo.stderr(std::process::Stdio::piped()); + let output = cargo.output().context("failed to run cargo")?; + if output.status.success() { + Ok(()) + } else { + anyhow::bail!(String::from_utf8(output.stderr).context("non-utf8 error output")?); + } } #[cfg(feature = "standalone")] -fn get_metadata(opts: MetadataOptions) -> Result { +fn fetch(opts: MetadataOptions) -> anyhow::Result<()> { use anyhow::Context; use cargo::{core, ops, util}; @@ -229,36 +350,23 @@ fn get_metadata(opts: MetadataOptions) -> Result Option { match log_level { log::LevelFilter::Off => None, @@ -272,7 +380,7 @@ pub fn log_level_to_severity(log_level: log::LevelFilter) -> Option { use codespan_reporting::term::{self, termcolor::ColorChoice}; use std::io::Write; -fn color_to_choice(color: crate::Color, stream: impl IsTerminal) -> ColorChoice { +fn color_to_choice(color: crate::Color, stream: impl std::io::IsTerminal) -> ColorChoice { match color { crate::Color::Auto => { // The termcolor crate doesn't check the stream to see if it's a TTY @@ -288,6 +396,15 @@ fn color_to_choice(color: crate::Color, stream: impl IsTerminal) -> ColorChoice } } +#[inline] +pub fn should_colorize(color: crate::Color, stream: impl std::io::IsTerminal) -> bool { + match color { + crate::Color::Auto => stream.is_terminal(), + crate::Color::Always => true, + crate::Color::Never => false, + } +} + type CsDiag = codespan_reporting::diagnostic::Diagnostic; pub struct Human<'a> { diff --git a/src/cargo-deny/fetch.rs b/src/cargo-deny/fetch.rs index af949c28..73dbb8ea 100644 --- a/src/cargo-deny/fetch.rs +++ b/src/cargo-deny/fetch.rs @@ -2,8 +2,8 @@ use anyhow::{Context, Error}; use cargo_deny::{ advisories, diag::{Diagnostic, Files}, + PathBuf, }; -use std::path::PathBuf; #[derive(clap::ValueEnum, Debug, PartialEq, Eq, Copy, Clone)] pub enum FetchSource { @@ -43,15 +43,13 @@ impl ValidConfig { let (cfg_contents, cfg_path) = match cfg_path { Some(cfg_path) if cfg_path.exists() => ( - std::fs::read_to_string(&cfg_path).with_context(|| { - format!("failed to read config from {}", cfg_path.display()) - })?, + std::fs::read_to_string(&cfg_path) + .with_context(|| format!("failed to read config from {cfg_path}"))?, cfg_path, ), Some(cfg_path) => { log::warn!( - "config path '{}' doesn't exist, falling back to default config", - cfg_path.display() + "config path '{cfg_path}' doesn't exist, falling back to default config" ); (String::new(), cfg_path) } @@ -61,11 +59,10 @@ impl ValidConfig { } }; - let cfg: Config = toml::from_str(&cfg_contents).with_context(|| { - format!("failed to deserialize config from '{}'", cfg_path.display()) - })?; + let cfg: Config = toml::from_str(&cfg_contents) + .with_context(|| format!("failed to deserialize config from '{cfg_path}'"))?; - log::info!("using config from {}", cfg_path.display()); + log::info!("using config from {cfg_path}"); let id = files.add(&cfg_path, cfg_contents); @@ -91,10 +88,7 @@ impl ValidConfig { print(diags); if has_errors { - anyhow::bail!( - "failed to validate configuration file {}", - cfg_path.display() - ); + anyhow::bail!("failed to validate configuration file {cfg_path}"); } else { Ok(Self { advisories }) } @@ -123,18 +117,9 @@ pub fn cmd( if fetch_index { s.spawn(|_| { - log::info!("fetching crates.io index..."); - index = Some(match crates_index::Index::new_cargo_default() { - Ok(mut index) => match index.update() { - Ok(_) => Ok(index), - Err(err) => Err(anyhow::anyhow!( - "opened crates.io index but failed to fetch updates: {}", - err - )), - }, - Err(err) => Err(anyhow::anyhow!("failed to open crates.io index: {}", err)), - }); - log::info!("fetched crates.io index"); + log::info!("fetching crates"); + index = Some(ctx.fetch_krates()); + log::info!("fetched crates"); }); } diff --git a/src/cargo-deny/init.rs b/src/cargo-deny/init.rs index 5a045bc0..6a4eb3f6 100644 --- a/src/cargo-deny/init.rs +++ b/src/cargo-deny/init.rs @@ -1,5 +1,5 @@ +use crate::PathBuf; use anyhow::{ensure, Context, Error}; -use std::path::PathBuf; #[derive(clap::Parser, Debug, Clone)] pub struct Args { @@ -21,19 +21,17 @@ pub fn cmd(args: Args, ctx: crate::common::KrateContext) -> Result<(), Error> { // make sure the file does not exist yet ensure!( std::fs::metadata(&cfg_path).is_err(), - "unable to initialize cargo-deny config: '{}' already exists", - cfg_path.display(), + "unable to initialize cargo-deny config: '{cfg_path}' already exists" ); // make sure the path does not terminate in '..'; we need a file name. ensure!( cfg_path.file_name().is_some(), - "unable to create cargo-deny config: '{}' has an invalid filename", - cfg_path.display(), + "unable to create cargo-deny config: '{cfg_path}' has an invalid filename" ); std::fs::write(&cfg_path, CONTENTS).context("unable to write config file")?; - log::info!("saved config file to: {}", cfg_path.display()); + log::info!("saved config file to: {cfg_path}"); Ok(()) } diff --git a/src/cargo-deny/list.rs b/src/cargo-deny/list.rs index 30cdd67d..88a975f9 100644 --- a/src/cargo-deny/list.rs +++ b/src/cargo-deny/list.rs @@ -1,9 +1,7 @@ use anyhow::{Context, Error}; -use cargo_deny::{diag::Files, licenses, Kid}; -use is_terminal::IsTerminal as _; +use cargo_deny::{diag::Files, licenses, Kid, PathBuf}; use nu_ansi_term::Color; use serde::Serialize; -use std::path::PathBuf; #[derive(clap::ValueEnum, Copy, Clone, Debug)] pub enum Layout { @@ -61,15 +59,13 @@ impl ValidConfig { ) -> Result { let (cfg_contents, cfg_path) = match cfg_path { Some(cfg_path) if cfg_path.exists() => ( - std::fs::read_to_string(&cfg_path).with_context(|| { - format!("failed to read config from {}", cfg_path.display()) - })?, + std::fs::read_to_string(&cfg_path) + .with_context(|| format!("failed to read config from {cfg_path}"))?, cfg_path, ), Some(cfg_path) => { log::warn!( - "config path '{}' doesn't exist, falling back to default config", - cfg_path.display() + "config path '{cfg_path}' doesn't exist, falling back to default config" ); return Ok(Self { @@ -87,11 +83,10 @@ impl ValidConfig { } }; - let cfg: Config = toml::from_str(&cfg_contents).with_context(|| { - format!("failed to deserialize config from '{}'", cfg_path.display()) - })?; + let cfg: Config = toml::from_str(&cfg_contents) + .with_context(|| format!("failed to deserialize config from '{cfg_path}'"))?; - log::info!("using config from {}", cfg_path.display()); + log::info!("using config from {cfg_path}"); let id = files.add(&cfg_path, cfg_contents); @@ -126,10 +121,7 @@ impl ValidConfig { Err(diags) => { print(diags); - anyhow::bail!( - "failed to validate configuration file {}", - cfg_path.display() - ); + anyhow::bail!("failed to validate configuration file {cfg_path}"); } } } @@ -248,11 +240,7 @@ pub fn cmd( match args.format { OutputFormat::Human => { let mut output = String::with_capacity(4 * 1024); - let color = match log_ctx.color { - crate::Color::Always => true, - crate::Color::Never => false, - crate::Color::Auto => std::io::stdout().is_terminal(), - }; + let color = crate::common::should_colorize(log_ctx.color, std::io::stdout()); match args.layout { Layout::License => { diff --git a/src/cargo-deny/main.rs b/src/cargo-deny/main.rs index 121b1aed..fcc6fb11 100644 --- a/src/cargo-deny/main.rs +++ b/src/cargo-deny/main.rs @@ -1,9 +1,8 @@ #![allow(clippy::exit)] -use anyhow::{bail, Context, Error}; +use anyhow::{Context as _, Error}; +use cargo_deny::PathBuf; use clap::{Parser, Subcommand, ValueEnum}; -use is_terminal::IsTerminal as _; -use std::path::PathBuf; mod check; mod common; @@ -43,7 +42,7 @@ pub enum Color { fn parse_level(s: &str) -> Result { s.parse::() - .with_context(|| format!("failed to parse level '{}'", s)) + .with_context(|| format!("failed to parse level '{s}'")) } #[derive(Parser)] @@ -86,9 +85,16 @@ pub(crate) struct GraphContext { /// Require Cargo.lock is up to date #[clap(long, action)] pub(crate) locked: bool, - /// Run without accessing the network. If used with the `check` subcommand, this also disables advisory database fetching. + /// Run without accessing the network. + /// + /// If used with the `check` subcommand, this disables advisory database + /// fetching #[clap(long, action)] pub(crate) offline: bool, + /// If set, the crates.io git index is initialized for use in fetching crate information, otherwise it is enabled + /// only if using a cargo < 1.70.0 without the sparse protocol enabled + #[clap(long, action)] + pub(crate) allow_git_index: bool, } /// Lints your project's crate graph @@ -231,11 +237,7 @@ fn real_main() -> Result<(), Error> { let log_level = args.log_level; - let color = match args.color { - Color::Auto => std::io::stderr().is_terminal(), - Color::Always => true, - Color::Never => false, - }; + let color = crate::common::should_colorize(args.color, std::io::stderr()); setup_logger(log_level, args.format, color)?; @@ -247,38 +249,38 @@ fn real_main() -> Result<(), Error> { let cwd = std::env::current_dir().context("unable to determine current working directory")?; - if !cwd.exists() { - bail!("current working directory {} was not found", cwd.display()); - } + anyhow::ensure!( + cwd.exists(), + "current working directory {} was not found", + cwd.display() + ); - if !cwd.is_dir() { - bail!( - "current working directory {} is not a directory", - cwd.display() - ); - } + anyhow::ensure!( + cwd.is_dir(), + "current working directory {} is not a directory", + cwd.display() + ); let man_path = cwd.join("Cargo.toml"); - if !man_path.exists() { - bail!( - "the directory {} doesn't contain a Cargo.toml file", - cwd.display() - ); - } + anyhow::ensure!( + man_path.exists(), + "the directory {} doesn't contain a Cargo.toml file", + cwd.display() + ); - man_path + man_path.try_into().context("non-utf8 path")? }; - if manifest_path.file_name() != Some(std::ffi::OsStr::new("Cargo.toml")) - || !manifest_path.is_file() - { - bail!("--manifest-path must point to a Cargo.toml file"); - } + anyhow::ensure!( + manifest_path.file_name() == Some("Cargo.toml") && manifest_path.is_file(), + "--manifest-path must point to a Cargo.toml file" + ); - if !manifest_path.exists() { - bail!("unable to find cargo manifest {}", manifest_path.display()); - } + anyhow::ensure!( + manifest_path.exists(), + "unable to find cargo manifest {manifest_path}" + ); let krate_ctx = common::KrateContext { manifest_path, @@ -291,6 +293,7 @@ fn real_main() -> Result<(), Error> { frozen: args.ctx.frozen, locked: args.ctx.locked, offline: args.ctx.offline, + allow_git_index: args.ctx.allow_git_index, }; let log_ctx = crate::common::LogContext { diff --git a/src/cargo-deny/stats.rs b/src/cargo-deny/stats.rs index fdfdff70..fa3b13d6 100644 --- a/src/cargo-deny/stats.rs +++ b/src/cargo-deny/stats.rs @@ -1,5 +1,4 @@ use crate::Format; -use is_terminal::IsTerminal as _; use nu_ansi_term::Color; use serde::Serialize; @@ -46,11 +45,7 @@ pub(crate) fn print_stats( Format::Human => { let mut summary = String::new(); - let color = match color { - crate::Color::Auto => std::io::stdout().is_terminal(), - crate::Color::Always => true, - crate::Color::Never => false, - }; + let color = crate::common::should_colorize(color, std::io::stdout()); // If we're using the default or higher log level, just emit // a single line, anything else gets a full table @@ -61,7 +56,7 @@ pub(crate) fn print_stats( } if !summary.is_empty() { - print!("{}", summary); + print!("{summary}"); } } Format::Json => { @@ -86,7 +81,7 @@ fn write_min_stats(mut summary: &mut String, stats: &AllStats, color: bool) { use std::fmt::Write; if let Some(stats) = stats { - write!(&mut summary, "{} ", check).unwrap(); + write!(&mut summary, "{check} ").unwrap(); if color { write!( @@ -156,8 +151,7 @@ fn write_full_stats(summary: &mut String, stats: &AllStats, color: bool) { summary, "{:>column$}: {} errors, {} warnings, {} notes", format!( - "{} {}", - check, + "{check} {}", if stats.errors > 0 { Color::Red.paint("FAILED") } else { @@ -174,11 +168,7 @@ fn write_full_stats(summary: &mut String, stats: &AllStats, color: bool) { writeln!( summary, "{:>column$}: {} errors, {} warnings, {} notes", - format!( - "{} {}", - check, - if stats.errors > 0 { "FAILED" } else { "ok" } - ), + format!("{check} {}", if stats.errors > 0 { "FAILED" } else { "ok" }), stats.errors, stats.warnings, stats.notes + stats.helps, diff --git a/src/cfg.rs b/src/cfg.rs index d4f6e654..9d06425c 100644 --- a/src/cfg.rs +++ b/src/cfg.rs @@ -19,7 +19,7 @@ pub struct Spanned { impl Spanned { #[inline] - pub(crate) fn new(value: T, span: std::ops::Range) -> Self { + pub(crate) const fn new(value: T, span: std::ops::Range) -> Self { Self { value, span } } @@ -208,8 +208,10 @@ pub(crate) fn parse_url( #[cfg(test)] pub(crate) mod test { - use crate::diag::{FileId, Files}; - use std::path::PathBuf; + use crate::{ + diag::{FileId, Files}, + PathBuf, + }; pub(crate) struct ConfigData { pub(crate) config: T, diff --git a/src/lib.rs b/src/lib.rs index 7a664541..9c9c8439 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ #![doc = include_str!("../README.md")] pub use semver::Version; -use std::{cmp, collections::HashMap, fmt}; +use std::{cmp, collections::BTreeMap, fmt}; use url::Url; pub mod advisories; @@ -15,12 +15,10 @@ pub mod sources; #[doc(hidden)] pub mod test_utils; +pub use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf}; pub use cfg::{Spanned, UnvalidatedConfig}; use krates::cm; -pub use krates::{DepKind, Kid, Utf8PathBuf}; - -const CRATES_IO_SPARSE: &str = "sparse+https://index.crates.io/"; -const CRATES_IO_GIT: &str = "registry+https://github.com/rust-lang/crates.io-index"; +pub use krates::{DepKind, Kid}; /// The possible lint levels for the various lints. These function similarly /// to the standard [Rust lint levels](https://doc.rust-lang.org/rustc/lints/levels.html) @@ -50,136 +48,144 @@ const fn lint_deny() -> LintLevel { LintLevel::Deny } -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum SourceKind { +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum Source { /// crates.io, the boolean indicates whether it is a sparse index CratesIo(bool), /// A remote git patch - Git(GitSpec), + Git { spec: GitSpec, url: Url }, /// A remote git index - Registry, + Registry(Url), /// A remote sparse index - Sparse, + Sparse(Url), } -/// Wrapper around the original source url -#[derive(Debug, PartialEq)] -pub struct Source { - pub kind: SourceKind, - url: Option, -} +/// The directory name under which crates sourced from the crates.io sparse +/// registry are placed +const CRATES_IO_SPARSE_DIR: &str = "index.crates.io-6f17d22bba15001f"; impl Source { - fn from_metadata(urls: String) -> anyhow::Result { - if urls == CRATES_IO_GIT { - return Ok(Self { - kind: SourceKind::CratesIo(false), - url: None, - }); - } else if urls == CRATES_IO_SPARSE { - return Ok(Self { - kind: SourceKind::CratesIo(true), - url: None, - }); - } + pub fn crates_io(is_sparse: bool) -> Self { + Self::CratesIo(is_sparse) + } + /// Parses the source url to get its kind + /// + /// Note that the path is the path to the manifest of the package. This is + /// used to determine if the crates.io registry is git or sparse, as, currently, + /// cargo always uses the git registry+ url for crates.io, even if it uses the + /// sparse registry. + /// + /// This method therefore assumes that the crates sources are laid out in the + /// canonical cargo structure, though it can be rooted somewhere other than + /// `CARGO_HOME` + fn from_metadata(urls: String, manifest_path: &Path) -> anyhow::Result { use anyhow::Context as _; - let index = urls.find('+').context("url is not a valid crate source")?; - let mut url = Url::parse(&urls[index + 1..]).context("failed to parse url")?; - let kind = match &urls[..index] { - "sparse" => SourceKind::Sparse, - "registry" => SourceKind::Registry, + let (kind, url_str) = urls + .split_once('+') + .with_context(|| format!("'{urls}' is not a valid crate source"))?; + + match kind { + "sparse" => { + // This code won't ever be hit in current cargo, but could in the future + if urls == tame_index::CRATES_IO_HTTP_INDEX { + Ok(Self::crates_io(true)) + } else { + Url::parse(&urls) + .map(Self::Sparse) + .context("failed to parse url") + } + } + "registry" => { + if url_str == tame_index::CRATES_IO_INDEX { + // registry/src/index.crates.io-6f17d22bba15001f/crate-version/Cargo.toml + let is_sparse = manifest_path.ancestors().nth(2).map_or(false, |dir| { + dir.file_name() + .map_or(false, |dir_name| dir_name == CRATES_IO_SPARSE_DIR) + }); + Ok(Self::crates_io(is_sparse)) + } else { + Url::parse(url_str) + .map(Self::Registry) + .context("failed to parse url") + } + } "git" => { + let mut url = Url::parse(url_str).context("failed to parse url")?; let spec = normalize_git_url(&mut url); - SourceKind::Git(spec) + + Ok(Self::Git { url, spec }) } unknown => anyhow::bail!("unknown source spec '{unknown}' for url {urls}"), - }; - - Ok(Self { - kind, - url: Some(url), - }) + } } #[inline] pub fn is_git(&self) -> bool { - matches!(self.kind, SourceKind::Git(_)) + matches!(self, Self::Git { .. }) } #[inline] pub fn git_spec(&self) -> Option { - if let SourceKind::Git(spec) = self.kind { - Some(spec) - } else { - None - } + let Self::Git { spec, .. } = self else { return None; }; + Some(*spec) } #[inline] pub fn is_registry(&self) -> bool { - matches!( - self.kind, - SourceKind::CratesIo(_) | SourceKind::Registry | SourceKind::Sparse - ) + !self.is_git() } #[inline] pub fn is_crates_io(&self) -> bool { - matches!(self.kind, SourceKind::CratesIo(_)) - } - - #[inline] - pub fn url(&self) -> Option<&Url> { - self.url.as_ref() + matches!(self, Self::CratesIo(_)) } #[inline] pub fn to_rustsec(&self) -> rustsec::package::SourceId { use rustsec::package::SourceId; // TODO: Change this once rustsec supports sparse indices - match (self.kind, &self.url) { - (SourceKind::CratesIo(_), None) => SourceId::default(), - (SourceKind::Registry | SourceKind::Sparse, Some(url)) => { - SourceId::for_registry(url).unwrap() + match self { + Self::CratesIo(_) => SourceId::default(), + Self::Registry(url) => SourceId::for_registry(url).unwrap(), + Self::Sparse(sparse) => { + // There is currently no way to publicly construct a sparse registry + // id other than this method + SourceId::from_url(sparse.as_str()).unwrap() } - _ => unreachable!(), + Self::Git { .. } => unreachable!(), } } #[inline] pub fn matches_rustsec(&self, sid: Option<&rustsec::package::SourceId>) -> bool { let Some(sid) = sid else { return self.is_crates_io(); }; - let Some(ksid) = &self.url else { return false; }; - - if self.is_registry() && sid.is_remote_registry() { - sid.url() == ksid - } else { - false + if !sid.is_remote_registry() { + return false; } + + let (Self::Registry(url) | Self::Sparse(url)) = self else { return false; }; + sid.url() == url } } impl fmt::Display for Source { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match (self.kind, self.url.as_ref()) { - (SourceKind::CratesIo(is_sparse), None) => f.write_str(if is_sparse { - CRATES_IO_SPARSE - } else { - CRATES_IO_GIT - }), - (SourceKind::Git(_), Some(url)) => { + match self { + Self::CratesIo(_) => { + write!(f, "registry+{}", tame_index::CRATES_IO_INDEX) + } + Self::Git { url, .. } => { write!(f, "git+{url}") } - (SourceKind::Registry, Some(url)) => { + Self::Registry(url) => { write!(f, "registry+{url}") } - (SourceKind::Sparse, Some(url)) => { - write!(f, "sparse+{url}") + Self::Sparse(url) => { + write!(f, "{url}") } - _ => unreachable!(), } } } @@ -193,11 +199,11 @@ pub struct Krate { pub authors: Vec, pub repository: Option, pub description: Option, - pub manifest_path: Utf8PathBuf, + pub manifest_path: PathBuf, pub license: Option, - pub license_file: Option, + pub license_file: Option, pub deps: Vec, - pub features: HashMap>, + pub features: BTreeMap>, pub targets: Vec, pub publish: Option>, } @@ -218,8 +224,8 @@ impl Default for Krate { license: None, license_file: None, targets: Vec::new(), - features: HashMap::new(), - manifest_path: Utf8PathBuf::new(), + features: BTreeMap::new(), + manifest_path: PathBuf::new(), repository: None, publish: None, } @@ -247,10 +253,12 @@ impl PartialEq for Krate { impl Eq for Krate {} impl krates::KrateDetails for Krate { + #[inline] fn name(&self) -> &str { &self.name } + #[inline] fn version(&self) -> &semver::Version { &self.version } @@ -261,7 +269,7 @@ impl From for Krate { let source = pkg.source.and_then(|src| { let url = src.to_string(); - Source::from_metadata(url) + Source::from_metadata(url, &pkg.manifest_path) .map_err(|err| { log::warn!( "unable to parse source url for {}:{}: {err}", @@ -323,19 +331,20 @@ impl Krate { pub(crate) fn matches_url(&self, url: &Url, exact: bool) -> bool { let Some(src) = &self.source else { return false }; - // It's irrelevant if it's sparse or not - if src.is_crates_io() { - return url.as_str().ends_with(&CRATES_IO_SPARSE[8..]) - || url.as_str().ends_with(&CRATES_IO_GIT[10..]); - } - - let Some(kurl) = &src.url else { return false; }; - - if kurl.scheme() != url.scheme() || kurl.host() != url.host() { - return false; - } + let kurl = match src { + Source::CratesIo(_is_sparse) => { + // It's irrelevant if it's sparse or not for crates.io, they're the same + // index, just different protocols/kinds + return url + .as_str() + .ends_with(&tame_index::CRATES_IO_HTTP_INDEX[8..]) + || url.as_str().ends_with(&tame_index::CRATES_IO_INDEX[10..]); + } + Source::Sparse(surl) | Source::Registry(surl) | Source::Git { url: surl, .. } => surl, + }; - (exact && kurl.path() == url.path()) || (!exact && kurl.path().starts_with(url.path())) + kurl.host() == url.host() && (exact && kurl.path() == url.path()) + || (!exact && kurl.path().starts_with(url.path())) } #[inline] @@ -468,35 +477,75 @@ pub(crate) fn normalize_git_url(url: &mut Url) -> GitSpec { spec } +/// Helper function to convert a std `PathBuf` to a camino one +#[inline] +#[allow(clippy::disallowed_types)] +pub fn utf8path(pb: std::path::PathBuf) -> anyhow::Result { + use anyhow::Context; + PathBuf::try_from(pb).context("non-utf8 path") +} + #[cfg(test)] mod test { use super::Source; #[test] fn parses_sources() { + let empty_dir = super::Path::new(""); let crates_io_git = Source::from_metadata( - "registry+https://github.com/rust-lang/crates.io-index".to_owned(), + format!("registry+{}", tame_index::CRATES_IO_INDEX), + empty_dir, ) .unwrap(); let crates_io_sparse = - Source::from_metadata("sparse+https://index.crates.io/".to_owned()).unwrap(); - - assert!(crates_io_git.is_registry() && crates_io_sparse.is_registry()); - assert!(crates_io_git.is_crates_io() && crates_io_sparse.is_crates_io()); + Source::from_metadata(tame_index::CRATES_IO_HTTP_INDEX.to_owned(), empty_dir).unwrap(); + let crates_io_sparse_but_git = Source::from_metadata( + format!("registry+{}", tame_index::CRATES_IO_INDEX), + super::Path::new(&format!( + "registry/src/{}/cargo-deny-0.69.0/Cargo.toml", + super::CRATES_IO_SPARSE_DIR + )), + ) + .unwrap(); assert!( - Source::from_metadata("registry+https://my-own-my-precious.com/".to_owned()) - .unwrap() - .is_registry() + crates_io_git.is_registry() + && crates_io_sparse.is_registry() + && crates_io_sparse_but_git.is_registry() ); assert!( - Source::from_metadata("sparse+https://my-registry.rs/".to_owned()) + crates_io_git.is_crates_io() + && crates_io_sparse.is_crates_io() + && crates_io_sparse_but_git.is_crates_io() + ); + + assert!(Source::from_metadata( + "registry+https://my-own-my-precious.com/".to_owned(), + empty_dir + ) + .unwrap() + .is_registry()); + assert!( + Source::from_metadata("sparse+https://my-registry.rs/".to_owned(), empty_dir) .unwrap() .is_registry() ); - let src = Source::from_metadata("git+https://github.com/EmbarkStudios/wasmtime?branch=v6.0.1-profiler#84b8cacceacb585ef53774c3790b2372ba080067".to_owned()).unwrap(); + let src = Source::from_metadata("git+https://github.com/EmbarkStudios/wasmtime?branch=v6.0.1-profiler#84b8cacceacb585ef53774c3790b2372ba080067".to_owned(), empty_dir).unwrap(); assert!(src.is_git()); } + + /// Sanity checks that the crates.io sparse registry still uses the same + /// local directory. Really this should be doing a cargo invocation, but + /// meh, we depend on tame-index to stay up to date + #[test] + fn validate_crates_io_sparse_dir_name() { + assert_eq!( + tame_index::utils::url_to_local_dir(tame_index::CRATES_IO_HTTP_INDEX) + .unwrap() + .dir_name, + super::CRATES_IO_SPARSE_DIR + ); + } } diff --git a/src/licenses/cfg.rs b/src/licenses/cfg.rs index d7ff439e..18c571b9 100644 --- a/src/licenses/cfg.rs +++ b/src/licenses/cfg.rs @@ -23,11 +23,10 @@ use crate::{ diag::{Diagnostic, FileId, Label}, - LintLevel, Spanned, + LintLevel, PathBuf, Spanned, }; use semver::VersionReq; use serde::Deserialize; -use std::path::PathBuf; const fn confidence_threshold() -> f32 { 0.8 diff --git a/src/licenses/diags.rs b/src/licenses/diags.rs index 07f96c6d..63d102fe 100644 --- a/src/licenses/diags.rs +++ b/src/licenses/diags.rs @@ -96,7 +96,7 @@ impl From for Diag { } pub(crate) struct MissingClarificationFile<'a> { - pub(crate) expected: &'a crate::cfg::Spanned, + pub(crate) expected: &'a crate::cfg::Spanned, pub(crate) cfg_file_id: crate::diag::FileId, } diff --git a/src/licenses/gather.rs b/src/licenses/gather.rs index cb8fdbc3..96700a39 100644 --- a/src/licenses/gather.rs +++ b/src/licenses/gather.rs @@ -1,10 +1,8 @@ use super::cfg::{FileSource, ValidClarification, ValidConfig}; use crate::{ diag::{FileId, Files, Label}, - Krate, + Krate, Path, PathBuf, }; -use anyhow::Error; -use krates::{Utf8Path, Utf8PathBuf}; use rayon::prelude::*; use smallvec::SmallVec; use std::{fmt, sync::Arc}; @@ -34,12 +32,12 @@ impl fmt::Debug for FileSource { } } -fn find_license_files(dir: &Utf8Path) -> Result, std::io::Error> { +fn find_license_files(dir: &Path) -> Result, std::io::Error> { let entries = std::fs::read_dir(dir)?; Ok(entries .filter_map(|e| { e.ok().and_then(|e| { - let p = match Utf8PathBuf::from_path_buf(e.path()) { + let p = match PathBuf::from_path_buf(e.path()) { Ok(pb) => pb, Err(e) => { log::warn!("{} contains invalid utf-8, skipping", e.display()); @@ -57,7 +55,7 @@ fn find_license_files(dir: &Utf8Path) -> Result, std::io::Error .collect()) } -fn get_file_source(path: Utf8PathBuf) -> PackFile { +fn get_file_source(path: PathBuf) -> PackFile { use std::io::BufRead; // Normalize on plain newlines to handle terrible Windows conventions @@ -111,7 +109,7 @@ enum PackFileData { } struct PackFile { - path: Utf8PathBuf, + path: PathBuf, data: PackFileData, } @@ -385,9 +383,10 @@ pub struct LicenseStore { } impl LicenseStore { - pub fn from_cache() -> Result { - let store = askalono::Store::from_cache(LICENSE_CACHE) - .map_err(|e| anyhow::anyhow!("failed to load license store: {}", e))?; + pub fn from_cache() -> anyhow::Result { + use anyhow::Context as _; + let store = + askalono::Store::from_cache(LICENSE_CACHE).context("failed to load license store")?; Ok(Self { store }) } @@ -461,7 +460,7 @@ impl Gatherer { .optimize(false) .max_passes(1); - let files_lock = std::sync::Arc::new(std::sync::RwLock::new(files)); + let files_lock = std::sync::Arc::new(parking_lot::RwLock::new(files)); // Retrieve the license expression we'll use to evaluate the user's overall // constraints with. @@ -503,7 +502,7 @@ impl Gatherer { let mut get_span = |key: &'static str| -> (FileId, std::ops::Range) { if let Some(id) = synth_id { - let l = files_lock.read().unwrap(); + let l = files_lock.read(); (synth_id.unwrap(), get_toml_span(key, l.source(id))) } else { // Synthesize a minimal Cargo.toml for reporting diagnostics @@ -516,7 +515,7 @@ impl Gatherer { ); { - let mut fl = files_lock.write().unwrap(); + let mut fl = files_lock.write(); synth_id = Some(fl.add(krate.id.repr.clone(), synth_manifest)); ( synth_id.unwrap(), @@ -677,7 +676,7 @@ impl Gatherer { // Push our synthesized license files toml content to the end of // the other synthesized toml then fixup all of our spans let expr_offset = { - let mut fl = files_lock.write().unwrap(); + let mut fl = files_lock.write(); let (new_source, offset) = { let source = fl.source(id); @@ -720,7 +719,7 @@ impl Gatherer { // Push our synthesized license files toml content to the end of // the other synthesized toml then fixup all of our spans let old_end = { - let mut fl = files_lock.write().unwrap(); + let mut fl = files_lock.write(); let (new_source, old_end) = { let source = fl.source(id); @@ -770,7 +769,7 @@ impl Gatherer { mod test { #[test] fn normalizes_line_endings() { - let pf = super::get_file_source(krates::Utf8PathBuf::from("./tests/LICENSE-RING")); + let pf = super::get_file_source(crate::PathBuf::from("./tests/LICENSE-RING")); let expected = { let text = std::fs::read_to_string("./tests/LICENSE-RING").unwrap(); diff --git a/src/sources.rs b/src/sources.rs index 3a6f8fc9..18b6d6c9 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -77,7 +77,7 @@ pub fn check(ctx: crate::CheckCtx<'_, ValidConfig>, sink: impl Into) continue; }; - // check if the source URL is list of allowed sources + // check if the source URL is in the list of allowed sources let diag: crate::diag::Diag = if let Some(ind) = ctx .cfg .allowed_sources @@ -102,11 +102,10 @@ pub fn check(ctx: crate::CheckCtx<'_, ValidConfig>, sink: impl Into) }, } .into() - } else if let Some((orgt, orgname)) = krate - .source - .as_ref() - .and_then(|s| s.url().and_then(get_org)) - { + } else if let Some((orgt, orgname)) = krate.source.as_ref().and_then(|s| { + let crate::Source::Git { url, .. } = s else { return None; }; + get_org(url) + }) { if let Some(ind) = ctx .cfg .allowed_orgs diff --git a/src/sources/cfg.rs b/src/sources/cfg.rs index ba41957f..da890e4a 100644 --- a/src/sources/cfg.rs +++ b/src/sources/cfg.rs @@ -17,7 +17,7 @@ pub struct Orgs { /// The types of specifiers that can be used on git sources by cargo, in order /// of their specificity from least to greatest -#[derive(Deserialize, PartialEq, Eq, Debug, PartialOrd, Clone, Copy, Default)] +#[derive(Deserialize, PartialEq, Eq, Debug, PartialOrd, Ord, Clone, Copy, Default)] #[serde(rename_all = "snake_case")] pub enum GitSpec { /// Specifies the HEAD of the `master` branch, though eventually this might diff --git a/src/test_utils.rs b/src/test_utils.rs index 59330fe1..e2b0db5b 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -21,7 +21,7 @@ impl<'k> KrateGather<'k> { } pub fn gather(self) -> crate::Krates { - let mut project_dir = std::path::PathBuf::from("./tests/test_data"); + let mut project_dir = crate::PathBuf::from("./tests/test_data"); project_dir.push(self.name); let mut cmd = krates::Cmd::new(); diff --git a/tests/advisories.rs b/tests/advisories.rs index 93c8452e..92a79835 100644 --- a/tests/advisories.rs +++ b/tests/advisories.rs @@ -11,6 +11,21 @@ struct TestCtx { } 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(), ) @@ -24,7 +39,7 @@ fn load() -> TestCtx { advisories::DbSet::load( Some("tests/advisory-db"), vec![], - advisories::Fetch::Disallow, + advisories::Fetch::Disallow(time::Duration::days(100)), ) .unwrap() }; @@ -48,6 +63,7 @@ fn find_by_code<'a>( }) } +/// Validates we emit diagnostics when a vulnerability advisory is detected #[test] fn detects_vulnerabilities() { let TestCtx { dbs, krates } = load(); @@ -56,7 +72,13 @@ fn detects_vulnerabilities() { let diags = tu::gather_diagnostics::(&krates, func_name!(), cfg, |ctx, _, tx| { - advisories::check(ctx, &dbs, Option::::None, tx); + advisories::check( + ctx, + &dbs, + Option::::None, + None, + tx, + ); }); let diag = find_by_code(&diags, "RUSTSEC-2019-0001").unwrap(); @@ -64,6 +86,7 @@ fn detects_vulnerabilities() { insta::assert_json_snapshot!(diag); } +/// Validates we emit diagnostics when an unmaintained advisory is detected #[test] fn detects_unmaintained() { let TestCtx { dbs, krates } = load(); @@ -72,13 +95,20 @@ fn detects_unmaintained() { let diags = tu::gather_diagnostics::(&krates, func_name!(), cfg, |ctx, _, tx| { - advisories::check(ctx, &dbs, Option::::None, tx); + advisories::check( + ctx, + &dbs, + Option::::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(); @@ -87,13 +117,21 @@ fn detects_unsound() { let diags = tu::gather_diagnostics::(&krates, func_name!(), cfg, |ctx, _, tx| { - advisories::check(ctx, &dbs, Option::::None, tx); + advisories::check( + ctx, + &dbs, + Option::::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(); @@ -104,7 +142,13 @@ fn downgrades_lint_levels() { let diags = tu::gather_diagnostics::(&krates, func_name!(), cfg, |ctx, _, tx| { - advisories::check(ctx, &dbs, Option::::None, tx); + advisories::check( + ctx, + &dbs, + Option::::None, + None, + tx, + ); }); let downgraded = [ @@ -115,12 +159,50 @@ fn downgrades_lint_levels() { insta::assert_json_snapshot!(downgraded); } -fn verify_yanked(name: &str, dbs: &advisories::DbSet, krates: &Krates) { +/// 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 cfg = tu::Config::new("yanked = 'deny'\nunmaintained = 'allow'\nvulnerability = 'allow'"); + let dbs = advisories::DbSet { dbs: Vec::new() }; + let diags = - tu::gather_diagnostics::(krates, func_name!(), cfg, |ctx, _, tx| { - advisories::check(ctx, dbs, Option::::None, tx); + tu::gather_diagnostics::(&krates, func_name!(), cfg, |ctx, _, tx| { + advisories::check( + ctx, + &dbs, + Option::::None, + Some(indices), + tx, + ); }); let diags: Vec<_> = diags @@ -128,81 +210,38 @@ fn verify_yanked(name: &str, dbs: &advisories::DbSet, krates: &Krates) { .filter(|v| field_eq!(v, "/fields/message", "detected yanked crate")) .collect(); - insta::assert_json_snapshot!(name, diags); + 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 detects_yanked() { +fn warns_on_index_failures() { let TestCtx { dbs, krates } = load(); - verify_yanked("detects_yanked", &dbs, &krates); -} - -/// We screw around with disk here, only run this if you really want to -#[cfg(target_os = "linux")] -#[test] -#[ignore] -fn detects_yanked_sparse() { - let dbs = { - advisories::DbSet::load( - Some("tests/advisory-db"), - vec![], - advisories::Fetch::Disallow, - ) - .unwrap() - }; - - let mut mdc = krates::Cmd::new(); - mdc.manifest_path("examples/06_advisories/Cargo.toml"); - - let mut cmd: krates::cm::MetadataCommand = mdc.into(); - // Force enable sparse registries - cmd.env("CARGO_REGISTRIES_CRATES_IO_PROTOCOL", "sparse"); - - let krates: Krates = krates::Builder::new() - .build(cmd, krates::NoneFilter) - .unwrap(); + let cfg = tu::Config::new("yanked = 'deny'\nunmaintained = 'allow'\nvulnerability = 'allow'"); - let registry_root = home::cargo_home() - .unwrap() - .join("registry/index/github.com-1ecc6299db9ec823"); - std::fs::remove_dir_all(registry_root).expect("failed to nuke registry"); - verify_yanked("detects_yanked_sparse", &dbs, &krates); -} + let source = cargo_deny::Source::crates_io(true); -/// Again, screwing around on disk, don't run this test unless you really want to -#[cfg(target_os = "linux")] -#[test] -#[ignore] -fn warns_on_index_failures() { - let dbs = { - advisories::DbSet::load( - Some("tests/advisory-db"), - vec![], - advisories::Fetch::Disallow, - ) - .unwrap() + 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: Default::default(), }; - let mut mdc = krates::Cmd::new(); - mdc.manifest_path("examples/06_advisories/Cargo.toml"); - - let mut cmd: krates::cm::MetadataCommand = mdc.into(); - - // Force enable sparse registries - cmd.env("CARGO_REGISTRIES_CRATES_IO_PROTOCOL", "sparse"); - - let krates: Krates = krates::Builder::new() - .build(cmd, krates::NoneFilter) - .unwrap(); - - let cfg = tu::Config::new("yanked = 'deny'\ncrates-io-git-fallback = false\nunmaintained = 'allow'\nvulnerability = 'allow'"); - let registry_root = home::cargo_home().unwrap().join("registry/index"); - std::fs::remove_dir_all(registry_root).expect("failed to nuke registry"); - let diags = tu::gather_diagnostics::(&krates, func_name!(), cfg, |ctx, _, tx| { - advisories::check(ctx, &dbs, Option::::None, tx); + advisories::check( + ctx, + &dbs, + Option::::None, + Some(indices), + tx, + ); }); // This is the number of crates sourced to crates.io @@ -215,6 +254,8 @@ fn warns_on_index_failures() { ); } +/// Validates that we emit a warning if a crate in the graph _does_ match an +/// advisory, however that advisory has been withdrawn #[test] fn warns_on_ignored_and_withdrawn() { let TestCtx { dbs, krates } = load(); @@ -225,7 +266,13 @@ fn warns_on_ignored_and_withdrawn() { let diags = tu::gather_diagnostics::(&krates, func_name!(), cfg, |ctx, _, tx| { - advisories::check(ctx, &dbs, Option::::None, tx); + advisories::check( + ctx, + &dbs, + Option::::None, + None, + tx, + ); }); insta::assert_json_snapshot!(diags @@ -233,3 +280,332 @@ fn warns_on_ignored_and_withdrawn() { .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( + Some("tests/advisory-db"), + 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 +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), 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(10) + ); +} + +/// 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() { + validate_fetch(Fetch::Allow); +} + +/// Validates we can fetch advisory db updates with git +#[test] +fn fetches_with_git() { + 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::index::{local, reqwest}; + + 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 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()) + .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 == 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 krates: Krates = krates::Builder::new() + .with_crates_io_index( + Some(to_path(&pkg_dir).unwrap().join("12_yank_check")), + Some(cargo_home.to_owned().try_into().unwrap()), + None, + ) + .unwrap() + .build(cmd, krates::NoneFilter) + .unwrap(); + + let indices = advisories::Indices::load(&krates, cargo_home.to_owned().try_into().unwrap()); + + let cfg = tu::Config::new("yanked = 'deny'\nunmaintained = 'allow'\nvulnerability = 'allow'"); + + let dbs = advisories::DbSet { dbs: Vec::new() }; + + let diags = + tu::gather_diagnostics::(&krates, func_name!(), cfg, |ctx, _, tx| { + advisories::check( + ctx, + &dbs, + Option::::None, + Some(indices), + tx, + ); + }); + + let diags: Vec<_> = diags + .into_iter() + .filter(|v| field_eq!(v, "/fields/message", "detected yanked crate")) + .collect(); + + insta::assert_json_snapshot!(diags); +} diff --git a/tests/advisory-db/github.com-2f857891b7f43c59 b/tests/advisory-db/github.com-2f857891b7f43c59 deleted file mode 160000 index 222ccf84..00000000 --- a/tests/advisory-db/github.com-2f857891b7f43c59 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 222ccf84ef7d733a6eac71f694a5e14b4d0d0289 diff --git a/tests/advisory-db/github.com-a946fc29ac602819 b/tests/advisory-db/github.com-a946fc29ac602819 new file mode 160000 index 00000000..9cf72357 --- /dev/null +++ b/tests/advisory-db/github.com-a946fc29ac602819 @@ -0,0 +1 @@ +Subproject commit 9cf72357c8c52629d22edd8b4b8d7f7cdeea2504 diff --git a/tests/advisory-db/github.com-c373669cccc50ac0 b/tests/advisory-db/github.com-c373669cccc50ac0 new file mode 160000 index 00000000..1f44d565 --- /dev/null +++ b/tests/advisory-db/github.com-c373669cccc50ac0 @@ -0,0 +1 @@ +Subproject commit 1f44d565d81692a44b8c7af8a80f587e19757f8c diff --git a/tests/bans.rs b/tests/bans.rs index 048fa7ee..f6e9d6f7 100644 --- a/tests/bans.rs +++ b/tests/bans.rs @@ -103,14 +103,14 @@ fn duplicate_graphs() { let krates = KrateGather::new("duplicates").gather(); let cfg = "multiple-versions = 'deny'".into(); - let dup_graphs = std::sync::Arc::new(std::sync::Mutex::new(Vec::new())); + let dup_graphs = std::sync::Arc::new(parking_lot::Mutex::new(Vec::new())); let duped_graphs = dup_graphs.clone(); gather_diagnostics::(&krates, func_name!(), cfg, |ctx, cs, tx| { bans::check( ctx, Some(Box::new(move |dg| { - duped_graphs.lock().unwrap().push(dg); + duped_graphs.lock().push(dg); Ok(()) })), cs, @@ -118,7 +118,7 @@ fn duplicate_graphs() { ); }); - insta::assert_debug_snapshot!(dup_graphs.lock().unwrap()); + insta::assert_debug_snapshot!(dup_graphs.lock()); } /// Ensures that we can allow duplicates generally, but deny them for specific diff --git a/tests/cfg/advisories.toml b/tests/cfg/advisories.toml index 54233eaa..548603f5 100644 --- a/tests/cfg/advisories.toml +++ b/tests/cfg/advisories.toml @@ -1,12 +1,10 @@ [advisories] db-path = "~/.cargo/advisory-dbs" -db-url = "https://github.com/RustSec/advisory-db" +db-urls = ["https://github.com/RustSec/advisory-db"] vulnerability = "deny" unmaintained = "warn" unsound = "warn" yanked = "warn" notice = "warn" -ignore = [ - "RUSTSEC-0000-0000", -] -severity-threshold = "medium" \ No newline at end of file +ignore = ["RUSTSEC-0000-0000"] +severity-threshold = "medium" diff --git a/tests/licenses.rs b/tests/licenses.rs index 69b44894..3d7245b7 100644 --- a/tests/licenses.rs +++ b/tests/licenses.rs @@ -1,5 +1,6 @@ use cargo_deny::{diag, field_eq, func_name, licenses, test_utils as tu, Krates}; -use std::sync::{Arc, Once}; +use parking_lot::Once; +use std::sync::Arc; static mut STORE: Option> = None; static INIT: Once = Once::new(); diff --git a/tests/snapshots/advisories__crates_io_source_replacement.snap b/tests/snapshots/advisories__crates_io_source_replacement.snap new file mode 100644 index 00000000..d669d9ff --- /dev/null +++ b/tests/snapshots/advisories__crates_io_source_replacement.snap @@ -0,0 +1,70 @@ +--- +source: tests/advisories.rs +expression: diags +--- +[ + { + "fields": { + "code": "yanked", + "graphs": [ + { + "Krate": { + "name": "crate-two", + "version": "0.1.0" + }, + "parents": [ + { + "Krate": { + "name": "yank-check", + "version": "0.1.0" + } + } + ] + } + ], + "labels": [ + { + "column": 1, + "line": 2, + "message": "yanked version", + "span": "crate-two 0.1.0 registry+https://github.com/EmbarkStudios/cargo-test-index" + } + ], + "message": "detected yanked crate", + "severity": "error" + }, + "type": "diagnostic" + }, + { + "fields": { + "code": "yanked", + "graphs": [ + { + "Krate": { + "name": "spdx", + "version": "0.3.1" + }, + "parents": [ + { + "Krate": { + "name": "yank-check", + "version": "0.1.0" + } + } + ] + } + ], + "labels": [ + { + "column": 1, + "line": 7, + "message": "yanked version", + "span": "spdx 0.3.1 registry+https://github.com/rust-lang/crates.io-index" + } + ], + "message": "detected yanked crate", + "severity": "error" + }, + "type": "diagnostic" + } +] diff --git a/tests/snapshots/advisories__detects_unsound.snap b/tests/snapshots/advisories__detects_unsound.snap index 7a69334a..c37e83b8 100644 --- a/tests/snapshots/advisories__detects_unsound.snap +++ b/tests/snapshots/advisories__detects_unsound.snap @@ -6,8 +6,10 @@ expression: unsound_diag "fields": { "advisory": { "aliases": [ + "CVE-2019-25010", "CVE-2020-25575", - "CVE-2019-25010" + "GHSA-jq66-xh47-j9f3", + "GHSA-r98r-j25q-rmpr" ], "categories": [], "collection": "crates", diff --git a/tests/snapshots/advisories__detects_vulnerabilities.snap b/tests/snapshots/advisories__detects_vulnerabilities.snap index 6a619dd6..23f35fac 100644 --- a/tests/snapshots/advisories__detects_vulnerabilities.snap +++ b/tests/snapshots/advisories__detects_vulnerabilities.snap @@ -6,7 +6,8 @@ expression: diag "fields": { "advisory": { "aliases": [ - "CVE-2019-15542" + "CVE-2019-15542", + "GHSA-5hp8-35wj-m525" ], "categories": [], "collection": "crates", @@ -58,7 +59,7 @@ expression: diag "Advisory: https://rustsec.org/advisories/RUSTSEC-2019-0001", "Affected versions of this crate did use recursion for serialization of HTML\nDOM trees.\n\nThis allows an attacker to cause abort due to stack overflow by providing\na pathologically nested input.\n\nThe flaw was corrected by serializing the DOM tree iteratively instead.", "Announcement: https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md#210", - "Solution: Upgrade to >=2.1.0" + "Solution: Upgrade to >=2.1.0 (try `cargo update`)" ], "severity": "error" }, diff --git a/tests/snapshots/advisories__detects_yanked.snap b/tests/snapshots/advisories__detects_yanked.snap index 165bfe17..d669d9ff 100644 --- a/tests/snapshots/advisories__detects_yanked.snap +++ b/tests/snapshots/advisories__detects_yanked.snap @@ -9,13 +9,13 @@ expression: diags "graphs": [ { "Krate": { - "name": "ammonia", - "version": "0.7.0" + "name": "crate-two", + "version": "0.1.0" }, "parents": [ { "Krate": { - "name": "advisories", + "name": "yank-check", "version": "0.1.0" } } @@ -25,49 +25,9 @@ expression: diags "labels": [ { "column": 1, - "line": 4, + "line": 2, "message": "yanked version", - "span": "ammonia 0.7.0 registry+https://github.com/rust-lang/crates.io-index" - } - ], - "message": "detected yanked crate", - "severity": "error" - }, - "type": "diagnostic" - }, - { - "fields": { - "code": "yanked", - "graphs": [ - { - "Krate": { - "name": "ammonia", - "version": "1.2.0" - }, - "parents": [ - { - "Krate": { - "name": "artifact_serde", - "version": "0.3.1" - }, - "parents": [ - { - "Krate": { - "name": "advisories", - "version": "0.1.0" - } - } - ] - } - ] - } - ], - "labels": [ - { - "column": 1, - "line": 5, - "message": "yanked version", - "span": "ammonia 1.2.0 registry+https://github.com/rust-lang/crates.io-index" + "span": "crate-two 0.1.0 registry+https://github.com/EmbarkStudios/cargo-test-index" } ], "message": "detected yanked crate", @@ -87,7 +47,7 @@ expression: diags "parents": [ { "Krate": { - "name": "advisories", + "name": "yank-check", "version": "0.1.0" } } @@ -97,7 +57,7 @@ expression: diags "labels": [ { "column": 1, - "line": 154, + "line": 7, "message": "yanked version", "span": "spdx 0.3.1 registry+https://github.com/rust-lang/crates.io-index" } diff --git a/tests/snapshots/advisories__downgrades_lint_levels.snap b/tests/snapshots/advisories__downgrades_lint_levels.snap index e690b0a7..a6c8e011 100644 --- a/tests/snapshots/advisories__downgrades_lint_levels.snap +++ b/tests/snapshots/advisories__downgrades_lint_levels.snap @@ -64,7 +64,8 @@ expression: downgraded "fields": { "advisory": { "aliases": [ - "CVE-2019-15542" + "CVE-2019-15542", + "GHSA-5hp8-35wj-m525" ], "categories": [], "collection": "crates", @@ -116,7 +117,7 @@ expression: downgraded "Advisory: https://rustsec.org/advisories/RUSTSEC-2019-0001", "Affected versions of this crate did use recursion for serialization of HTML\nDOM trees.\n\nThis allows an attacker to cause abort due to stack overflow by providing\na pathologically nested input.\n\nThe flaw was corrected by serializing the DOM tree iteratively instead.", "Announcement: https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md#210", - "Solution: Upgrade to >=2.1.0" + "Solution: Upgrade to >=2.1.0 (try `cargo update`)" ], "severity": "note" }, diff --git a/tests/snapshots/cargo_deny__test__cargo_deny.snap b/tests/snapshots/cargo_deny__test__cargo_deny.snap index 3f73ab3b..79f8779d 100644 --- a/tests/snapshots/cargo_deny__test__cargo_deny.snap +++ b/tests/snapshots/cargo_deny__test__cargo_deny.snap @@ -7,16 +7,11 @@ Cargo plugin to help you manage large dependency graphs Usage: cargo_deny [OPTIONS] Commands: - check - Checks a project's crate graph - fetch - Fetches remote data - init - Creates a cargo-deny config from a template - list - Outputs a listing of all licenses and the crates that use them - help - Print this message or the help of the given subcommand(s) + check Checks a project's crate graph + fetch Fetches remote data + init Creates a cargo-deny config from a template + list Outputs a listing of all licenses and the crates that use them + help Print this message or the help of the given subcommand(s) Options: -L, --log-level @@ -84,7 +79,12 @@ Options: Require Cargo.lock is up to date --offline - Run without accessing the network. If used with the `check` subcommand, this also disables advisory database fetching + Run without accessing the network. + + If used with the `check` subcommand, this disables advisory database fetching + + --allow-git-index + If set, the crates.io git index is initialized for use in fetching crate information, otherwise it is enabled only if using a cargo < 1.70.0 without the sparse protocol enabled -h, --help Print help (see a summary with '-h')