use semver::Version;
use std::collections::{btree_map, BTreeMap};
use std::ffi::{OsStr, OsString};
use toml;
use advisory::{self, Advisory, AdvisoryId, AdvisoryWrapper};
use error::{Error, ErrorKind};
use lockfile::Lockfile;
use package::PackageName;
use repository::Repository;
use vulnerability::Vulnerabilities;
pub const PLACEHOLDER_ADVISORY_ID: &str = "RUSTSEC-0000-0000";
#[derive(Debug)]
pub struct AdvisoryDatabase {
advisories: BTreeMap<AdvisoryId, Advisory>,
crates: BTreeMap<PackageName, Vec<AdvisoryId>>,
}
impl AdvisoryDatabase {
#[cfg(feature = "chrono")]
pub fn fetch() -> Result<Self, Error> {
let repo = Repository::fetch_default_repo()?;
Self::from_repository(&repo)
}
pub fn from_repository(repo: &Repository) -> Result<Self, Error> {
let mut advisories = BTreeMap::new();
let mut crates = BTreeMap::new();
for advisory_file in repo.crate_advisories()? {
let AdvisoryWrapper { advisory } = toml::from_str(&advisory_file.read_to_string()?)?;
if !advisory.id.is_rustsec() {
fail!(
ErrorKind::Parse,
"expected a RUSTSEC advisory ID: {}",
advisory.id
);
}
let advisory_path = advisory_file.path().to_owned();
let expected_filename = OsString::from(format!("{}.toml", advisory.id));
if advisory_path.file_name().unwrap() != expected_filename {
fail!(
ErrorKind::Repo,
"expected {} to be named {:?}",
advisory_file.path().display(),
expected_filename
);
}
let advisory_parent_dir = advisory_path.parent().unwrap().file_name().unwrap();
if advisory_parent_dir != OsStr::new(advisory.package.as_str()) {
fail!(
ErrorKind::Repo,
"expected {} to be in {} directory (instead of \"{:?}\")",
advisory.id,
advisory.package,
advisory_parent_dir
);
}
if advisory.id.as_str() != PLACEHOLDER_ADVISORY_ID {
let mut crate_advisories = match crates.entry(advisory.package.clone()) {
btree_map::Entry::Vacant(entry) => entry.insert(vec![]),
btree_map::Entry::Occupied(entry) => entry.into_mut(),
};
crate_advisories.push(advisory.id.clone());
advisories.insert(advisory.id.clone(), advisory.clone());
}
}
Ok(Self { advisories, crates })
}
pub fn find<A: AsRef<AdvisoryId>>(&self, id: A) -> Option<&Advisory> {
self.advisories.get(id.as_ref())
}
pub fn find_by_crate<N: AsRef<PackageName>>(&self, crate_name: N) -> Vec<&Advisory> {
if let Some(ids) = self.crates.get(crate_name.as_ref()) {
ids.iter().map(|id| self.find(&id).unwrap()).collect()
} else {
vec![]
}
}
pub fn advisories_for_crate<N: AsRef<PackageName>>(
&self,
crate_name: N,
version: &Version,
) -> Vec<&Advisory> {
self.find_by_crate(crate_name)
.iter()
.filter(|advisory| {
!advisory
.patched_versions
.iter()
.any(|req| req.matches(version))
})
.map(|a| *a)
.collect()
}
pub fn vulnerabilities(&self, lockfile: &Lockfile) -> Vulnerabilities {
Vulnerabilities::find(self, lockfile)
}
pub fn advisories(&self) -> advisory::Iter {
advisory::Iter(self.advisories.iter())
}
}