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
21pub const PREFIX: &str = "CVSS";
23
24#[derive(Clone, PartialEq, Eq, Debug)]
26#[non_exhaustive]
27pub enum Cvss {
28 #[cfg(feature = "v3")]
29 CvssV30(v3::Base),
31 #[cfg(feature = "v3")]
32 CvssV31(v3::Base),
34 #[cfg(feature = "v4")]
35 CvssV40(v4::Vector),
37}
38
39impl Cvss {
40 #[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 #[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 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 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#[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 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 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}