1use crate::errors::*;
2use nom::bytes::complete::{tag, take_until};
3use nom::combinator::map_res;
4use nom::multi::fold_many0;
5use nom::IResult;
6use serde::{Deserialize, Serialize};
7use std::str::FromStr;
8
9mod stealth;
10pub use self::stealth::Stealth;
11
12#[derive(Debug, PartialEq, Clone)]
13pub enum EntryType {
14 Description,
15 Version,
16 Source,
17 KeyringAccess,
18 Stealth,
19 Author,
20 Repository,
21 License,
22}
23
24impl FromStr for EntryType {
25 type Err = Error;
26
27 fn from_str(s: &str) -> Result<EntryType> {
28 match s {
29 "Description" => Ok(EntryType::Description),
30 "Version" => Ok(EntryType::Version),
31 "Source" => Ok(EntryType::Source),
32 "Keyring-Access" => Ok(EntryType::KeyringAccess),
33 "Stealth" => Ok(EntryType::Stealth),
34 "Author" => Ok(EntryType::Author),
35 "Repository" => Ok(EntryType::Repository),
36 "License" => Ok(EntryType::License),
37 x => bail!("Unknown EntryType: {:?}", x),
38 }
39 }
40}
41
42#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
43pub enum Source {
44 Domains,
45 Subdomains,
46 IpAddrs,
47 Urls,
48 Emails,
49 PhoneNumbers,
50 Networks,
51 Devices,
52 Accounts(Option<String>),
53 Breaches,
54 Images,
55 Ports,
56 Netblocks,
57 CryptoAddrs(Option<String>),
58 KeyRing(String),
59 Notifications,
60}
61
62impl Source {
63 pub fn group_as_str(&self) -> &'static str {
64 match self {
65 Source::Domains => "domains",
66 Source::Subdomains => "subdomains",
67 Source::IpAddrs => "ipaddrs",
68 Source::Urls => "urls",
69 Source::Emails => "emails",
70 Source::PhoneNumbers => "phonenumbers",
71 Source::Networks => "networks",
72 Source::Devices => "devices",
73 Source::Accounts(_) => "accounts",
74 Source::Breaches => "breaches",
75 Source::Images => "images",
76 Source::Ports => "ports",
77 Source::Netblocks => "netblocks",
78 Source::CryptoAddrs(_) => "cryptoaddrs",
79 Source::Notifications => "notifications",
80 Source::KeyRing(_) => "keyring",
81 }
82 }
83}
84
85impl FromStr for Source {
86 type Err = Error;
87
88 fn from_str(s: &str) -> Result<Source> {
89 let (key, param) = if let Some(idx) = s.find(':') {
90 let (a, b) = s.split_at(idx);
91 (a, Some(&b[1..]))
92 } else {
93 (s, None)
94 };
95
96 match (key, param) {
97 ("domains", None) => Ok(Source::Domains),
98 ("subdomains", None) => Ok(Source::Subdomains),
99 ("ipaddrs", None) => Ok(Source::IpAddrs),
100 ("urls", None) => Ok(Source::Urls),
101 ("emails", None) => Ok(Source::Emails),
102 ("phonenumbers", None) => Ok(Source::PhoneNumbers),
103 ("networks", None) => Ok(Source::Networks),
104 ("devices", None) => Ok(Source::Devices),
105 ("accounts", param) => Ok(Source::Accounts(param.map(String::from))),
106 ("breaches", None) => Ok(Source::Breaches),
107 ("images", None) => Ok(Source::Images),
108 ("ports", None) => Ok(Source::Ports),
109 ("netblocks", None) => Ok(Source::Netblocks),
110 ("cryptoaddrs", param) => Ok(Source::CryptoAddrs(param.map(String::from))),
111 ("notifications", None) => Ok(Source::Notifications),
112 ("keyring", Some(param)) => Ok(Source::KeyRing(param.to_string())),
113 (x, Some(param)) => bail!("Unknown Source: {:?} ({:?})", x, param),
114 (x, None) => bail!("Unknown Source: {:?}", x),
115 }
116 }
117}
118
119#[derive(Debug, PartialEq)]
120pub enum License {
121 MIT,
122 GPL3,
123 LGPL3,
124 BSD2,
125 BSD3,
126 WTFPL,
127}
128
129impl FromStr for License {
130 type Err = Error;
131
132 fn from_str(s: &str) -> Result<License> {
133 match s {
134 "MIT" => Ok(License::MIT),
135 "GPL-3.0" => Ok(License::GPL3),
136 "LGPL-3.0" => Ok(License::LGPL3),
137 "BSD-2-Clause" => Ok(License::BSD2),
138 "BSD-3-Clause" => Ok(License::BSD3),
139 "WTFPL" => Ok(License::WTFPL),
140 x => bail!("Unsupported license: {:?}", x),
141 }
142 }
143}
144
145#[derive(Debug, PartialEq)]
146pub struct Metadata {
147 pub description: String,
148 pub version: String,
149 pub source: Option<Source>,
150 pub keyring_access: Vec<String>,
151 pub stealth: Stealth,
152 pub authors: Vec<String>,
153 pub repository: Option<String>,
154 pub license: License,
155}
156
157impl FromStr for Metadata {
158 type Err = Error;
159
160 fn from_str(code: &str) -> Result<Metadata> {
161 let (_, lines) = metalines(code).map_err(|_| format_err!("Failed to parse header"))?;
162
163 let mut data = NewMetadata::default();
164
165 for (k, v) in lines {
166 match k {
167 EntryType::Description => data.description = Some(v),
168 EntryType::Version => data.version = Some(v),
169 EntryType::Source => data.source = Some(v),
170 EntryType::KeyringAccess => data.keyring_access.push(v),
171 EntryType::Stealth => data.stealth = Some(v),
172 EntryType::Author => data.authors.push(v),
173 EntryType::Repository => data.repository = Some(v),
174 EntryType::License => data.license = Some(v),
175 }
176 }
177
178 data.try_from()
179 }
180}
181
182#[derive(Default)]
183pub struct NewMetadata<'a> {
184 pub description: Option<&'a str>,
185 pub version: Option<&'a str>,
186 pub source: Option<&'a str>,
187 pub keyring_access: Vec<&'a str>,
188 pub stealth: Option<&'a str>,
189 pub authors: Vec<&'a str>,
190 pub repository: Option<&'a str>,
191 pub license: Option<&'a str>,
192}
193
194impl<'a> NewMetadata<'a> {
195 fn try_from(self) -> Result<Metadata> {
196 let description = self
197 .description
198 .ok_or_else(|| format_err!("Description is required"))?;
199 let version = self
200 .version
201 .ok_or_else(|| format_err!("Version is required"))?;
202 let source = match self.source {
203 Some(x) => Some(x.parse()?),
204 _ => None,
205 };
206 let keyring_access = self.keyring_access.into_iter().map(String::from).collect();
207 let stealth = match self.stealth {
208 Some(x) => x.parse()?,
209 _ => Stealth::Normal,
210 };
211 let authors = self.authors.into_iter().map(String::from).collect();
212 let repository = self.repository.map(String::from);
213 let license = self
214 .license
215 .ok_or_else(|| format_err!("License is required"))?;
216 let license = license.parse()?;
217
218 Ok(Metadata {
219 description: description.to_string(),
220 version: version.to_string(),
221 source,
222 keyring_access,
223 stealth,
224 authors,
225 repository,
226 license,
227 })
228 }
229}
230
231fn metaline(input: &str) -> IResult<&str, (EntryType, &str)> {
232 let (input, _) = tag("-- ")(input)?;
233 let (input, name) = map_res(take_until(": "), EntryType::from_str)(input)?;
234 let (input, _) = tag(": ")(input)?;
235 let (input, value) = take_until("\n")(input)?;
236 let (input, _) = tag("\n")(input)?;
237
238 Ok((input, (name, value)))
239}
240
241fn metalines(input: &str) -> IResult<&str, Vec<(EntryType, &str)>> {
242 let (input, lines) = fold_many0(metaline, Vec::new, |mut acc: Vec<_>, item| {
243 acc.push(item);
244 acc
245 })(input)?;
246 let (input, _) = tag("\n")(input)?;
247
248 Ok((input, lines))
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254
255 #[test]
256 fn verify_simple() {
257 let metadata = Metadata::from_str(
258 r#"-- Description: Hello world, this is my description
259-- Version: 1.0.0
260-- Source: domains
261-- License: WTFPL
262
263"#,
264 )
265 .expect("parse");
266 assert_eq!(
267 metadata,
268 Metadata {
269 description: "Hello world, this is my description".to_string(),
270 version: "1.0.0".to_string(),
271 license: License::WTFPL,
272 source: Some(Source::Domains),
273 stealth: Stealth::Normal,
274 authors: vec![],
275 repository: None,
276 keyring_access: Vec::new(),
277 }
278 );
279 }
280
281 #[test]
282 fn verify_much_metadata() {
283 let metadata = Metadata::from_str(
284 r#"-- Description: Hello world, this is my description
285-- Version: 1.0.0
286-- Source: domains
287-- Stealth: passive
288-- Author: kpcyrd <git at rxv dot cc>
289-- Author: kpcyrd's cat
290-- Repository: https://github.com/kpcyrd/sn0int
291-- License: WTFPL
292
293"#,
294 )
295 .expect("parse");
296 assert_eq!(
297 metadata,
298 Metadata {
299 description: "Hello world, this is my description".to_string(),
300 version: "1.0.0".to_string(),
301 license: License::WTFPL,
302 source: Some(Source::Domains),
303 stealth: Stealth::Passive,
304 authors: vec![
305 "kpcyrd <git at rxv dot cc>".to_string(),
306 "kpcyrd's cat".to_string(),
307 ],
308 repository: Some("https://github.com/kpcyrd/sn0int".to_string()),
309 keyring_access: Vec::new(),
310 }
311 );
312 }
313
314 #[test]
315 fn verify_no_source() {
316 let metadata = Metadata::from_str(
317 r#"-- Description: Hello world, this is my description
318-- Version: 1.0.0
319-- License: WTFPL
320
321"#,
322 )
323 .expect("parse");
324 assert_eq!(
325 metadata,
326 Metadata {
327 description: "Hello world, this is my description".to_string(),
328 version: "1.0.0".to_string(),
329 license: License::WTFPL,
330 source: None,
331 stealth: Stealth::Normal,
332 authors: vec![],
333 repository: None,
334 keyring_access: Vec::new(),
335 }
336 );
337 }
338
339 #[test]
340 fn verify_require_license() {
341 let metadata = Metadata::from_str(
342 r#"-- Description: Hello world, this is my description
343-- Version: 1.0.0
344-- Source: domains
345
346"#,
347 );
348 assert!(metadata.is_err());
349 }
350
351 #[test]
352 fn verify_require_opensource_license() {
353 let metadata = Metadata::from_str(
354 r#"-- Description: Hello world, this is my description
355-- Version: 1.0.0
356-- Source: domains
357-- License: Proprietary
358
359"#,
360 );
361 assert!(metadata.is_err());
362 }
363
364 #[test]
365 fn verify_keyring_source() {
366 let x = Source::from_str("keyring:foo").unwrap();
367 assert_eq!(x, Source::KeyRing("foo".to_string()));
368
369 let x = Source::from_str("keyring:").unwrap();
370 assert_eq!(x, Source::KeyRing("".to_string()));
371 }
372
373 #[test]
374 fn verify_invalid_keyring_source() {
375 let x = Source::from_str("keyring");
376 assert!(x.is_err());
377 }
378
379 #[test]
380 fn verify_account_source() {
381 let x = Source::from_str("accounts").unwrap();
382 assert_eq!(x, Source::Accounts(None));
383
384 let x = Source::from_str("accounts:github.com").unwrap();
385 assert_eq!(x, Source::Accounts(Some("github.com".into())));
386 }
387}