diff --git a/Cargo.lock b/Cargo.lock
index 9f0b0c81..02c314ca 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -246,6 +246,7 @@ dependencies = [
"clap",
"crates-index",
"ctor",
+ "curl",
"env_logger",
"home",
"libc",
@@ -1948,9 +1949,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
-version = "0.9.93"
+version = "0.9.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d"
+checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f"
dependencies = [
"cc",
"libc",
diff --git a/Cargo.toml b/Cargo.toml
index a26cba39..f6eb614a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,6 +12,7 @@ anyhow = { version = "1.0", features = ["backtrace"] }
cargo_metadata = "0.18"
clap = { version = "4.4", features = ["cargo", "derive", "wrap_help"] }
crates-index = { version = "2.3", features = ["git-https"] }
+curl = "0.4"
env_logger = "0.10"
home = "0.5"
log = "0.4"
diff --git a/README.md b/README.md
index 575984b0..266b402b 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +4,13 @@
`cargo-unmaintained` is similar to [`cargo-audit`]. However, `cargo-unmaintained` finds unmaintained packages automatically using heuristics, rather than rely on users to manually submit them to the [RustSec Advisory Database].
-`cargo-unmaintained` defines an unmaintained package X as one that satisfies either 1 or 2 below:
+`cargo-unmaintained` defines an unmaintained package X as one that satisfies one of 1 through 3 below:
-1. X's repository is archived (see [Notes] below).
+1. X's repository does not exist.
-2. Both a and b below.
+2. X's repository is archived (see [Notes] below).
+
+3. Both a and b below.
a. X depends on a version of a package Y that is incompatible with the Y's latest version.
@@ -24,7 +26,11 @@ As of 2023-11-15, the RustSec Advisory Database contains 87 active advisories fo
- The purpose of condition 2(b) is to give package maintainers a chance to update their packages. That is, an incompatible upgrade to one of X's dependencies could require time-consuming changes to X. Without this check, `cargo-unmaintained` would produce many false positives.
-- Of the 35 packages in the RustSec Advisory Database _not_ identified by `cargo-unmaintained`, 6 do not build, 9 are unarchived leaves, and 2 were updated within the past 365 days. The remaining 18 were not identified for other reasons.
+- Of the 34 packages in the RustSec Advisory Database _not_ identified by `cargo-unmaintained`:
+ - 6 do not build.
+ - 8 are existent, unarchived leaves.
+ - 2 were updated within the past 365 days.
+ - 17 were not identified for other reasons.
## Output
diff --git a/src/curl.rs b/src/curl.rs
new file mode 100644
index 00000000..6327ec87
--- /dev/null
+++ b/src/curl.rs
@@ -0,0 +1,14 @@
+use anyhow::Result;
+use curl::easy::Easy;
+
+pub(crate) fn existence(url: &str) -> Result> {
+ let mut handle = Easy::new();
+ handle.url(url)?;
+ handle.transfer().perform()?;
+ let response_code = handle.response_code()?;
+ match response_code {
+ 200 => Ok(Some(true)),
+ 404 => Ok(Some(false)),
+ _ => Ok(None),
+ }
+}
diff --git a/src/github.rs b/src/github.rs
index 663a15e5..f93f154f 100644
--- a/src/github.rs
+++ b/src/github.rs
@@ -1,9 +1,11 @@
+use super::RepoStatus;
use anyhow::{anyhow, bail, Context, Result};
use once_cell::sync::Lazy;
use regex::Regex;
use std::{
cell::RefCell,
collections::HashMap,
+ convert::Infallible,
fs::read_to_string,
rc::Rc,
time::{Duration, SystemTime},
@@ -38,14 +40,18 @@ pub(crate) fn load_token(path: &str) -> Result<()> {
})
}
-pub(crate) fn archival_status(url: &str) -> Result > {
+pub(crate) fn archival_status(url: &str) -> Result >> {
let (url, owner_slash_repo, owner, repo) = match_github_url(url)?;
let Some(repository) = repository(owner_slash_repo, owner, repo)? else {
- return Ok(None);
+ return Ok(Some(RepoStatus::Nonexistent));
};
- Ok(Some((url, repository.archived.unwrap_or_default())))
+ if repository.archived.unwrap_or_default() {
+ Ok(Some(RepoStatus::Archived(url)))
+ } else {
+ Ok(None)
+ }
}
pub(crate) fn timestamp(url: &str) -> Result > {
@@ -112,20 +118,27 @@ fn repository(
.clone()),
Err(error) => {
repository_cache.insert(owner_slash_repo.to_owned(), None);
- Err(error)
+ if let octocrab::Error::GitHub { source, .. } = &error {
+ if source.message == "Not Found" {
+ Ok(None)
+ } else {
+ Err(error.into())
+ }
+ } else {
+ Err(error.into())
+ }
}
}
})
}
#[cfg_attr(dylint_lib = "general", allow(non_local_effect_before_error_return))]
-fn repository_uncached(owner: &str, repo: &str) -> Result {
+fn repository_uncached(owner: &str, repo: &str) -> octocrab::Result {
RT.block_on(async {
let octocrab = octocrab::instance();
octocrab.repos(owner, repo).get().await
})
- .map_err(Into::into)
}
fn match_github_url(url: &str) -> Result<(&str, &str, &str, &str)> {
diff --git a/src/main.rs b/src/main.rs
index e46c1c36..da94e13b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -14,6 +14,7 @@ use regex::Regex;
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
+ convert::Infallible,
env::{args, var},
ffi::OsStr,
fs::{read_to_string, remove_dir_all, File},
@@ -27,6 +28,7 @@ use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use toml::{Table, Value};
use walkdir::WalkDir;
+mod curl;
mod github;
mod opts;
mod verbose;
@@ -146,6 +148,17 @@ impl<'a, T> RepoStatus<'a, T> {
}
}
+impl<'a> RepoStatus<'a, Infallible> {
+ fn lift(&self) -> RepoStatus<'a, T> {
+ match self {
+ Self::Uncloneable => RepoStatus::Uncloneable,
+ Self::Nonexistent => RepoStatus::Nonexistent,
+ Self::Success(_, _) => unreachable!(),
+ Self::Archived(url) => RepoStatus::Archived(url),
+ }
+ }
+}
+
/// Multiples of `max_age` that cause the color to go completely from yellow to red.
const SATURATION_MULTIPLIER: u64 = 3;
@@ -356,11 +369,20 @@ fn unmaintained() -> Result {
);
for pkg in packages {
- if var("GITHUB_TOKEN_PATH").is_ok() {
- if let Some((url, true)) = archival_status(pkg)? {
+ if let Some(url) = &pkg.repository {
+ if var("GITHUB_TOKEN_PATH").is_ok() && url.starts_with("https://github.com/") {
+ if let Some(status) = archival_status(&pkg.name, url)? {
+ unnmaintained_pkgs.push(UnmaintainedPkg {
+ pkg,
+ repo_age: status.lift(),
+ outdated_deps: Vec::new(),
+ });
+ continue;
+ }
+ } else if let Some(false) = existence(&pkg.name, url)? {
unnmaintained_pkgs.push(UnmaintainedPkg {
pkg,
- repo_age: RepoStatus::Archived(url),
+ repo_age: RepoStatus::Nonexistent,
outdated_deps: Vec::new(),
});
continue;
@@ -495,25 +517,25 @@ fn build_metadata_latest_version_map(metadata: &Metadata) -> HashMap Result> {
- let Some(url) = &pkg.repository else {
- return Ok(None);
- };
-
- if !url.starts_with("https://github.com/") {
- return Ok(None);
- }
-
+fn archival_status<'a>(name: &str, url: &'a str) -> Result >> {
verbose::wrap!(
|| github::archival_status(url).or_else(|error| {
- warn!(
- "failed to determine `{}` archival status: {}",
- pkg.name, error
- );
+ warn!("failed to determine `{}` archival status: {}", name, error);
Ok(None)
}),
"archival status of `{}` using GitHub API",
- pkg.name
+ name
+ )
+}
+
+fn existence(name: &str, url: &str) -> Result > {
+ verbose::wrap!(
+ || curl::existence(url).or_else(|error| {
+ warn!("failed to determine `{}` existence: {}", name, error);
+ Ok(None)
+ }),
+ "existence of `{}` using HTTP request",
+ name
)
}
diff --git a/tests/cases/hyperfine.with_token.stdout b/tests/cases/hyperfine.with_token.stdout
index 4a3fa828..f7e547dc 100644
--- a/tests/cases/hyperfine.with_token.stdout
+++ b/tests/cases/hyperfine.with_token.stdout
@@ -1,3 +1,4 @@
+fuchsia-cprng (no repository)
rdrand (https://github.com/nagisa/rust_rdrand updated [..] days ago)
rand_core (requirement: ^0.3, version used: 0.3.1, latest: [..])
atty (https://github.com/softprops/atty updated [..] days ago)
diff --git a/tests/cases/hyperfine.without_token.stdout b/tests/cases/hyperfine.without_token.stdout
index 4a3fa828..f7e547dc 100644
--- a/tests/cases/hyperfine.without_token.stdout
+++ b/tests/cases/hyperfine.without_token.stdout
@@ -1,3 +1,4 @@
+fuchsia-cprng (no repository)
rdrand (https://github.com/nagisa/rust_rdrand updated [..] days ago)
rand_core (requirement: ^0.3, version used: 0.3.1, latest: [..])
atty (https://github.com/softprops/atty updated [..] days ago)
diff --git a/tests/rustsec_comparison.with_token.stdout b/tests/rustsec_comparison.with_token.stdout
index d109af26..350fd193 100644
--- a/tests/rustsec_comparison.with_token.stdout
+++ b/tests/rustsec_comparison.with_token.stdout
@@ -24,7 +24,7 @@ Error: found no packages matching `stream-cipher`
```
safe-nd...found
ffi_utils...found
-fake_clock...leaf
+fake_clock...found
safe_bindgen...found
quic-p2p...found
routing...found
@@ -125,8 +125,7 @@ not found - error (6)
safe-api https://rustsec.org/advisories/RUSTSEC-2021-0024.html
miscreant https://rustsec.org/advisories/RUSTSEC-2021-0062.html
cargo-download https://rustsec.org/advisories/RUSTSEC-2021-0133.html
-not found - leaf (9)
- fake_clock https://rustsec.org/advisories/RUSTSEC-2020-0065.html
+not found - leaf (8)
aesni https://rustsec.org/advisories/RUSTSEC-2021-0059.html
aes-soft https://rustsec.org/advisories/RUSTSEC-2021-0060.html
aes-ctr https://rustsec.org/advisories/RUSTSEC-2021-0061.html
@@ -157,7 +156,7 @@ not found - other (18)
xsalsa20poly1305 https://rustsec.org/advisories/RUSTSEC-2023-0037.html
users https://rustsec.org/advisories/RUSTSEC-2023-0040.html
fehler https://rustsec.org/advisories/RUSTSEC-2023-0067.html
-found (52)
+found (53)
lz4-compress https://rustsec.org/advisories/RUSTSEC-2017-0007.html
tempdir https://rustsec.org/advisories/RUSTSEC-2018-0017.html
boxfnonce https://rustsec.org/advisories/RUSTSEC-2019-0040.html
@@ -165,6 +164,7 @@ found (52)
failure https://rustsec.org/advisories/RUSTSEC-2020-0036.html
safe-nd https://rustsec.org/advisories/RUSTSEC-2020-0063.html
ffi_utils https://rustsec.org/advisories/RUSTSEC-2020-0064.html
+ fake_clock https://rustsec.org/advisories/RUSTSEC-2020-0065.html
safe_bindgen https://rustsec.org/advisories/RUSTSEC-2020-0066.html
quic-p2p https://rustsec.org/advisories/RUSTSEC-2020-0067.html
routing https://rustsec.org/advisories/RUSTSEC-2020-0076.html
diff --git a/tests/rustsec_comparison.without_token.stdout b/tests/rustsec_comparison.without_token.stdout
index 1dd04b65..43551f7d 100644
--- a/tests/rustsec_comparison.without_token.stdout
+++ b/tests/rustsec_comparison.without_token.stdout
@@ -24,7 +24,7 @@ Error: found no packages matching `stream-cipher`
```
safe-nd...found
ffi_utils...found
-fake_clock...leaf
+fake_clock...found
safe_bindgen...found
quic-p2p...found
routing...found
@@ -125,9 +125,8 @@ not found - error (6)
safe-api https://rustsec.org/advisories/RUSTSEC-2021-0024.html
miscreant https://rustsec.org/advisories/RUSTSEC-2021-0062.html
cargo-download https://rustsec.org/advisories/RUSTSEC-2021-0133.html
-not found - leaf (10)
+not found - leaf (9)
boxfnonce https://rustsec.org/advisories/RUSTSEC-2019-0040.html
- fake_clock https://rustsec.org/advisories/RUSTSEC-2020-0065.html
aesni https://rustsec.org/advisories/RUSTSEC-2021-0059.html
aes-soft https://rustsec.org/advisories/RUSTSEC-2021-0060.html
aes-ctr https://rustsec.org/advisories/RUSTSEC-2021-0061.html
@@ -166,12 +165,13 @@ not found - other (24)
xsalsa20poly1305 https://rustsec.org/advisories/RUSTSEC-2023-0037.html
users https://rustsec.org/advisories/RUSTSEC-2023-0040.html
fehler https://rustsec.org/advisories/RUSTSEC-2023-0067.html
-found (43)
+found (44)
lz4-compress https://rustsec.org/advisories/RUSTSEC-2017-0007.html
tempdir https://rustsec.org/advisories/RUSTSEC-2018-0017.html
bigint https://rustsec.org/advisories/RUSTSEC-2020-0025.html
safe-nd https://rustsec.org/advisories/RUSTSEC-2020-0063.html
ffi_utils https://rustsec.org/advisories/RUSTSEC-2020-0064.html
+ fake_clock https://rustsec.org/advisories/RUSTSEC-2020-0065.html
safe_bindgen https://rustsec.org/advisories/RUSTSEC-2020-0066.html
quic-p2p https://rustsec.org/advisories/RUSTSEC-2020-0067.html
routing https://rustsec.org/advisories/RUSTSEC-2020-0076.html