1use std::collections::HashMap;
4use std::path::PathBuf;
5
6use pyo3::{pyclass, pymethods, PyResult};
7use uuid::Uuid;
8
9#[pyclass(frozen)]
11#[derive(Debug, Clone)]
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)]
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!(
102 "MalwareDB {} running on {} for {}",
103 self.mdb_version, self.os_name, self.uptime
104 )
105 }
106
107 #[must_use]
109 pub fn __str__(&self) -> String {
110 format!("MalwareDB {}", self.mdb_version)
111 }
112}
113
114impl From<malwaredb_client::malwaredb_api::ServerInfo> for ServerInfo {
115 fn from(value: malwaredb_client::malwaredb_api::ServerInfo) -> Self {
116 Self {
117 os_name: value.os_name,
118 memory_used: value.memory_used,
119 mdb_version: value.mdb_version.to_string(),
120 db_version: value.db_version,
121 db_size: value.db_size,
122 num_samples: value.num_samples,
123 num_users: value.num_users,
124 uptime: value.uptime,
125 instance_name: value.instance_name,
126 }
127 }
128}
129
130#[pyclass(frozen)]
132#[derive(Debug, Clone)]
133pub struct Source {
134 #[pyo3(get)]
136 pub id: u32,
137
138 #[pyo3(get)]
140 pub name: String,
141
142 #[pyo3(get)]
144 pub description: Option<String>,
145
146 #[pyo3(get)]
148 pub url: Option<String>,
149
150 #[pyo3(get)]
152 pub first_acquisition: String,
153
154 #[pyo3(get)]
156 pub malicious: Option<bool>,
157}
158
159#[pymethods]
160impl Source {
161 #[must_use]
163 pub fn __repr__(&self) -> String {
164 let url = if let Some(url) = &self.url {
165 format!(" from {url}")
166 } else {
167 String::new()
168 };
169
170 let desc = if let Some(desc) = &self.description {
171 format!(" -- {desc}")
172 } else {
173 String::new()
174 };
175
176 format!("{}({}){url}{desc}", self.name, self.id)
177 }
178
179 #[must_use]
181 pub fn __str__(&self) -> String {
182 self.name.clone()
183 }
184}
185
186impl From<malwaredb_client::malwaredb_api::SourceInfo> for Source {
187 fn from(value: malwaredb_client::malwaredb_api::SourceInfo) -> Self {
188 Self {
189 id: value.id,
190 name: value.name,
191 description: value.description,
192 url: value.url,
193 first_acquisition: value.first_acquisition.to_rfc3339(),
194 malicious: value.malicious,
195 }
196 }
197}
198
199#[pyclass(frozen)]
201#[derive(Debug, Clone)]
202pub struct SupportedFileType {
203 #[pyo3(get)]
205 pub name: String,
206
207 #[pyo3(get)]
209 pub magic: Vec<String>,
210
211 #[pyo3(get)]
213 pub is_executable: bool,
214
215 #[pyo3(get)]
217 pub description: Option<String>,
218}
219
220#[pymethods]
221impl SupportedFileType {
222 #[must_use]
224 pub fn __repr__(&self) -> String {
225 format!("{}, starting with {}", self.name, self.magic.join(" or "))
226 }
227
228 #[must_use]
230 pub fn __str__(&self) -> String {
231 self.name.clone()
232 }
233}
234
235impl From<malwaredb_client::malwaredb_api::SupportedFileType> for SupportedFileType {
236 fn from(value: malwaredb_client::malwaredb_api::SupportedFileType) -> Self {
237 Self {
238 name: value.name,
239 magic: value.magic,
240 is_executable: value.is_executable,
241 description: value.description,
242 }
243 }
244}
245
246#[pyclass(frozen)]
248#[derive(Debug, Clone)]
249pub struct UserInfo {
250 #[pyo3(get)]
252 pub id: u32,
253
254 #[pyo3(get)]
256 pub username: String,
257
258 #[pyo3(get)]
260 pub groups: Vec<String>,
261
262 #[pyo3(get)]
264 pub sources: Vec<String>,
265
266 #[pyo3(get)]
268 pub is_admin: bool,
269
270 #[pyo3(get)]
272 pub created: String,
273
274 #[pyo3(get)]
276 pub is_readonly: bool,
277}
278
279#[pymethods]
280impl UserInfo {
281 #[must_use]
283 pub fn __str__(&self) -> String {
284 self.username.clone()
285 }
286}
287
288impl From<malwaredb_client::malwaredb_api::GetUserInfoResponse> for UserInfo {
289 fn from(value: malwaredb_client::malwaredb_api::GetUserInfoResponse) -> Self {
290 Self {
291 id: value.id,
292 username: value.username,
293 groups: value.groups,
294 sources: value.sources,
295 is_admin: value.is_admin,
296 created: value.created.to_rfc3339(),
297 is_readonly: value.is_readonly,
298 }
299 }
300}
301
302#[pyclass(frozen)]
304#[derive(Debug, Clone)]
305pub struct SearchResults {
306 #[pyo3(get)]
308 pub hashes: Vec<String>,
309
310 #[pyo3(get)]
312 pub results: u64,
313
314 #[pyo3(get)]
316 pub pagination: Option<Uuid>,
317
318 #[pyo3(get)]
320 pub message: Option<String>,
321}
322
323#[pymethods]
324impl SearchResults {
325 #[must_use]
327 pub fn __repr__(&self) -> String {
328 if self.pagination.is_some() && self.results > self.hashes.len() as u64 {
329 format!(
330 "Search results showing {} of {} available hashes",
331 self.hashes.len(),
332 self.results
333 )
334 } else if let Some(message) = &self.message {
335 format!(
336 "{message}: Search results showing {} hashes",
337 self.hashes.len()
338 )
339 } else {
340 format!("Search results showing {} hashes", self.hashes.len())
341 }
342 }
343
344 #[must_use]
346 pub fn __str__(&self) -> String {
347 if let Some(message) = &self.message {
348 format!(
349 "{message}: Search results with {} hashes",
350 self.hashes.len()
351 )
352 } else {
353 format!("Search results with {} hashes", self.hashes.len())
354 }
355 }
356}
357
358impl From<malwaredb_client::malwaredb_api::SearchResponse> for SearchResults {
359 fn from(value: malwaredb_client::malwaredb_api::SearchResponse) -> Self {
360 Self {
361 hashes: value.hashes,
362 results: value.total_results,
363 pagination: value.pagination,
364 message: value.message,
365 }
366 }
367}
368
369#[pyclass(frozen)]
371#[derive(Debug, Clone)]
372pub struct YaraResult {
373 #[pyo3(get)]
375 pub results: HashMap<String, Vec<String>>,
376}
377
378impl YaraResult {
379 #[must_use]
381 pub fn __len__(&self) -> usize {
382 self.results.len()
383 }
384}
385
386impl From<malwaredb_client::malwaredb_api::YaraSearchResponse> for YaraResult {
387 fn from(value: malwaredb_client::malwaredb_api::YaraSearchResponse) -> Self {
388 Self {
389 results: value
390 .results
391 .into_iter()
392 .map(|(k, v)| (k, v.into_iter().map(|h| h.to_string()).collect()))
393 .collect(),
394 }
395 }
396}
397
398#[pyclass(frozen)]
400#[derive(Debug, Clone)]
401pub struct DiscoveredServer {
402 #[pyo3(get)]
404 pub host: String,
405
406 #[pyo3(get)]
408 pub port: u16,
409
410 #[pyo3(get)]
412 pub ssl: bool,
413
414 #[pyo3(get)]
416 pub name: String,
417
418 #[pyo3(get)]
420 pub info: Option<ServerInfo>,
421}
422
423#[pymethods]
424impl DiscoveredServer {
425 #[pyo3(signature = (username, password, save = false, cert_path = None))]
431 pub fn connect(
432 &self,
433 username: String,
434 password: String,
435 save: bool,
436 cert_path: Option<PathBuf>,
437 ) -> PyResult<crate::MalwareDBClient> {
438 crate::MalwareDBClient::login(self.__str__(), username, password, save, cert_path)
439 }
440
441 #[must_use]
443 pub fn __repr__(&self) -> String {
444 if self.ssl {
445 format!("{} at https://{}:{}", self.name, self.host, self.port)
446 } else {
447 format!("{} at http://{}:{}", self.name, self.host, self.port)
448 }
449 }
450
451 #[must_use]
453 pub fn __str__(&self) -> String {
454 if self.ssl {
455 format!("https://{}:{}", self.host, self.port)
456 } else {
457 format!("http://{}:{}", self.host, self.port)
458 }
459 }
460}
461
462impl From<malwaredb_client::MalwareDBServer> for DiscoveredServer {
463 fn from(value: malwaredb_client::MalwareDBServer) -> Self {
464 let info = value.server_info_blocking().ok();
465 Self {
466 host: value.host,
467 port: value.port,
468 ssl: value.ssl,
469 name: value.name,
470 info: info.map(Into::into),
471 }
472 }
473}