use crate::{
error::{Error, ErrorKind},
lock::acquire_cargo_package_lock,
prelude::*,
};
use std::{
fs,
path::{Path, PathBuf},
};
use tame_index::index::RemoteGitIndex;
pub const COLLECTIONS: &[rustsec::Collection] =
&[rustsec::Collection::Crates, rustsec::Collection::Rust];
pub struct Linter {
repo_path: PathBuf,
crates_index: RemoteGitIndex,
advisory_db: rustsec::Database,
invalid_advisories: usize,
skip_namecheck: Option<String>,
}
impl Linter {
pub fn new(
repo_path: impl Into<PathBuf>,
skip_namecheck: Option<String>,
) -> Result<Self, Error> {
let repo_path = repo_path.into();
let cargo_package_lock = acquire_cargo_package_lock()?;
let mut crates_index = RemoteGitIndex::new(
tame_index::GitIndex::new(tame_index::IndexLocation::new(
tame_index::IndexUrl::CratesIoGit,
))?,
&cargo_package_lock,
)?;
crates_index.fetch(&cargo_package_lock)?;
let advisory_db = rustsec::Database::open(&repo_path)?;
Ok(Self {
repo_path,
crates_index,
advisory_db,
invalid_advisories: 0,
skip_namecheck,
})
}
pub fn advisory_db(&self) -> &rustsec::Database {
&self.advisory_db
}
pub fn lint(mut self) -> Result<usize, Error> {
for collection in COLLECTIONS {
for crate_entry in fs::read_dir(self.repo_path.join(collection.as_str())).unwrap() {
let crate_dir = crate_entry.unwrap().path();
if !crate_dir.is_dir() {
fail!(
ErrorKind::RustSec,
"unexpected file in `{}`: {}",
collection,
crate_dir.display()
);
}
for advisory_entry in crate_dir.read_dir().unwrap() {
let advisory_path = advisory_entry.unwrap().path();
self.lint_advisory(*collection, &advisory_path)?;
}
}
}
Ok(self.invalid_advisories)
}
fn lint_advisory(
&mut self,
collection: rustsec::Collection,
advisory_path: &Path,
) -> Result<(), Error> {
if !advisory_path.is_file() {
fail!(
ErrorKind::RustSec,
"unexpected entry in `{}`: {}",
collection,
advisory_path.display()
);
}
let advisory = rustsec::Advisory::load_file(advisory_path)?;
if collection == rustsec::Collection::Crates {
self.crates_io_lints(&advisory)?;
}
let lint_result = rustsec::advisory::Linter::lint_file(advisory_path)?;
if lint_result.errors().is_empty() {
status_ok!("Linted", "ok: {}", advisory_path.display());
} else {
self.invalid_advisories += 1;
status_err!(
"{} contained the following lint errors:",
advisory_path.display()
);
for error in lint_result.errors() {
println!(" - {}", error);
}
}
Ok(())
}
fn crates_io_lints(&mut self, advisory: &rustsec::Advisory) -> Result<(), Error> {
if !self.name_is_skipped(advisory.metadata.package.as_str())
&& !self.name_exists_on_crates_io(advisory.metadata.package.as_str())
{
self.invalid_advisories += 1;
fail!(
ErrorKind::CratesIo,
"crates.io package name does not match package name in advisory for {} in {}",
advisory.metadata.package.as_str(),
advisory.metadata.id
);
}
Ok(())
}
fn name_is_skipped(&self, package_name: &str) -> bool {
match &self.skip_namecheck {
Some(skips) => skips.split(',').any(|a| a == package_name),
None => false,
}
}
fn name_exists_on_crates_io(&self, name: &str) -> bool {
if let Ok(Some(crate_)) = self.crates_index.krate(
name.try_into().unwrap(),
true,
&acquire_cargo_package_lock().unwrap(),
) {
crate_.name() == name
} else {
false
}
}
}