Skip to main content

nvd_cpe/
dictionary.rs

1//! ### Common Platform Enumeration (CPE): Dictionary
2//! The Dictionary specification defines the concept of a CPE dictionary, which is a repository of CPE names and metadata, with each name identifying a single class of IT product. The Dictionary specification defines processes for using the dictionary, such as how to search for a particular CPE name or look for dictionary entries that belong to a broader product class. Also, the Dictionary specification outlines all the rules that dictionary maintainers must follow when creating new dictionary entries and updating existing entries.
3//!
4use crate::{parse_uri_attribute, CPEName};
5use chrono::{DateTime, Utc};
6use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
7use std::fmt;
8use std::marker::PhantomData;
9use std::str::FromStr;
10/// The cpe-list element acts as a top-level container for CPE Name items. Each individual item must be unique. Please refer to the description of ListType for additional information about the structure of this element.
11///
12#[derive(Deserialize, Serialize, Debug, Clone)]
13#[serde(rename_all = "kebab-case")]
14pub struct CPEList {
15  pub generator: Generator,
16  pub cpe_item: Vec<CPEItem>,
17}
18/**
19The ItemType complex type defines an element that represents a single CPE
20Name. The required name attribute is a URI which must be a unique key and should follow the URI
21structure outlined in the CPE Specification. The optional title element is used to provide a
22human-readable title for the platform. To support uses intended for multiple languages, this element
23supports the ‘xml:lang’ attribute. At most one title element can appear for each language. The notes
24element holds optional descriptive material. Multiple notes elements are allowed, but only one per
25language should be used. Note that the language associated with the notes element applies to all child
26note elements. The optional references element holds external info references. The optional check
27element is used to call out an OVAL Definition that can confirm or reject an IT system as an instance of
28the named platform. Additional elements not part of the CPE namespace are allowed and are just skipped
29by validation. In essence, a dictionary file can contain additional information that a user can choose
30to use or not, but this information is not required to be used or understood.
31 */
32#[derive(Deserialize, Serialize, Debug, Clone)]
33#[serde(deny_unknown_fields)]
34pub struct CPEItem {
35  #[serde(rename(deserialize = "@name"), deserialize_with = "parse_name")]
36  pub name: String,
37  #[serde(default, rename(serialize = "deprecated", deserialize = "@deprecated"))]
38  pub deprecated: bool,
39  #[serde(
40    default,
41    rename(serialize = "deprecation_date", deserialize = "@deprecation_date"),
42    skip_serializing_if = "Option::is_none"
43  )]
44  pub deprecation_date: Option<DateTime<Utc>>,
45  #[serde(rename(serialize = "cpe23", deserialize = "cpe23-item"))]
46  pub cpe23_item: CPE23Item,
47  #[serde(default)]
48  pub title: Vec<Title>,
49  #[serde(skip_serializing_if = "Option::is_none")]
50  pub notes: Option<Vec<Notes>>,
51  pub references: Option<References>,
52  #[serde(skip_serializing_if = "Option::is_none")]
53  pub check: Option<Vec<Check>>,
54}
55
56#[derive(Deserialize, Serialize, Debug, Clone)]
57#[serde(deny_unknown_fields)]
58pub struct Title {
59  #[serde(rename(deserialize = "@lang"))]
60  pub lang: String,
61  #[serde(rename(deserialize = "$value"), deserialize_with = "parse_name")]
62  pub value: String,
63}
64/**
65The NotesType complex type defines an element that consists of one or more
66child note elements. It is assumed that each of these note elements is representative of the same
67language as defined by their parent.*/
68#[derive(Deserialize, Serialize, Debug, Clone)]
69#[serde(deny_unknown_fields)]
70pub struct Notes {
71  #[serde(rename(deserialize = "@lang"))]
72  pub lang: String,
73  #[serde(rename(deserialize = "$value"))]
74  pub value: String,
75}
76/**
77The CheckType complex type is used to define an element to hold information
78about an individual check. It includes a checking system specification URI, string content, and an
79optional external file reference. The checking system specification should be the URI for a particular
80version of OVAL or a related system testing language, and the content will be an identifier of a test
81written in that language. The external file reference could be used to point to the file in which the
82content test identifier is defined.*/
83#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
84#[serde(deny_unknown_fields)]
85pub struct Check {
86  pub system: String,
87  pub href: Option<String>,
88  #[serde(rename(deserialize = "$value"))]
89  pub value: String,
90}
91
92#[derive(Deserialize, Serialize, Debug, Clone)]
93#[serde(deny_unknown_fields)]
94pub struct References {
95  pub reference: Vec<Reference>,
96}
97/**
98The ReferencesType complex type defines an element used to hold a
99collection of individual references. Each reference consists of a piece of text (intended to be
100human-readable) and a URI (intended to be a URL, and point to a real resource) and is used to point to
101extra descriptive material, for example a supplier's web site or platform
102documentation.
103 */
104#[derive(Deserialize, Serialize, Debug, Clone)]
105#[serde(deny_unknown_fields)]
106pub struct Reference {
107  #[serde(rename(deserialize = "@href"))]
108  pub href: String,
109  #[serde(rename(deserialize = "$value"))]
110  pub value: String,
111}
112
113#[derive(Deserialize, Serialize, Debug, Clone)]
114#[serde(deny_unknown_fields)]
115pub struct CPE23Item {
116  #[serde(
117    rename(deserialize = "@name"),
118    deserialize_with = "uri_to_attribute",
119    serialize_with = "attribute_to_uri"
120  )]
121  pub name: CPEName,
122  #[serde(skip_serializing_if = "Option::is_none")]
123  pub deprecation: Option<Deprecation>,
124}
125
126#[derive(Deserialize, Serialize, Debug, Clone, Default)]
127#[serde(deny_unknown_fields)]
128pub struct Deprecation {
129  #[serde(rename(deserialize = "@date"))]
130  pub date: DateTime<Utc>,
131  #[serde(rename(deserialize = "deprecated-by"))]
132  pub deprecated_by: Vec<DeprecatedInfo>,
133}
134
135#[derive(Deserialize, Serialize, Debug, Clone)]
136#[serde(deny_unknown_fields)]
137pub struct DeprecatedInfo {
138  #[serde(
139    rename(deserialize = "@name"),
140    deserialize_with = "uri_to_attribute",
141    serialize_with = "attribute_to_uri"
142  )]
143  pub name: CPEName,
144  #[serde(rename(deserialize = "@type"))]
145  pub r#type: String,
146}
147/** The GeneratorType complex type defines an element that is used to hold
148information about when a particular document was compiled, what version of the schema was used, what
149tool compiled the document, and what version of that tool was used. Additional generator information is
150also allowed although it is not part of the official schema. Individual organizations can place
151generator information that they feel is important and it will be skipped during the validation. All that
152this schema really cares about is that the stated generator information is there.*/
153#[derive(Deserialize, Serialize, Debug, Clone)]
154#[serde(deny_unknown_fields)]
155pub struct Generator {
156  /// The optional product_name element specifies the name of the application used to generate the file.
157  pub product_name: String,
158  /// The optional product_version element specifies the version of the application used to generate the file.
159  pub product_version: String,
160  /// The required schema_version element specifies the version of the schema that the document has been written against and that should be used for validation.
161  pub schema_version: String,
162  /** The required timestamp element specifies when the particular
163  document was compiled. The format for the timestamp is yyyy-mm-ddThh:mm:ss. Note that the
164  timestamp element does not specify when an item in the document was created or modified but
165  rather when the actual XML document that contains the items was created. For example, a document
166  might pull a bunch of existing items together, each of which was created at some point in the
167  past. The timestamp in this case would be when this combined document was
168  created.*/
169  pub timestamp: DateTime<Utc>,
170}
171
172fn parse_name<'de, D>(deserializer: D) -> Result<String, D::Error>
173where
174  D: Deserializer<'de>,
175{
176  struct ParseString(PhantomData<CPEName>);
177  impl<'de> de::Visitor<'de> for ParseString {
178    type Value = String;
179    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
180      formatter.write_str("parse_name")
181    }
182    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
183    where
184      E: de::Error,
185    {
186      // cpe:2.3:part:vendor:product:version:update:edition:language:sw_edition:target_sw: target_hw:other
187      match parse_uri_attribute(value) {
188        Ok(p) => Ok(p),
189        Err(e) => Err(de::Error::custom(e)),
190      }
191    }
192    fn visit_seq<S>(self, visitor: S) -> Result<Self::Value, S::Error>
193    where
194      S: de::SeqAccess<'de>,
195    {
196      Deserialize::deserialize(de::value::SeqAccessDeserializer::new(visitor))
197    }
198  }
199  deserializer.deserialize_any(ParseString(PhantomData))
200}
201
202pub fn uri_to_attribute<'de, D>(deserializer: D) -> Result<CPEName, D::Error>
203where
204  D: Deserializer<'de>,
205{
206  struct UriToAttribute(PhantomData<CPEName>);
207  impl<'de> de::Visitor<'de> for UriToAttribute {
208    type Value = CPEName;
209    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
210      formatter.write_str("uri_to_attribute")
211    }
212    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
213    where
214      E: de::Error,
215    {
216      // cpe:2.3:part:vendor:product:version:update:edition:language:sw_edition:target_sw: target_hw:other
217      // https://cpe.mitre.org/specification/#downloads
218      let value = parse_uri_attribute(value).unwrap_or_default();
219      match CPEName::from_str(value.as_str()) {
220        Ok(p) => Ok(p),
221        Err(e) => Err(de::Error::custom(e)),
222      }
223    }
224    fn visit_seq<S>(self, visitor: S) -> Result<Self::Value, S::Error>
225    where
226      S: de::SeqAccess<'de>,
227    {
228      Deserialize::deserialize(de::value::SeqAccessDeserializer::new(visitor))
229    }
230  }
231  deserializer.deserialize_any(UriToAttribute(PhantomData))
232}
233
234pub fn attribute_to_uri<S>(cpe: &CPEName, s: S) -> Result<S::Ok, S::Error>
235where
236  S: Serializer,
237{
238  s.serialize_str(&cpe.to_string())
239}