Skip to content

Commit cc86ab5

Browse files
authored
Merge pull request #62 from gcmurphy/Jarrah-TLR/main
fix: address issues parsing Ubuntu ecosystem Can now parse: ``` Ubuntu:22.04:LTS:for:NVIDIA:BlueField Ubuntu:Pro:FIPS-preview:22.04:LTS Ubuntu:Pro:FIPS-updates:18.04:LTS Ubuntu:Pro:FIPS-updates:20.04:LTS Ubuntu:Pro:FIPS:16.04:LTS ```
2 parents d26da39 + 2c13a74 commit cc86ab5

File tree

2 files changed

+125
-81
lines changed

2 files changed

+125
-81
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ reqwest = { version = "0.12", features = ["json"], optional = true }
2121
tokio = { version = "1", features = ["full"], optional = true }
2222
thiserror = { version = "2.0", optional = true }
2323
url = { version = "2.3.1", optional = true }
24+
lazy-regex = "3.4.1"
2425

2526
[dev-dependencies]
2627
comfy-table = "7.1.1"

src/schema.rs

Lines changed: 124 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use chrono::{DateTime, Utc};
2+
use lazy_regex::regex_switch;
23
use serde::de::{self, Visitor};
34
use serde::{Deserialize, Deserializer, Serialize, Serializer};
5+
46
/// Package identifies the code library or command that
57
/// is potentially affected by a particular vulnerability.
6-
#[derive(Debug, Serialize, Deserialize)]
7-
8+
#[derive(Debug, Serialize, Deserialize, Clone)]
89
pub struct Package {
910
/// The name of the package or dependency.
1011
pub name: String,
@@ -68,6 +69,8 @@ pub enum Ecosystem {
6869
SwiftURL,
6970
Ubuntu {
7071
version: String,
72+
metadata: Option<String>,
73+
fips: Option<String>,
7174
pro: bool,
7275
lts: bool,
7376
},
@@ -146,25 +149,29 @@ impl Serialize for Ecosystem {
146149
}
147150
Ecosystem::SwiftURL => serializer.serialize_str("SwiftURL"),
148151
Ecosystem::Ubuntu {
149-
version: v,
150-
pro: true,
151-
lts: true,
152-
} => serializer.serialize_str(&format!("Ubuntu:Pro:{}:LTS", v)),
153-
Ecosystem::Ubuntu {
154-
version: v,
155-
pro: true,
156-
lts: false,
157-
} => serializer.serialize_str(&format!("Ubuntu:Pro:{}", v)),
158-
Ecosystem::Ubuntu {
159-
version: v,
160-
pro: false,
161-
lts: true,
162-
} => serializer.serialize_str(&format!("Ubuntu:{}:LTS", v)),
163-
Ecosystem::Ubuntu {
164-
version: v,
165-
pro: false,
166-
lts: false,
167-
} => serializer.serialize_str(&format!("Ubuntu:{}", v)),
152+
version,
153+
pro,
154+
lts,
155+
metadata,
156+
fips,
157+
} => {
158+
let mut parts: Vec<String> = vec!["Ubuntu".to_string()];
159+
if *pro {
160+
parts.push("Pro".to_string());
161+
}
162+
if let Some(stream) = fips {
163+
parts.push(stream.clone());
164+
}
165+
parts.push(version.clone());
166+
if *lts {
167+
parts.push("LTS".to_string());
168+
}
169+
if let Some(meta) = metadata {
170+
parts.push(meta.clone());
171+
}
172+
let serialized = parts.join(":");
173+
serializer.serialize_str(&serialized)
174+
}
168175
Ecosystem::UVI => serializer.serialize_str("UVI"),
169176
}
170177
}
@@ -177,7 +184,7 @@ impl<'de> Deserialize<'de> for Ecosystem {
177184
{
178185
struct EcosystemVisitor;
179186

180-
impl<'de> Visitor<'de> for EcosystemVisitor {
187+
impl Visitor<'_> for EcosystemVisitor {
181188
type Value = Ecosystem;
182189

183190
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
@@ -258,52 +265,19 @@ impl<'de> Deserialize<'de> for Ecosystem {
258265
value.strip_prefix("SUSE:").map(|v| v.to_string()),
259266
)),
260267
"SwiftURL" => Ok(Ecosystem::SwiftURL),
261-
_ if value.starts_with("Ubuntu:Pro:") => {
262-
value.strip_prefix("Ubuntu:Pro:").map_or(
263-
Err(de::Error::unknown_variant(value, &["Ecosystem"])),
264-
|v| {
265-
let parts: Vec<&str> = v.split(':').collect();
266-
match parts.as_slice() {
267-
[ver, "LTS"] => Ok(Ecosystem::Ubuntu {
268-
version: ver.to_string(),
269-
pro: true,
270-
lts: true,
271-
}),
272-
[ver] => Ok(Ecosystem::Ubuntu {
273-
version: ver.to_string(),
274-
pro: true,
275-
lts: false,
276-
}),
277-
_ => Err(de::Error::unknown_variant(
278-
value,
279-
&["Ecosystem", "Ubuntu:Pro:YY.MM:(LTS?)"],
280-
)),
268+
_ if value.starts_with("Ubuntu:") => {
269+
regex_switch!(value,
270+
r#"^Ubuntu(?::Pro)?(?::(?<fips>FIPS(?:-preview|-updates)?))?:(?<version>\d+\.\d+)(?::LTS)?(?::for:(?<specialized>.+))?$"# => {
271+
Ecosystem::Ubuntu {
272+
version: version.to_string(),
273+
metadata: (!specialized.is_empty()).then_some(specialized.to_string()),
274+
fips: (!fips.is_empty()).then_some(fips.to_string()),
275+
pro: value.contains(":Pro"),
276+
lts: value.contains(":LTS"),
281277
}
282-
},
283-
)
284-
}
285-
_ if value.starts_with("Ubuntu:") => value.strip_prefix("Ubuntu:").map_or(
286-
Err(de::Error::unknown_variant(value, &["Ecosystem"])),
287-
|v| {
288-
let parts: Vec<&str> = v.split(':').collect();
289-
match parts.as_slice() {
290-
[ver, "LTS"] => Ok(Ecosystem::Ubuntu {
291-
version: ver.to_string(),
292-
pro: false,
293-
lts: true,
294-
}),
295-
[ver] => Ok(Ecosystem::Ubuntu {
296-
version: ver.to_string(),
297-
pro: false,
298-
lts: false,
299-
}),
300-
_ => Err(de::Error::unknown_variant(
301-
value,
302-
&["Ecosystem", "Ubuntu:YY.MM:(?LTS)"],
303-
)),
304278
}
305-
},
306-
),
279+
).ok_or(de::Error::unknown_variant(value, &["Ecosystem"]))
280+
}
307281
"UVI" => Ok(Ecosystem::UVI),
308282
_ => Err(de::Error::unknown_variant(value, &["Ecosystem"])),
309283
}
@@ -315,7 +289,7 @@ impl<'de> Deserialize<'de> for Ecosystem {
315289

316290
/// Type of the affected range supplied. This can be an ecosystem
317291
/// specific value, semver, or a git commit hash.
318-
#[derive(Debug, Serialize, Deserialize)]
292+
#[derive(Debug, Serialize, Deserialize, Clone)]
319293
#[serde(rename_all = "UPPERCASE")]
320294
#[non_exhaustive]
321295
pub enum RangeType {
@@ -335,7 +309,7 @@ pub enum RangeType {
335309

336310
/// The event captures information about the how and when
337311
/// the package was affected by the vulnerability.
338-
#[derive(Debug, Serialize, Deserialize)]
312+
#[derive(Debug, Serialize, Deserialize, Clone)]
339313
#[serde(rename_all = "lowercase")]
340314
#[non_exhaustive]
341315
pub enum Event {
@@ -356,7 +330,7 @@ pub enum Event {
356330

357331
/// The range of versions of a package for which
358332
/// it is affected by the vulnerability.
359-
#[derive(Debug, Serialize, Deserialize)]
333+
#[derive(Debug, Serialize, Deserialize, Clone)]
360334
pub struct Range {
361335
/// The format that the range events are specified in, for
362336
/// example SEMVER or GIT.
@@ -378,7 +352,7 @@ pub struct Range {
378352
/// by a particular vulnerability. The affected ranges can include
379353
/// when the vulnerability was first introduced and also when it
380354
/// was fixed.
381-
#[derive(Debug, Serialize, Deserialize)]
355+
#[derive(Clone, Debug, Serialize, Deserialize)]
382356
pub struct Affected {
383357
/// The package that is affected by the vulnerability
384358
#[serde(skip_serializing_if = "Option::is_none")]
@@ -418,7 +392,7 @@ pub struct Affected {
418392
/// The type of reference information that has been provided. Examples include
419393
/// links to the original report, external advisories, or information about the
420394
/// fix.
421-
#[derive(Debug, Serialize, Deserialize)]
395+
#[derive(Clone, Debug, Serialize, Deserialize)]
422396
#[serde(rename_all = "UPPERCASE")]
423397
#[non_exhaustive]
424398
pub enum ReferenceType {
@@ -461,7 +435,7 @@ pub enum ReferenceType {
461435
}
462436

463437
/// Reference to additional information about the vulnerability.
464-
#[derive(Debug, Serialize, Deserialize)]
438+
#[derive(Clone, Debug, Serialize, Deserialize)]
465439
pub struct Reference {
466440
/// The type of reference this URL points to.
467441
#[serde(rename = "type")]
@@ -474,7 +448,7 @@ pub struct Reference {
474448

475449
/// The [`SeverityType`](SeverityType) describes the quantitative scoring method used to rate the
476450
/// severity of the vulnerability.
477-
#[derive(Debug, Serialize, Deserialize)]
451+
#[derive(Clone, Debug, Serialize, Deserialize)]
478452
#[non_exhaustive]
479453
pub enum SeverityType {
480454
/// A CVSS vector string representing the unique characteristics and severity of the vulnerability
@@ -503,7 +477,7 @@ pub enum SeverityType {
503477

504478
/// The type and score used to describe the severity of a vulnerability using one
505479
/// or more quantitative scoring methods.
506-
#[derive(Debug, Serialize, Deserialize)]
480+
#[derive(Clone, Debug, Serialize, Deserialize)]
507481
pub struct Severity {
508482
/// The severity type property must be a [`SeverityType`](SeverityType), which describes the
509483
/// quantitative method used to calculate the associated score.
@@ -519,7 +493,7 @@ pub struct Severity {
519493
/// the type or role of the individual or entity being credited.
520494
///
521495
/// These values and their definitions correspond directly to the [MITRE CVE specification](https://cveproject.github.io/cve-schema/schema/v5.0/docs/#collapseDescription_oneOf_i0_containers_cna_credits_items_type).
522-
#[derive(Debug, Serialize, Deserialize)]
496+
#[derive(Clone, Debug, Serialize, Deserialize)]
523497
#[serde(rename_all = "UPPERCASE")]
524498
#[non_exhaustive]
525499
pub enum CreditType {
@@ -558,7 +532,7 @@ pub enum CreditType {
558532

559533
/// Provides a way to give credit for the discovery, confirmation, patch or other events in the
560534
/// life cycle of a vulnerability.
561-
#[derive(Debug, Serialize, Deserialize)]
535+
#[derive(Clone, Debug, Serialize, Deserialize)]
562536
pub struct Credit {
563537
pub name: String,
564538
#[serde(skip_serializing_if = "Option::is_none")]
@@ -573,7 +547,7 @@ pub struct Credit {
573547
/// This is the entity that is returned when vulnerable data exists for
574548
/// a given package or when requesting information about a specific vulnerability
575549
/// by unique identifier.
576-
#[derive(Debug, Serialize, Deserialize)]
550+
#[derive(Clone, Debug, Serialize, Deserialize)]
577551
pub struct Vulnerability {
578552
/// The schema_version field is used to indicate which version of the OSV schema a particular
579553
/// vulnerability was exported with.
@@ -720,6 +694,8 @@ mod tests {
720694
version: "20.04".to_string(),
721695
pro: true,
722696
lts: true,
697+
fips: None,
698+
metadata: None,
723699
};
724700
let as_json = serde_json::json!(ubuntu);
725701
assert_eq!(as_json, serde_json::json!("Ubuntu:Pro:20.04:LTS"));
@@ -728,6 +704,8 @@ mod tests {
728704
version: "20.04".to_string(),
729705
pro: true,
730706
lts: false,
707+
fips: None,
708+
metadata: None,
731709
};
732710
let as_json = serde_json::json!(ubuntu);
733711
assert_eq!(as_json, serde_json::json!("Ubuntu:Pro:20.04"));
@@ -736,6 +714,8 @@ mod tests {
736714
version: "20.04".to_string(),
737715
pro: false,
738716
lts: true,
717+
fips: None,
718+
metadata: None,
739719
};
740720
let as_json = serde_json::json!(ubuntu);
741721
assert_eq!(as_json, serde_json::json!("Ubuntu:20.04:LTS"));
@@ -744,6 +724,8 @@ mod tests {
744724
version: "20.04".to_string(),
745725
pro: false,
746726
lts: false,
727+
fips: None,
728+
metadata: None,
747729
};
748730
let as_json = serde_json::json!(ubuntu);
749731
assert_eq!(as_json, serde_json::json!("Ubuntu:20.04"));
@@ -755,7 +737,9 @@ mod tests {
755737
Ecosystem::Ubuntu {
756738
version: "20.04".to_string(),
757739
pro: true,
758-
lts: true
740+
lts: true,
741+
fips: None,
742+
metadata: None,
759743
}
760744
);
761745

@@ -766,7 +750,9 @@ mod tests {
766750
Ecosystem::Ubuntu {
767751
version: "20.04".to_string(),
768752
pro: true,
769-
lts: false
753+
lts: false,
754+
fips: None,
755+
metadata: None,
770756
}
771757
);
772758

@@ -777,7 +763,9 @@ mod tests {
777763
Ecosystem::Ubuntu {
778764
version: "20.04".to_string(),
779765
pro: false,
780-
lts: true
766+
lts: true,
767+
fips: None,
768+
metadata: None,
781769
}
782770
);
783771

@@ -788,7 +776,62 @@ mod tests {
788776
Ecosystem::Ubuntu {
789777
version: "20.04".to_string(),
790778
pro: false,
791-
lts: false
779+
lts: false,
780+
fips: None,
781+
metadata: None,
782+
}
783+
);
784+
785+
let json_str = r#""Ubuntu:22.04:LTS:for:NVIDIA:BlueField""#;
786+
let ubuntu: Ecosystem = serde_json::from_str(json_str).unwrap();
787+
assert_eq!(
788+
ubuntu,
789+
Ecosystem::Ubuntu {
790+
version: "22.04".to_string(),
791+
pro: false,
792+
lts: true,
793+
fips: None,
794+
metadata: Some("NVIDIA:BlueField".to_string()),
795+
}
796+
);
797+
798+
let json_str = r#""Ubuntu:Pro:FIPS-preview:22.04:LTS""#;
799+
let ubuntu: Ecosystem = serde_json::from_str(json_str).unwrap();
800+
assert_eq!(
801+
ubuntu,
802+
Ecosystem::Ubuntu {
803+
version: "22.04".to_string(),
804+
pro: true,
805+
lts: true,
806+
fips: Some("FIPS-preview".to_string()),
807+
metadata: None,
808+
}
809+
);
810+
811+
let json_str = r#""Ubuntu:Pro:FIPS-updates:18.04:LTS""#;
812+
let ubuntu: Ecosystem = serde_json::from_str(json_str).unwrap();
813+
assert_eq!(
814+
ubuntu,
815+
Ecosystem::Ubuntu {
816+
version: "18.04".to_string(),
817+
pro: true,
818+
lts: true,
819+
fips: Some("FIPS-updates".to_string()),
820+
metadata: None,
821+
}
822+
);
823+
824+
let json_str = r#""Ubuntu:Pro:FIPS:16.04:LTS""#;
825+
let ubuntu: Ecosystem = serde_json::from_str(json_str).unwrap();
826+
827+
assert_eq!(
828+
ubuntu,
829+
Ecosystem::Ubuntu {
830+
version: "16.04".to_string(),
831+
pro: true,
832+
lts: true,
833+
fips: Some("FIPS".to_string()),
834+
metadata: None,
792835
}
793836
);
794837
}

0 commit comments

Comments
 (0)