shodan_client/
lib.rs

1use serde::Deserialize;
2use std::collections::{hash_map, HashMap};
3use url::Url;
4
5mod account;
6mod api_status;
7mod builders;
8mod directory;
9mod dns;
10mod error;
11mod response;
12mod scanning;
13mod search;
14mod utility;
15
16pub use account::*;
17pub use api_status::*;
18pub use builders::*;
19pub use directory::*;
20pub use dns::*;
21pub use error::*;
22pub use response::*;
23pub use scanning::*;
24pub use search::*;
25pub use utility::*;
26
27const BASE_API_URL: &str = "https://api.shodan.io";
28
29pub struct ShodanClient {
30    api_key: String,
31}
32
33impl ShodanClient {
34    pub fn new(api_key: String) -> Self {
35        Self { api_key }
36    }
37
38    fn build_request_url(
39        &self,
40        endpoint: &str,
41        parameters: &ParameterBag,
42    ) -> Result<String, error::Error> {
43        let mut url = Url::parse(BASE_API_URL)?;
44        url.set_path(endpoint);
45
46        // Set API key
47        url.query_pairs_mut()
48            .append_pair("key", self.api_key.as_str());
49
50        // Set any additional parameters
51        url.query_pairs_mut().extend_pairs(parameters.pairs());
52
53        Ok(url.to_string())
54    }
55
56    async fn fetch<T: for<'a> Deserialize<'a>>(url: String) -> Result<T, Error> {
57        let response = reqwest::get(url)
58            .await?
59            .json::<ShodanClientResponse<T>>()
60            .await?;
61
62        match response {
63            ShodanClientResponse::Error(e) => {
64                Err(error::Error::Shodan(format!("Error response: {}", e.error)))
65            }
66            ShodanClientResponse::Response(r) => Ok(r),
67        }
68    }
69}
70
71#[derive(Default)]
72struct ParameterBag(HashMap<String, String>);
73
74impl ParameterBag {
75    pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
76        self.0.insert(key.into(), value.into());
77    }
78
79    pub fn set_optional(
80        &mut self,
81        key: impl Into<String>,
82        value: Option<impl Into<ParameterValue>>,
83    ) {
84        if let Some(value) = value {
85            self.set(key.into(), value.into().0);
86        }
87    }
88
89    pub fn pairs(&self) -> hash_map::Iter<'_, String, String> {
90        self.0.iter()
91    }
92}
93
94pub struct ParameterValue(String);
95
96impl From<u32> for ParameterValue {
97    fn from(value: u32) -> Self {
98        Self(value.to_string())
99    }
100}
101
102impl From<String> for ParameterValue {
103    fn from(value: String) -> Self {
104        Self(value)
105    }
106}
107
108impl From<&str> for ParameterValue {
109    fn from(value: &str) -> Self {
110        Self(value.into())
111    }
112}
113
114impl From<bool> for ParameterValue {
115    fn from(value: bool) -> Self {
116        Self(if value { "true".into() } else { "false".into() })
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use std::env;
123
124    pub fn get_test_api_key() -> String {
125        let api_key = env::var("SHODAN_TEST_KEY");
126        match api_key {
127            Ok(key) => key,
128            Err(_) => panic!("Did not specify a shodan API key for testing"),
129        }
130    }
131}