blob: 6a41aeca48725d1c0cf28a865b59703190e3811d [file] [log] [blame]
//! Database containing `RustSec` security advisories
mod entries;
mod index;
mod query;
pub use self::query::Query;
use self::{entries::Entries, index::Index};
use crate::{
advisory::{self, Advisory},
collection::Collection,
error::Error,
fs,
vulnerability::Vulnerability,
Lockfile,
};
use std::path::Path;
#[cfg(feature = "git")]
use crate::repository::git;
/// Iterator over entries in the database
pub type Iter<'a> = std::slice::Iter<'a, Advisory>;
/// Database of RustSec security advisories, indexed both by ID and collection
#[derive(Debug)]
pub struct Database {
/// All advisories in the database
advisories: Entries,
/// Index of Rust core vulnerabilities
rust_index: Index,
/// Index of third party crates
crate_index: Index,
/// Information about the last git commit to the database
#[cfg(feature = "git")]
latest_commit: Option<git::Commit>,
}
impl Database {
/// Open [`Database`] located at the given local path
pub fn open(path: &Path) -> Result<Self, Error> {
let mut advisory_paths = vec![];
for collection in Collection::all() {
let collection_path = path.join(collection.as_str());
if let Ok(collection_entry) = fs::read_dir(&collection_path) {
for dir_entry in collection_entry {
let dir_entry = dir_entry?;
if !dir_entry.file_type()?.is_dir() {
continue;
}
for advisory_entry in fs::read_dir(dir_entry.path())? {
let advisory_path = advisory_entry?.path();
let file_name = advisory_path.file_name().and_then(|f| f.to_str());
// skip dotfiles like .DS_Store
if file_name.map_or(false, |f| f.starts_with('.')) {
continue;
}
advisory_paths.push(advisory_path);
}
}
}
}
let mut advisories = Entries::new();
let mut rust_index = Index::new();
let mut crate_index = Index::new();
for path in &advisory_paths {
if let Some(slot) = advisories.load_file(path)? {
let advisory = advisories.get(slot).unwrap();
match advisory.metadata.collection.unwrap() {
Collection::Crates => {
crate_index.insert(&advisory.metadata.package, slot);
}
Collection::Rust => {
rust_index.insert(&advisory.metadata.package, slot);
}
}
}
}
Ok(Self {
advisories,
crate_index,
rust_index,
#[cfg(feature = "git")]
latest_commit: None,
})
}
/// Load [`Database`] from the given [`git::Repository`]
#[cfg(feature = "git")]
pub fn load_from_repo(repo: &git::Repository) -> Result<Self, Error> {
let mut db = Self::open(repo.path())?;
db.latest_commit = Some(repo.latest_commit()?);
Ok(db)
}
/// Fetch the default advisory database from GitHub
#[cfg(feature = "git")]
pub fn fetch() -> Result<Self, Error> {
git::Repository::fetch_default_repo().and_then(|repo| Self::load_from_repo(&repo))
}
/// Look up an advisory by an advisory ID (e.g. "RUSTSEC-YYYY-XXXX")
pub fn get(&self, id: &advisory::Id) -> Option<&Advisory> {
self.advisories.find_by_id(id)
}
/// Query the database according to the given query object
pub fn query(&self, query: &Query) -> Vec<&Advisory> {
// Use indexes if we know a package name and collection
if let Some(name) = &query.package_name {
if let Some(collection) = query.collection {
return match collection {
Collection::Crates => self.crate_index.get(name),
Collection::Rust => self.rust_index.get(name),
}
.map(|slots| {
slots
.map(|slot| self.advisories.get(*slot).unwrap())
.filter(|advisory| query.matches(advisory))
.collect()
})
.unwrap_or_else(Vec::new);
}
}
self.iter()
.filter(|advisory| query.matches(advisory))
.collect()
}
/// Find vulnerabilities in the provided `Lockfile` which match a given query.
pub fn query_vulnerabilities(&self, lockfile: &Lockfile, query: &Query) -> Vec<Vulnerability> {
let mut vulns = vec![];
for package in &lockfile.packages {
let advisories = self.query(&query.clone().package(package));
vulns.extend(
advisories
.iter()
.map(|advisory| Vulnerability::new(advisory, package)),
);
}
vulns
}
/// Scan for vulnerabilities in the provided `Lockfile`.
pub fn vulnerabilities(&self, lockfile: &Lockfile) -> Vec<Vulnerability> {
self.query_vulnerabilities(lockfile, &Query::crate_scope())
}
/// Iterate over all of the advisories in the database
pub fn iter(&self) -> Iter<'_> {
self.advisories.iter()
}
/// Get information about the latest commit to the repo
#[cfg(feature = "git")]
pub fn latest_commit(&self) -> Option<&git::Commit> {
self.latest_commit.as_ref()
}
}
impl IntoIterator for Database {
type Item = Advisory;
type IntoIter = std::vec::IntoIter<Advisory>;
fn into_iter(self) -> Self::IntoIter {
self.advisories.into_iter()
}
}