Skip to main content

sqlmap_rs/
types.rs

1//! Core type definitions for SQLMap REST API (`sqlmapapi`) payloads.
2
3use serde::{Deserialize, Serialize};
4
5/// Response when creating a new task.
6#[derive(Debug, Clone, Deserialize)]
7pub struct NewTaskResponse {
8    /// True if task creation succeeded
9    pub success: bool,
10    /// The unique execution ID assigned to this task
11    pub taskid: Option<String>,
12    /// Error or info message
13    pub message: Option<String>,
14}
15
16/// Generic success response.
17#[derive(Debug, Clone, Deserialize)]
18pub struct BasicResponse {
19    /// True if operation succeeded
20    pub success: bool,
21    /// Detailed message
22    pub message: Option<String>,
23}
24
25/// Response containing current execution status.
26#[derive(Debug, Clone, Deserialize)]
27pub struct StatusResponse {
28    /// True if request succeeded
29    pub success: bool,
30    /// Current engine status ("running", "terminated", etc.)
31    pub status: Option<String>,
32    /// Underlying process exit code
33    pub returncode: Option<i32>,
34}
35
36/// A chunk of extracted data reported by SQLMap engine.
37#[derive(Debug, Clone, Deserialize, Serialize)]
38pub struct SqlmapDataChunk {
39    /// 0 (log), 1 (vulnerabilities), etc.
40    pub r#type: i32,
41    /// The actual JSON payload chunk
42    pub value: serde_json::Value,
43}
44
45/// Final payload block returning all gathered data for a task.
46#[derive(Debug, Clone, Deserialize)]
47pub struct DataResponse {
48    /// True if fetch succeeded
49    pub success: bool,
50    /// The aggregated data chunks representing SQL injected results
51    pub data: Option<Vec<SqlmapDataChunk>>,
52    /// Unused array of structured errors
53    pub error: Option<Vec<String>>,
54}
55
56/// A parsed finding representing a confirmed injection.
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct SqlmapFinding {
59    /// Original parameter attacked
60    pub parameter: String,
61    /// Classification of injection
62    pub vulnerability_type: String,
63    /// Raw payload executed
64    pub payload: String,
65    /// Arbitrary engine output block
66    pub details: serde_json::Value,
67}
68
69impl DataResponse {
70    /// Extract structured findings from the raw data chunks.
71    ///
72    /// Type 1 chunks contain vulnerability data. This parses them into
73    /// `SqlmapFinding` structs with parameter, type, payload, and details.
74    pub fn findings(&self) -> Vec<SqlmapFinding> {
75        let Some(ref chunks) = self.data else { return vec![] };
76        let mut findings = Vec::new();
77
78        for chunk in chunks {
79            // Type 1 = vulnerability findings
80            if chunk.r#type == 1 {
81                if let Some(arr) = chunk.value.as_array() {
82                    for item in arr {
83                        if let Some(obj) = item.as_object() {
84                            let parameter = obj.get("parameter")
85                                .and_then(|v| v.as_str())
86                                .unwrap_or("unknown")
87                                .to_string();
88                            let vulnerability_type = obj.get("type")
89                                .and_then(|v| v.as_str())
90                                .unwrap_or("unknown")
91                                .to_string();
92                            let payload = obj.get("payload")
93                                .and_then(|v| v.as_str())
94                                .unwrap_or("")
95                                .to_string();
96                            findings.push(SqlmapFinding {
97                                parameter,
98                                vulnerability_type,
99                                payload,
100                                details: item.clone(),
101                            });
102                        }
103                    }
104                }
105            }
106        }
107
108        findings
109    }
110}
111
112/// Configuration payload mapped directly to SQLMap CLI arguments.
113#[derive(Debug, Clone, Serialize, Default)]
114pub struct SqlmapOptions {
115    /// The target URL
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub url: Option<String>,
118
119    /// Target directly on specific parameter, e.g. "id"
120    #[serde(rename = "testParameter", skip_serializing_if = "Option::is_none")]
121    pub test_parameter: Option<String>,
122
123    /// Specific database management system. e.g. "MySQL"
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub dbms: Option<String>,
126
127    /// HTTP Cookie header value.
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub cookie: Option<String>,
130
131    /// Specific payload technqiues to test (B = Boolean blind, T = Time blind, E = Error, U = UNION query, S = Stacked queries).
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub tech: Option<String>,
134
135    /// Output extraction verbosity level (1-6). 
136    /// For the API, we usually keep this low since data extraction comes via REST endpoint `data`.
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub verbose: Option<i32>,
139
140    /// Number of concurrent workers (default is 1).
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub threads: Option<i32>,
143    
144    /// Do not ask for user input. Default is true for bot orchestration.
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub batch: Option<bool>,
147
148    /// HTTP headers to manually pass into the request.
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub headers: Option<String>,
151
152    /// Payload risk (1-3)
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub risk: Option<i32>,
155
156    /// Level of tests to perform (1-5, default 1)
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub level: Option<i32>,
159    
160    /// Use a proxy?
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub proxy: Option<String>,
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn empty_data_response_gives_no_findings() {
171        let resp = DataResponse { success: true, data: None, error: None };
172        assert!(resp.findings().is_empty());
173    }
174
175    #[test]
176    fn type_0_chunks_ignored() {
177        let resp = DataResponse {
178            success: true,
179            data: Some(vec![SqlmapDataChunk { r#type: 0, value: serde_json::json!("log message") }]),
180            error: None,
181        };
182        assert!(resp.findings().is_empty());
183    }
184
185    #[test]
186    fn type_1_chunk_parsed_as_finding() {
187        let resp = DataResponse {
188            success: true,
189            data: Some(vec![SqlmapDataChunk {
190                r#type: 1,
191                value: serde_json::json!([{
192                    "parameter": "id",
193                    "type": "boolean-based blind",
194                    "payload": "id=1 AND 1=1"
195                }]),
196            }]),
197            error: None,
198        };
199        let findings = resp.findings();
200        assert_eq!(findings.len(), 1);
201        assert_eq!(findings[0].parameter, "id");
202        assert_eq!(findings[0].vulnerability_type, "boolean-based blind");
203    }
204
205    #[test]
206    fn options_serialization() {
207        let opts = SqlmapOptions {
208            url: Some("http://test.com?id=1".into()),
209            level: Some(3),
210            risk: Some(2),
211            batch: Some(true),
212            ..Default::default()
213        };
214        let json = serde_json::to_string(&opts).unwrap();
215        assert!(json.contains("http://test.com"));
216        assert!(json.contains("\"level\":3"));
217        // None fields should be skipped
218        assert!(!json.contains("dbms"));
219    }
220}