1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//! Security advisories in the RustSec database

pub mod affected;
pub mod category;
pub mod date;
pub mod id;
pub mod informational;
pub mod keyword;
pub mod linter;
pub mod metadata;
pub mod parser;
pub mod versions;

pub use self::{
    affected::Affected, category::Category, date::Date, id::Id, informational::Informational,
    keyword::Keyword, linter::Linter, metadata::Metadata, versions::Versions,
};
pub use cvss::Severity;

use crate::{
    error::{Error, ErrorKind},
    fs,
};
use serde::{Deserialize, Serialize};
use std::{path::Path, str::FromStr};

/// RustSec Security Advisories
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Advisory {
    /// The `[advisory]` section of a RustSec advisory
    #[serde(rename = "advisory")]
    pub metadata: Metadata,

    /// The (optional) `[affected]` section of a RustSec advisory
    pub affected: Option<Affected>,

    /// Versions related to this advisory which are patched or unaffected.
    pub versions: Versions,
}

impl Advisory {
    /// Load an advisory from a `RUSTSEC-20XX-NNNN.toml` file
    pub fn load_file(path: impl AsRef<Path>) -> Result<Self, Error> {
        let path = path.as_ref();

        let advisory_data = fs::read_to_string(path)
            .map_err(|e| format_err!(ErrorKind::Io, "couldn't open {}: {}", path.display(), e))?;

        advisory_data
            .parse()
            .map_err(|e| format_err!(ErrorKind::Parse, "error parsing {}: {}", path.display(), e))
    }

    /// Get advisory ID
    pub fn id(&self) -> &Id {
        &self.metadata.id
    }

    /// Get advisory title
    pub fn title(&self) -> &str {
        self.metadata.title.as_ref()
    }

    /// Get advisory description
    pub fn description(&self) -> &str {
        self.metadata.description.as_ref()
    }

    /// Get the description of this advisory as HTML rendered from Markdown
    #[cfg(feature = "markdown")]
    pub fn description_html(&self) -> String {
        comrak::markdown_to_html(&self.description(), &Default::default())
    }

    /// Get advisory date
    pub fn date(&self) -> &Date {
        &self.metadata.date
    }

    /// Get the severity of this advisory if it has a CVSS v3 associated
    pub fn severity(&self) -> Option<Severity> {
        self.metadata.cvss.as_ref().map(|cvss| cvss.severity())
    }
}

impl FromStr for Advisory {
    type Err = Error;

    fn from_str(advisory_data: &str) -> Result<Self, Error> {
        let parts = parser::Parts::parse(&advisory_data)?;

        let mut advisory: Self = toml::from_str(&parts.front_matter)?;

        if !advisory.metadata.title.is_empty() {
            fail!(
                ErrorKind::Parse,
                "invalid `title` attribute in advisory TOML"
            );
        }

        if !advisory.metadata.description.is_empty() {
            fail!(
                ErrorKind::Parse,
                "invalid `description` attribute in advisory TOML"
            );
        }

        advisory.metadata.title = parts.title.to_owned();
        advisory.metadata.description = parts.description.to_owned();

        Ok(advisory)
    }
}