| //! 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() |
| } |
| } |