sentinel_modsec/variables/
request.rs

1//! Request data for variable resolution.
2
3use super::collection::{Collection, HashMapCollection};
4
5/// Request data container.
6#[derive(Debug, Clone, Default)]
7pub struct RequestData {
8    /// HTTP method.
9    pub method: String,
10    /// Request URI (with query string).
11    pub uri: String,
12    /// Raw URI.
13    pub uri_raw: String,
14    /// Request path (without query string).
15    pub path: String,
16    /// Query string.
17    pub query_string: String,
18    /// HTTP protocol version.
19    pub protocol: String,
20    /// Request headers.
21    pub headers: HashMapCollection,
22    /// GET arguments.
23    pub args_get: HashMapCollection,
24    /// POST arguments.
25    pub args_post: HashMapCollection,
26    /// Cookies.
27    pub cookies: HashMapCollection,
28    /// Request body.
29    pub body: Vec<u8>,
30    /// Client IP address.
31    pub client_ip: String,
32    /// Client port.
33    pub client_port: u16,
34    /// Server name.
35    pub server_name: String,
36    /// Server port.
37    pub server_port: u16,
38}
39
40impl RequestData {
41    /// Create new request data.
42    pub fn new() -> Self {
43        Self::default()
44    }
45
46    /// Set the URI and parse path/query string.
47    pub fn set_uri(&mut self, uri: &str) {
48        self.uri = uri.to_string();
49        self.uri_raw = uri.to_string();
50
51        if let Some(pos) = uri.find('?') {
52            self.path = uri[..pos].to_string();
53            self.query_string = uri[pos + 1..].to_string();
54            self.parse_query_string(&self.query_string.clone());
55        } else {
56            self.path = uri.to_string();
57            self.query_string.clear();
58        }
59    }
60
61    /// Set the HTTP method.
62    pub fn set_method(&mut self, method: &str) {
63        self.method = method.to_string();
64    }
65
66    /// Set the protocol.
67    pub fn set_protocol(&mut self, protocol: &str) {
68        self.protocol = protocol.to_string();
69    }
70
71    /// Add a request header.
72    pub fn add_header(&mut self, name: &str, value: &str) {
73        self.headers.add(name.to_lowercase(), value.to_string());
74    }
75
76    /// Append to request body.
77    pub fn append_body(&mut self, data: &[u8]) {
78        self.body.extend_from_slice(data);
79    }
80
81    /// Get body as string.
82    pub fn body_str(&self) -> String {
83        String::from_utf8_lossy(&self.body).to_string()
84    }
85
86    /// Get body length.
87    pub fn body_length(&self) -> usize {
88        self.body.len()
89    }
90
91    /// Parse query string into args_get.
92    fn parse_query_string(&mut self, qs: &str) {
93        for pair in qs.split('&') {
94            if let Some(pos) = pair.find('=') {
95                let key = &pair[..pos];
96                let value = &pair[pos + 1..];
97                // URL decode
98                let key = percent_encoding::percent_decode_str(key)
99                    .decode_utf8_lossy()
100                    .to_string();
101                let value = percent_encoding::percent_decode_str(value)
102                    .decode_utf8_lossy()
103                    .to_string();
104                self.args_get.add(key, value);
105            } else if !pair.is_empty() {
106                let key = percent_encoding::percent_decode_str(pair)
107                    .decode_utf8_lossy()
108                    .to_string();
109                self.args_get.add(key, String::new());
110            }
111        }
112    }
113
114    /// Parse form body into args_post.
115    pub fn parse_form_body(&mut self) {
116        let body_str = self.body_str();
117        for pair in body_str.split('&') {
118            if let Some(pos) = pair.find('=') {
119                let key = &pair[..pos];
120                let value = &pair[pos + 1..];
121                let key = percent_encoding::percent_decode_str(key)
122                    .decode_utf8_lossy()
123                    .to_string();
124                let value = percent_encoding::percent_decode_str(value)
125                    .decode_utf8_lossy()
126                    .to_string();
127                self.args_post.add(key, value);
128            }
129        }
130    }
131
132    /// Get all arguments (GET + POST combined).
133    pub fn all_args(&self) -> Vec<(&str, &str)> {
134        let mut all = self.args_get.all();
135        all.extend(self.args_post.all());
136        all
137    }
138}