| //! Queries against the RustSec database |
| //! |
| use crate::{ |
| advisory::{Advisory, Severity}, |
| collection::Collection, |
| package::{self, Package}, |
| SourceId, |
| }; |
| use platforms::target::{Arch, OS}; |
| use semver::Version; |
| |
| /// Queries against the RustSec database |
| #[derive(Clone, Debug)] |
| pub struct Query { |
| /// Collection to query against |
| pub(super) collection: Option<Collection>, |
| |
| /// Package name to search for |
| pub(super) package_name: Option<package::Name>, |
| |
| /// Package version to search for |
| package_version: Option<Version>, |
| |
| /// Source of the package advisories should be matched against |
| package_source: Option<SourceId>, |
| |
| /// Severity threshold (i.e. minimum severity) |
| severity: Option<Severity>, |
| |
| /// Target architecture |
| target_arch: Option<Arch>, |
| |
| /// Target operating system |
| target_os: Option<OS>, |
| |
| /// Year associated with the advisory ID |
| year: Option<u32>, |
| |
| /// Query for withdrawn advisories |
| /// (i.e. advisories which were soft-deleted from the database, |
| /// as opposed to yanked crates) |
| withdrawn: Option<bool>, |
| |
| /// Query for informational advisories |
| informational: Option<bool>, |
| } |
| |
| impl Query { |
| /// Create a new query. |
| /// |
| /// This creates a "wildcard" query with no constraints. Use the various |
| /// builder methods of this type to restrict which advisories match. |
| /// |
| /// Note that this differs from [`Query::default()`], which scopes the |
| /// query to crates (i.e. [`Query::crate_scope`]). |
| /// |
| /// When in doubt, use [`Query::default()`]. |
| pub fn new() -> Self { |
| Self { |
| collection: None, |
| package_name: None, |
| package_version: None, |
| package_source: None, |
| severity: None, |
| target_arch: None, |
| target_os: None, |
| year: None, |
| withdrawn: None, |
| informational: None, |
| } |
| } |
| |
| /// Create a new query which uses the default scope rules for crates: |
| /// |
| /// - Only `Collection::Crates` |
| /// - Ignore withdrawn advisories |
| /// - Ignore informational advisories |
| pub fn crate_scope() -> Self { |
| Self::new() |
| .collection(Collection::Crates) |
| .withdrawn(false) |
| .informational(false) |
| } |
| |
| /// Set collection to query against |
| pub fn collection(mut self, collection: Collection) -> Self { |
| self.collection = Some(collection); |
| self |
| } |
| |
| /// Provide a package and use all of its attributes as part of the query |
| pub fn package(mut self, package: &Package) -> Self { |
| self.package_name = Some(package.name.clone()); |
| self.package_version = Some(package.version.clone()); |
| self.package_source = package.source.clone(); |
| self |
| } |
| |
| /// Set package name to search for. |
| pub fn package_name(mut self, name: package::Name) -> Self { |
| self.package_name = Some(name); |
| self |
| } |
| |
| /// Set package version to search for |
| pub fn package_version(mut self, version: Version) -> Self { |
| self.package_version = Some(version); |
| self |
| } |
| |
| /// Set package source (e.g. registry) where this package is located |
| pub fn package_source(mut self, source: SourceId) -> Self { |
| self.package_source = Some(source); |
| self |
| } |
| |
| /// Set minimum severity threshold according to the CVSS |
| /// Qualitative Severity Rating Scale. |
| /// |
| /// Vulnerabilities without associated CVSS information will always |
| /// match regardless of what this is set to. |
| pub fn severity(mut self, severity: Severity) -> Self { |
| self.severity = Some(severity); |
| self |
| } |
| |
| /// Set target architecture |
| pub fn target_arch(mut self, arch: Arch) -> Self { |
| self.target_arch = Some(arch); |
| self |
| } |
| |
| /// Set target operating system |
| pub fn target_os(mut self, os: OS) -> Self { |
| self.target_os = Some(os); |
| self |
| } |
| |
| /// Query for vulnerabilities occurring in a specific year. |
| pub fn year(mut self, year: u32) -> Self { |
| self.year = Some(year); |
| self |
| } |
| |
| /// Query for withdrawn advisories. |
| /// |
| /// By default they will be omitted from query results. |
| pub fn withdrawn(mut self, setting: bool) -> Self { |
| self.withdrawn = Some(setting); |
| self |
| } |
| |
| /// Query for informational advisories. By default they will be omitted |
| /// from query results. |
| pub fn informational(mut self, setting: bool) -> Self { |
| self.informational = Some(setting); |
| self |
| } |
| |
| /// Does this query match a given advisory? |
| pub fn matches(&self, advisory: &Advisory) -> bool { |
| if let Some(collection) = self.collection { |
| if Some(collection) != advisory.metadata.collection { |
| return false; |
| } |
| } |
| |
| if let Some(package_name) = &self.package_name { |
| if package_name != &advisory.metadata.package { |
| return false; |
| } |
| } |
| |
| if let Some(package_version) = &self.package_version { |
| if !advisory.versions.is_vulnerable(package_version) { |
| return false; |
| } |
| } |
| |
| if let Some(package_source) = &self.package_source { |
| let advisory_source = advisory |
| .metadata |
| .source |
| .as_ref() |
| .cloned() |
| .unwrap_or_default(); |
| |
| // TODO(tarcieri): better source comparison? |
| if advisory_source.kind() != package_source.kind() |
| || advisory_source.url() != package_source.url() |
| { |
| return false; |
| } |
| } |
| |
| if let Some(severity_threshold) = self.severity { |
| if let Some(advisory_severity) = advisory.severity() { |
| if advisory_severity < severity_threshold { |
| return false; |
| } |
| } |
| } |
| |
| if let Some(affected) = &advisory.affected { |
| if let Some(target_arch) = self.target_arch { |
| if !affected.arch.is_empty() && !affected.arch.contains(&target_arch) { |
| return false; |
| } |
| } |
| |
| if let Some(target_os) = self.target_os { |
| if !affected.os.is_empty() && !affected.os.contains(&target_os) { |
| return false; |
| } |
| } |
| } |
| |
| if let Some(query_year) = self.year { |
| if let Some(advisory_year) = advisory.metadata.id.year() { |
| if query_year != advisory_year { |
| return false; |
| } |
| } |
| } |
| |
| if let Some(withdrawn) = self.withdrawn { |
| if withdrawn != advisory.metadata.withdrawn.is_some() { |
| return false; |
| } |
| } |
| |
| if let Some(informational) = self.informational { |
| if informational != advisory.metadata.informational.is_some() { |
| return false; |
| } |
| } |
| |
| true |
| } |
| } |
| |
| impl Default for Query { |
| fn default() -> Query { |
| Query::crate_scope() |
| } |
| } |