1use std::collections::HashMap;
4use std::path::PathBuf;
5
6use pyo3::{PyResult, pyclass, pymethods};
7use uuid::Uuid;
8
9#[pyclass(frozen)]
11#[derive(Debug)]
12pub struct Label {
13 #[pyo3(get)]
15 pub id: u64,
16
17 #[pyo3(get)]
19 pub name: String,
20
21 #[pyo3(get)]
23 pub parent: Option<String>,
24}
25
26#[pymethods]
27impl Label {
28 #[must_use]
30 pub fn __repr__(&self) -> String {
31 if let Some(parent) = &self.parent {
32 format!("Label {}: {parent}/{}", self.id, self.name)
33 } else {
34 format!("Label {}: {}", self.id, self.name)
35 }
36 }
37
38 #[must_use]
40 pub fn __str__(&self) -> String {
41 self.name.clone()
42 }
43}
44
45impl From<malwaredb_client::malwaredb_api::Label> for Label {
46 fn from(label: malwaredb_client::malwaredb_api::Label) -> Self {
47 Self {
48 id: label.id,
49 name: label.name,
50 parent: label.parent,
51 }
52 }
53}
54
55#[pyclass(frozen, from_py_object)]
57#[derive(Debug, Clone)]
58pub struct ServerInfo {
59 #[pyo3(get)]
61 pub os_name: String,
62
63 #[pyo3(get)]
65 pub memory_used: String,
66
67 #[pyo3(get)]
69 pub mdb_version: String,
70
71 #[pyo3(get)]
73 pub db_version: String,
74
75 #[pyo3(get)]
77 pub db_size: String,
78
79 #[pyo3(get)]
81 pub num_samples: u64,
82
83 #[pyo3(get)]
85 pub num_users: u32,
86
87 #[pyo3(get)]
89 pub uptime: String,
90
91 #[pyo3(get)]
93 pub instance_name: String,
94}
95
96#[pymethods]
97impl ServerInfo {
98 #[must_use]
100 pub fn __repr__(&self) -> String {
101 format!("MalwareDB {} running on {} for {}", self.mdb_version, self.os_name, self.uptime)
102 }
103
104 #[must_use]
106 pub fn __str__(&self) -> String {
107 format!("MalwareDB {}", self.mdb_version)
108 }
109}
110
111impl From<malwaredb_client::malwaredb_api::ServerInfo> for ServerInfo {
112 fn from(value: malwaredb_client::malwaredb_api::ServerInfo) -> Self {
113 Self {
114 os_name: value.os_name,
115 memory_used: value.memory_used,
116 mdb_version: value.mdb_version.to_string(),
117 db_version: value.db_version,
118 db_size: value.db_size,
119 num_samples: value.num_samples,
120 num_users: value.num_users,
121 uptime: value.uptime,
122 instance_name: value.instance_name,
123 }
124 }
125}
126
127#[pyclass(frozen)]
129#[derive(Debug)]
130pub struct Source {
131 #[pyo3(get)]
133 pub id: u32,
134
135 #[pyo3(get)]
137 pub name: String,
138
139 #[pyo3(get)]
141 pub description: Option<String>,
142
143 #[pyo3(get)]
145 pub url: Option<String>,
146
147 #[pyo3(get)]
149 pub first_acquisition: String,
150
151 #[pyo3(get)]
153 pub malicious: Option<bool>,
154}
155
156#[pymethods]
157impl Source {
158 #[must_use]
160 pub fn __repr__(&self) -> String {
161 let url = if let Some(url) = &self.url {
162 format!(" from {url}")
163 } else {
164 String::new()
165 };
166
167 let desc = if let Some(desc) = &self.description {
168 format!(" -- {desc}")
169 } else {
170 String::new()
171 };
172
173 format!("{}({}){url}{desc}", self.name, self.id)
174 }
175
176 #[must_use]
178 pub fn __str__(&self) -> String {
179 self.name.clone()
180 }
181}
182
183impl From<malwaredb_client::malwaredb_api::SourceInfo> for Source {
184 fn from(value: malwaredb_client::malwaredb_api::SourceInfo) -> Self {
185 Self {
186 id: value.id,
187 name: value.name,
188 description: value.description,
189 url: value.url,
190 first_acquisition: value.first_acquisition.to_rfc3339(),
191 malicious: value.malicious,
192 }
193 }
194}
195
196#[pyclass(frozen)]
198#[derive(Debug)]
199pub struct SupportedFileType {
200 #[pyo3(get)]
202 pub name: String,
203
204 #[pyo3(get)]
206 pub magic: Vec<String>,
207
208 #[pyo3(get)]
210 pub is_executable: bool,
211
212 #[pyo3(get)]
214 pub description: Option<String>,
215}
216
217#[pymethods]
218impl SupportedFileType {
219 #[must_use]
221 pub fn __repr__(&self) -> String {
222 format!("{}, starting with {}", self.name, self.magic.join(" or "))
223 }
224
225 #[must_use]
227 pub fn __str__(&self) -> String {
228 self.name.clone()
229 }
230}
231
232impl From<malwaredb_client::malwaredb_api::SupportedFileType> for SupportedFileType {
233 fn from(value: malwaredb_client::malwaredb_api::SupportedFileType) -> Self {
234 Self {
235 name: value.name,
236 magic: value.magic,
237 is_executable: value.is_executable,
238 description: value.description,
239 }
240 }
241}
242
243#[pyclass(frozen)]
245#[derive(Debug)]
246pub struct UserInfo {
247 #[pyo3(get)]
249 pub id: u32,
250
251 #[pyo3(get)]
253 pub username: String,
254
255 #[pyo3(get)]
257 pub groups: Vec<String>,
258
259 #[pyo3(get)]
261 pub sources: Vec<String>,
262
263 #[pyo3(get)]
265 pub is_admin: bool,
266
267 #[pyo3(get)]
269 pub created: String,
270
271 #[pyo3(get)]
273 pub is_readonly: bool,
274}
275
276#[pymethods]
277impl UserInfo {
278 #[must_use]
280 pub fn __str__(&self) -> String {
281 self.username.clone()
282 }
283}
284
285impl From<malwaredb_client::malwaredb_api::GetUserInfoResponse> for UserInfo {
286 fn from(value: malwaredb_client::malwaredb_api::GetUserInfoResponse) -> Self {
287 Self {
288 id: value.id,
289 username: value.username,
290 groups: value.groups,
291 sources: value.sources,
292 is_admin: value.is_admin,
293 created: value.created.to_rfc3339(),
294 is_readonly: value.is_readonly,
295 }
296 }
297}
298
299#[pyclass(frozen)]
301#[derive(Debug)]
302pub struct SearchResults {
303 #[pyo3(get)]
305 pub hashes: Vec<String>,
306
307 #[pyo3(get)]
309 pub results: u64,
310
311 #[pyo3(get)]
313 pub pagination: Option<Uuid>,
314
315 #[pyo3(get)]
317 pub message: Option<String>,
318}
319
320#[pymethods]
321impl SearchResults {
322 #[must_use]
324 pub fn __repr__(&self) -> String {
325 if self.pagination.is_some() && self.results > self.hashes.len() as u64 {
326 format!(
327 "Search results showing {} of {} available hashes",
328 self.hashes.len(),
329 self.results
330 )
331 } else if let Some(message) = &self.message {
332 format!("{message}: Search results showing {} hashes", self.hashes.len())
333 } else {
334 format!("Search results showing {} hashes", self.hashes.len())
335 }
336 }
337
338 #[must_use]
340 pub fn __str__(&self) -> String {
341 if let Some(message) = &self.message {
342 format!("{message}: Search results with {} hashes", self.hashes.len())
343 } else {
344 format!("Search results with {} hashes", self.hashes.len())
345 }
346 }
347}
348
349impl From<malwaredb_client::malwaredb_api::SearchResponse> for SearchResults {
350 fn from(value: malwaredb_client::malwaredb_api::SearchResponse) -> Self {
351 Self {
352 hashes: value.hashes,
353 results: value.total_results,
354 pagination: value.pagination,
355 message: value.message,
356 }
357 }
358}
359
360#[pyclass(frozen)]
362#[derive(Debug)]
363pub struct YaraResult {
364 #[pyo3(get)]
366 pub results: HashMap<String, Vec<String>>,
367}
368
369impl YaraResult {
370 #[must_use]
372 pub fn __len__(&self) -> usize {
373 self.results.len()
374 }
375}
376
377impl From<malwaredb_client::malwaredb_api::YaraSearchResponse> for YaraResult {
378 fn from(value: malwaredb_client::malwaredb_api::YaraSearchResponse) -> Self {
379 Self {
380 results: value
381 .results
382 .into_iter()
383 .map(|(k, v)| (k, v.into_iter().map(|h| h.to_string()).collect()))
384 .collect(),
385 }
386 }
387}
388
389#[pyclass(frozen, from_py_object)]
391#[derive(Debug, Clone)]
392pub struct DiscoveredServer {
393 #[pyo3(get)]
395 pub host: String,
396
397 #[pyo3(get)]
399 pub port: u16,
400
401 #[pyo3(get)]
403 pub ssl: bool,
404
405 #[pyo3(get)]
407 pub name: String,
408
409 #[pyo3(get)]
411 pub info: Option<ServerInfo>,
412}
413
414#[pymethods]
415impl DiscoveredServer {
416 #[pyo3(signature = (username, password, save = false, cert_path = None))]
422 pub fn connect(
423 &self,
424 username: String,
425 password: String,
426 save: bool,
427 cert_path: Option<PathBuf>,
428 ) -> PyResult<crate::MalwareDBClient> {
429 crate::MalwareDBClient::login(self.__str__(), username, password, save, cert_path)
430 }
431
432 #[must_use]
434 pub fn __repr__(&self) -> String {
435 if self.ssl {
436 format!("{} at https://{}:{}", self.name, self.host, self.port)
437 } else {
438 format!("{} at http://{}:{}", self.name, self.host, self.port)
439 }
440 }
441
442 #[must_use]
444 pub fn __str__(&self) -> String {
445 if self.ssl {
446 format!("https://{}:{}", self.host, self.port)
447 } else {
448 format!("http://{}:{}", self.host, self.port)
449 }
450 }
451}
452
453impl From<malwaredb_client::MalwareDBServer> for DiscoveredServer {
454 fn from(value: malwaredb_client::MalwareDBServer) -> Self {
455 let info = value.server_info_blocking().ok();
456 Self {
457 host: value.host,
458 port: value.port,
459 ssl: value.ssl,
460 name: value.name,
461 info: info.map(Into::into),
462 }
463 }
464}