1pub mod affected;
4mod category;
5mod date;
6mod id;
7mod informational;
8mod keyword;
9mod license;
10pub mod linter;
11mod metadata;
12mod parts;
13pub(crate) mod versions;
14
15pub use self::{
16 affected::Affected,
17 category::Category,
18 date::Date,
19 id::{Id, IdKind},
20 informational::Informational,
21 keyword::Keyword,
22 license::License,
23 linter::Linter,
24 metadata::Metadata,
25 parts::Parts,
26 versions::Versions,
27};
28pub use cvss::Severity;
29
30use crate::{
31 error::{Error, ErrorKind},
32 fs,
33};
34use serde::{Deserialize, Serialize};
35use std::{ffi::OsStr, path::Path, str::FromStr};
36
37#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
39pub struct Advisory {
40 #[serde(rename = "advisory")]
42 pub metadata: Metadata,
43
44 pub affected: Option<Affected>,
46
47 pub versions: Versions,
49}
50
51impl Advisory {
52 pub fn load_file(path: impl AsRef<Path>) -> Result<Self, Error> {
54 let path = path.as_ref();
55
56 let advisory_data = fs::read_to_string(path)
57 .map_err(|e| format_err!(ErrorKind::Io, "couldn't open {}: {}", path.display(), e))?;
58
59 advisory_data
60 .parse()
61 .map_err(|e| format_err!(ErrorKind::Parse, "error parsing {}: {}", path.display(), e))
62 }
63
64 pub fn id(&self) -> &Id {
66 &self.metadata.id
67 }
68
69 pub fn title(&self) -> &str {
71 self.metadata.title.as_ref()
72 }
73
74 pub fn description(&self) -> &str {
76 self.metadata.description.as_ref()
77 }
78
79 pub fn date(&self) -> &Date {
81 &self.metadata.date
82 }
83
84 pub fn severity(&self) -> Option<Severity> {
86 self.metadata.cvss.as_ref().map(|cvss| cvss.severity())
87 }
88
89 pub fn withdrawn(&self) -> bool {
91 self.metadata.withdrawn.is_some()
92 }
93
94 pub fn is_draft(path: &Path) -> bool {
96 matches!(
97 path.file_name().and_then(OsStr::to_str),
98 Some(name) if name.starts_with("RUSTSEC-0000-0000."),
99 )
100 }
101}
102
103impl FromStr for Advisory {
104 type Err = Error;
105
106 fn from_str(advisory_data: &str) -> Result<Self, Error> {
107 let parts = Parts::parse(advisory_data)?;
108
109 let front_matter = if parts.front_matter.starts_with("[advisory]") {
111 parts.front_matter.to_owned()
112 } else {
113 String::from("[advisory]\n") + parts.front_matter
114 };
115
116 let mut advisory: Self = toml::from_str(&front_matter).map_err(Error::from_toml)?;
117
118 if !advisory.metadata.title.is_empty() {
119 fail!(
120 ErrorKind::Parse,
121 "invalid `title` attribute in advisory TOML"
122 );
123 }
124
125 if !advisory.metadata.description.is_empty() {
126 fail!(
127 ErrorKind::Parse,
128 "invalid `description` attribute in advisory TOML"
129 );
130 }
131
132 #[allow(clippy::assigning_clones)]
133 {
134 advisory.metadata.title = parts.title.to_owned();
135 advisory.metadata.description = parts.description.to_owned();
136 }
137
138 Ok(advisory)
139 }
140}