Skip to main content

sn0int_common/metadata/
mod.rs

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}