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