Skip to main content

shodan_client/
builders.rs

1use std::collections::HashMap;
2
3#[derive(Default)]
4pub struct SslFilterBuilder {
5    filters: HashMap<String, Vec<String>>,
6}
7
8impl SslFilterBuilder {
9    pub fn build(self) -> HashMap<String, Vec<String>> {
10        self.filters
11    }
12
13    pub fn cert_subject_cn(mut self, value: impl ToString) -> Self {
14        let values = self.filters.entry("cert.subject.cn".into()).or_default();
15
16        values.push(value.to_string());
17
18        self
19    }
20}
21
22#[derive(Default)]
23pub struct SearchQueryBuilder {
24    query: String,
25    filters: HashMap<String, Vec<String>>,
26}
27
28impl SearchQueryBuilder {
29    pub fn build(self) -> String {
30        let mut query = vec![];
31        if !self.query.is_empty() {
32            query.push(self.query);
33        }
34
35        for (filter, values) in self.filters {
36            query.push(format!("{}:{}", filter, values.join(",")));
37        }
38
39        query.join(" ")
40    }
41
42    pub fn query(mut self, query: impl ToString) -> Self {
43        self.query = query.to_string();
44
45        self
46    }
47
48    pub fn port(mut self, value: impl ToString) -> Self {
49        let values = self.filters.entry("port".into()).or_default();
50
51        values.push(value.to_string());
52
53        self
54    }
55
56    pub fn product(mut self, filter: impl ToString) -> Self {
57        let values = self.filters.entry("product".into()).or_default();
58
59        values.push(filter.to_string());
60
61        self
62    }
63
64    pub fn ssl(mut self, closure: fn(SslFilterBuilder) -> SslFilterBuilder) -> Self {
65        let filters = closure(SslFilterBuilder::default()).build();
66
67        self.filters.extend(filters);
68
69        self
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use crate::*;
76
77    #[test]
78    fn can_build_query() {
79        let query = SearchQueryBuilder::default()
80            .query("apache")
81            .port("69")
82            .port(420)
83            .product("Apache")
84            .product("Ngnix")
85            .build();
86
87        assert!(query.contains("port:69,420"), "query was: {query}");
88        assert!(query.contains("product:Apache,Ngnix"), "query was: {query}");
89        assert!(query.starts_with("apache"), "query was: {query}");
90    }
91
92    #[test]
93    fn can_build_without_query() {
94        let query = SearchQueryBuilder::default()
95            .port("69")
96            .port(420)
97            .product("Apache")
98            .product("Ngnix")
99            .build();
100
101        assert!(query.contains("port:69,420"), "query was: {query}");
102        assert!(query.contains("product:Apache,Ngnix"), "query was: {query}");
103    }
104
105    #[test]
106    fn can_build_with_ssl() {
107        let query = SearchQueryBuilder::default()
108            .port("69")
109            .port(420)
110            .product("Apache")
111            .product("Ngnix")
112            .ssl(|ssl_builder| {
113                ssl_builder
114                    .cert_subject_cn("google.com")
115                    .cert_subject_cn("bing.com")
116            })
117            .build();
118
119        assert!(query.contains("port:69,420"), "query was: {query}");
120        assert!(query.contains("product:Apache,Ngnix"), "query was: {query}");
121        assert!(
122            query.contains("cert.subject.cn:google.com,bing.com"),
123            "query was: {query}"
124        );
125    }
126}