misp_client/
warninglists.rs1use serde_json::{json, Value};
4use tracing::debug;
5
6use crate::client::MispClient;
7use crate::error::MispError;
8use crate::models::{Warninglist, WarninglistCheckResult, WarninglistMatch};
9
10#[derive(Clone)]
11pub struct WarninglistsClient {
12 client: MispClient,
13}
14
15impl WarninglistsClient {
16 pub fn new(client: MispClient) -> Self {
17 Self { client }
18 }
19
20 pub async fn list(&self) -> Result<Vec<Warninglist>, MispError> {
21 debug!("Listing warninglists");
22 let resp = self.client.get("/warninglists/index").await?;
23 parse_warninglists(resp)
24 }
25
26 pub async fn list_enabled(&self) -> Result<Vec<Warninglist>, MispError> {
27 let all = self.list().await?;
28 Ok(all
29 .into_iter()
30 .filter(|w| w.enabled == Some(true))
31 .collect())
32 }
33
34 pub async fn get(&self, id: &str) -> Result<Warninglist, MispError> {
35 debug!(%id, "Fetching warninglist");
36 let resp = self
37 .client
38 .get(&format!("/warninglists/view/{}", id))
39 .await?;
40 parse_warninglist(resp)
41 }
42
43 pub async fn check_value(&self, value: &str) -> Result<WarninglistCheckResult, MispError> {
44 debug!(%value, "Checking value against warninglists");
45 let body = json!({ "value": value });
46 let resp = self
47 .client
48 .post("/warninglists/checkValue", Some(body))
49 .await?;
50 parse_check_result(resp, value)
51 }
52
53 pub async fn check_values(
54 &self,
55 values: &[&str],
56 ) -> Result<Vec<WarninglistCheckResult>, MispError> {
57 debug!(count = values.len(), "Checking values against warninglists");
58 let body = json!({ "value": values });
59 let resp = self
60 .client
61 .post("/warninglists/checkValue", Some(body))
62 .await?;
63 parse_check_results(resp, values)
64 }
65
66 pub async fn is_whitelisted(&self, value: &str) -> Result<bool, MispError> {
67 let result = self.check_value(value).await?;
68 Ok(result.matched)
69 }
70
71 pub async fn get_matching_lists(
72 &self,
73 value: &str,
74 ) -> Result<Vec<WarninglistMatch>, MispError> {
75 let result = self.check_value(value).await?;
76 Ok(result.warninglists)
77 }
78}
79
80impl std::fmt::Debug for WarninglistsClient {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 f.debug_struct("WarninglistsClient").finish()
83 }
84}
85
86fn parse_warninglists(resp: Value) -> Result<Vec<Warninglist>, MispError> {
87 if let Some(wls) = resp.get("Warninglists") {
88 if let Some(arr) = wls.as_array() {
89 let lists: Result<Vec<Warninglist>, _> = arr
90 .iter()
91 .filter_map(|v| v.get("Warninglist"))
92 .map(|w| serde_json::from_value(w.clone()))
93 .collect();
94 return lists.map_err(MispError::Parse);
95 }
96 }
97 if let Some(arr) = resp.as_array() {
98 let lists: Result<Vec<Warninglist>, _> = arr
99 .iter()
100 .filter_map(|v| v.get("Warninglist"))
101 .map(|w| serde_json::from_value(w.clone()))
102 .collect();
103 return lists.map_err(MispError::Parse);
104 }
105 Err(MispError::InvalidResponse(
106 "unexpected warninglists format".into(),
107 ))
108}
109
110fn parse_warninglist(resp: Value) -> Result<Warninglist, MispError> {
111 if let Some(wl) = resp.get("Warninglist") {
112 return serde_json::from_value(wl.clone()).map_err(MispError::Parse);
113 }
114 Err(MispError::InvalidResponse(
115 "missing Warninglist wrapper".into(),
116 ))
117}
118
119fn parse_check_result(resp: Value, value: &str) -> Result<WarninglistCheckResult, MispError> {
120 let matched = resp
121 .get(value)
122 .and_then(|v| v.as_array())
123 .map(|arr| !arr.is_empty())
124 .unwrap_or(false);
125
126 let warninglists = if matched {
127 resp.get(value)
128 .and_then(|v| v.as_array())
129 .map(|arr| {
130 arr.iter()
131 .filter_map(|item| {
132 let id = item.get("id")?.as_str()?.to_string();
133 let name = item.get("name")?.as_str()?.to_string();
134 let matched_entry = item
135 .get("matched")
136 .and_then(|m| m.as_str())
137 .map(String::from);
138 Some(WarninglistMatch {
139 id,
140 name,
141 matched: matched_entry,
142 })
143 })
144 .collect()
145 })
146 .unwrap_or_default()
147 } else {
148 Vec::new()
149 };
150
151 Ok(WarninglistCheckResult {
152 value: value.to_string(),
153 matched,
154 warninglists,
155 })
156}
157
158fn parse_check_results(
159 resp: Value,
160 values: &[&str],
161) -> Result<Vec<WarninglistCheckResult>, MispError> {
162 values
163 .iter()
164 .map(|v| parse_check_result(resp.clone(), v))
165 .collect()
166}