[go: up one dir, main page]

cvss/
cvss.rs

1use alloc::boxed::Box;
2use alloc::str::FromStr;
3use core::fmt;
4
5#[cfg(feature = "v3")]
6use crate::v3;
7#[cfg(feature = "v4")]
8use crate::v4;
9use crate::{
10    Severity,
11    error::{Error, Result},
12};
13use alloc::borrow::ToOwned;
14#[cfg(feature = "serde")]
15use {
16    alloc::string::String,
17    alloc::string::ToString,
18    serde::{Deserialize, Serialize, de, ser},
19};
20
21/// Prefix used by all CVSS strings
22pub const PREFIX: &str = "CVSS";
23
24/// A CVSS vector
25#[derive(Clone, PartialEq, Eq, Debug)]
26#[non_exhaustive]
27pub enum Cvss {
28    #[cfg(feature = "v3")]
29    /// A CVSS 3.0 base vector
30    CvssV30(v3::Base),
31    #[cfg(feature = "v3")]
32    /// A CVSS 3.1 base vector
33    CvssV31(v3::Base),
34    #[cfg(feature = "v4")]
35    /// A CVSS 4.0 vector
36    CvssV40(v4::Vector),
37}
38
39impl Cvss {
40    /// Get the score of this CVSS vector
41    ///
42    /// The different versions of CVSS have dedicated `Score` types.
43    /// For CVSSv4 specifically, the dedicated type includes the nomenclature information.
44    #[cfg(feature = "std")]
45    pub fn score(&self) -> f64 {
46        match self {
47            #[cfg(feature = "v3")]
48            Self::CvssV30(base) => base.score().value(),
49            #[cfg(feature = "v3")]
50            Self::CvssV31(base) => base.score().value(),
51            #[cfg(feature = "v4")]
52            Self::CvssV40(vector) => vector.score().value(),
53        }
54    }
55
56    /// Get the severity of this CVSS vector
57    #[cfg(feature = "std")]
58    pub fn severity(&self) -> Severity {
59        match self {
60            #[cfg(feature = "v3")]
61            Self::CvssV30(base) => base.score().severity(),
62            #[cfg(feature = "v3")]
63            Self::CvssV31(base) => base.score().severity(),
64            #[cfg(feature = "v4")]
65            Self::CvssV40(vector) => vector.score().severity(),
66        }
67    }
68
69    /// Get an iterator over all defined metrics
70    pub fn metrics(&self) -> Box<dyn Iterator<Item = (MetricType, &dyn fmt::Debug)> + '_> {
71        match self {
72            #[cfg(feature = "v3")]
73            Self::CvssV30(base) => Box::new(base.metrics().map(|(m, v)| (MetricType::V3(m), v))),
74            #[cfg(feature = "v3")]
75            Self::CvssV31(base) => Box::new(base.metrics().map(|(m, v)| (MetricType::V3(m), v))),
76            #[cfg(feature = "v4")]
77            Self::CvssV40(vector) => {
78                Box::new(vector.metrics().map(|(m, v)| (MetricType::V4(m), v)))
79            }
80        }
81    }
82}
83
84#[cfg(feature = "serde")]
85#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
86impl<'de> Deserialize<'de> for Cvss {
87    fn deserialize<D: de::Deserializer<'de>>(
88        deserializer: D,
89    ) -> core::result::Result<Self, D::Error> {
90        String::deserialize(deserializer)?
91            .parse()
92            .map_err(de::Error::custom)
93    }
94}
95
96#[cfg(feature = "serde")]
97#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
98impl Serialize for Cvss {
99    fn serialize<S: ser::Serializer>(
100        &self,
101        serializer: S,
102    ) -> core::result::Result<S::Ok, S::Error> {
103        self.to_string().serialize(serializer)
104    }
105}
106
107impl FromStr for Cvss {
108    type Err = Error;
109
110    fn from_str(s: &str) -> Result<Self> {
111        // Parse the prefix and select the right vector parser
112        let (id, _) = s.split_once('/').ok_or(Error::InvalidComponent {
113            component: s.to_owned(),
114        })?;
115        let (prefix, version) = id.split_once(':').ok_or(Error::InvalidComponent {
116            component: id.to_owned(),
117        })?;
118        let (major_version, minor_version) =
119            version.split_once('.').ok_or(Error::InvalidComponent {
120                component: id.to_owned(),
121            })?;
122
123        match (prefix, major_version, minor_version) {
124            #[cfg(feature = "v3")]
125            (PREFIX, "3", "0") => v3::Base::from_str(s).map(Self::CvssV30),
126            #[cfg(feature = "v3")]
127            (PREFIX, "3", "1") => v3::Base::from_str(s).map(Self::CvssV31),
128            #[cfg(feature = "v4")]
129            (PREFIX, "4", "0") => v4::Vector::from_str(s).map(Self::CvssV40),
130            (PREFIX, _, _) => Err(Error::UnsupportedVersion {
131                version: version.to_owned(),
132            }),
133            (_, _, _) => Err(Error::InvalidPrefix {
134                prefix: prefix.to_owned(),
135            }),
136        }
137    }
138}
139
140impl fmt::Display for Cvss {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        match self {
143            #[cfg(feature = "v3")]
144            Self::CvssV30(base) => write!(f, "{}", base),
145            #[cfg(feature = "v3")]
146            Self::CvssV31(base) => write!(f, "{}", base),
147            #[cfg(feature = "v4")]
148            Self::CvssV40(vector) => write!(f, "{}", vector),
149        }
150    }
151}
152
153/// Metric type (across CVSS versions)
154#[non_exhaustive]
155#[derive(Copy, Clone, Debug, PartialEq, Eq)]
156pub enum MetricType {
157    V3(v3::MetricType),
158    V4(v4::MetricType),
159}
160
161impl MetricType {
162    /// Get the name of this metric (i.e. acronym)
163    pub fn name(self) -> &'static str {
164        match self {
165            Self::V3(m) => m.name(),
166            Self::V4(m) => m.name(),
167        }
168    }
169
170    /// Get a description of this metric.
171    pub fn description(self) -> &'static str {
172        match self {
173            Self::V3(m) => m.description(),
174            Self::V4(m) => m.description(),
175        }
176    }
177}
178
179#[cfg(all(feature = "std", test))]
180mod tests {
181    use super::{Cvss, Error};
182    use alloc::borrow::ToOwned;
183
184    #[test]
185    #[cfg(feature = "v3")]
186    fn test_parse_v3() {
187        let vector = "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N".parse::<Cvss>();
188        assert!(vector.is_ok());
189
190        let vector = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N".parse::<Cvss>();
191        assert!(vector.is_ok());
192    }
193
194    #[test]
195    #[cfg(feature = "v4")]
196    fn test_parse_v4() {
197        let vector = "CVSS:4.0/AV:P/AC:H/AT:P/PR:L/UI:P/VC:H/VI:H/VA:H/SC:L/SI:L/SA:L/E:A/S:P/AU:Y/R:A/V:D/RE:L/U:Red".parse::<Cvss>();
198        assert!(vector.is_ok());
199    }
200
201    #[test]
202    fn test_parse_invalid() {
203        let err = "CVSS:5.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N"
204            .parse::<Cvss>()
205            .unwrap_err();
206        assert_eq!(
207            err,
208            Error::UnsupportedVersion {
209                version: "5.0".to_owned()
210            }
211        );
212        let err = "CSS:4.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N"
213            .parse::<Cvss>()
214            .unwrap_err();
215        assert_eq!(
216            err,
217            Error::InvalidPrefix {
218                prefix: "CSS".to_owned()
219            }
220        );
221        let err = "garbage/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N"
222            .parse::<Cvss>()
223            .unwrap_err();
224        assert_eq!(
225            err,
226            Error::InvalidComponent {
227                component: "garbage".to_owned()
228            }
229        );
230    }
231}