kern/http/server/
request.rs

1//! HTTP request parsing
2
3use crate::byte::{split, splitn};
4use crate::http::common::ReadWrite;
5use crate::http::server::HttpSettings;
6use crate::{Fail, Result};
7
8use std::{collections::HashMap, net::SocketAddr};
9
10pub use crate::http::common::HttpMethod;
11
12/// HTTP request structure
13#[derive(Debug)]
14pub struct HttpRequest<'a> {
15    method: HttpMethod,
16    url: &'a str,
17    headers: HashMap<String, &'a str>,
18    get: HashMap<String, &'a str>,
19    post: HashMap<String, Vec<u8>>,
20    ip: String,
21    body: Vec<u8>,
22}
23
24impl<'a> HttpRequest<'a> {
25    /// Get HTTP request method
26    pub fn method(&self) -> &HttpMethod {
27        // return HTTP request method
28        &self.method
29    }
30
31    /// Get URL
32    pub fn url(&self) -> &str {
33        // return URL
34        self.url
35    }
36
37    /// Get headers map
38    pub fn headers(&self) -> &HashMap<String, &str> {
39        // return headers map
40        &self.headers
41    }
42
43    /// Get GET parameters
44    pub fn get(&self) -> &HashMap<String, &str> {
45        // return GET parameters map
46        &self.get
47    }
48
49    /// Get POST parameters
50    pub fn post(&self) -> &HashMap<String, Vec<u8>> {
51        // return POST parameters map
52        &self.post
53    }
54
55    /// Get POST parameters
56    pub fn post_utf8(&self) -> HashMap<String, String> {
57        // init map and iterate through byte map
58        let mut post_utf8 = HashMap::new();
59        for (k, v) in &self.post {
60            // parse and insert
61            post_utf8.insert(k.to_string(), String::from_utf8_lossy(v).to_string());
62        }
63
64        // return new UTF-8 POST parameters map
65        post_utf8
66    }
67
68    /// Get body
69    pub fn body(&self) -> &[u8] {
70        // return body string
71        &self.body
72    }
73
74    /// Get IP address
75    pub fn ip(&self) -> &str {
76        // return IP address string
77        &self.ip
78    }
79
80    /// Parse HTTP request
81    pub fn from(
82        raw_header: &'a str,
83        mut partial_body: Vec<u8>,
84        stream: &mut impl ReadWrite,
85        address: SocketAddr,
86        settings: &HttpSettings,
87    ) -> Result<Self> {
88        // split header
89        let mut header = raw_header.lines();
90        let mut reqln = header
91            .next()
92            .ok_or_else(|| Fail::new("Empty header"))?
93            .split(' ');
94
95        // parse method
96        let method: HttpMethod = reqln
97            .next()
98            .ok_or_else(|| Fail::new("No method in header"))?
99            .try_into()?;
100
101        // parse url and split raw get parameters
102        let mut get_raw = "";
103        let url = if let Some(full_url) = reqln.next() {
104            let mut split_url = full_url.splitn(2, '?');
105            let url = split_url
106                .next()
107                .ok_or_else(|| Fail::new("No URL in header"))?;
108            if let Some(params) = split_url.next() {
109                get_raw = params;
110            }
111            url
112        } else {
113            "/"
114        };
115
116        // parse headers
117        let mut headers = HashMap::new();
118        header.for_each(|hl| {
119            let mut hls = hl.splitn(2, ':');
120            if let (Some(key), Some(value)) = (hls.next(), hls.next()) {
121                headers.insert(key.trim().to_lowercase(), value.trim());
122            }
123        });
124
125        // get content length
126        let buf_len = if let Some(buf_len) = headers.get("Content-Length") {
127            Some(buf_len)
128        } else {
129            headers.get("content-length")
130        };
131
132        // read rest of body
133        if let Some(buf_len) = buf_len {
134            // parse buffer length
135            let con_len = buf_len
136                .parse::<usize>()
137                .ok()
138                .ok_or_else(|| Fail::new("Content-Length is not of type usize"))?;
139
140            // check if body size is ok.
141            if con_len > settings.max_body_size {
142                return Fail::from("Max body size exceeded");
143            }
144
145            // read body
146            let mut read_fails = 0;
147            while partial_body.len() < con_len {
148                // read next buffer
149                let mut rest_body = vec![0u8; settings.body_buffer];
150                let length = stream
151                    .read(&mut rest_body)
152                    .ok()
153                    .ok_or_else(|| Fail::new("Stream broken"))?;
154                rest_body.truncate(length);
155                partial_body.append(&mut rest_body);
156
157                // check if didn't read fully
158                if length < settings.body_buffer {
159                    read_fails += 1;
160
161                    // failed too often
162                    if read_fails > settings.body_read_attempts {
163                        return Fail::from("Read body failed too often");
164                    }
165                }
166            }
167        }
168
169        // parse GET and POST parameters
170        let get = parse_parameters(get_raw, |v| v)?;
171        let post = parse_post(&headers, &partial_body).unwrap_or_default();
172
173        // ip: x-real-ip if socket ip is loopback else socket ip
174        let ip = match headers.get("x-real-ip") {
175            Some(x_real_ip) if address.ip().is_loopback() => x_real_ip.to_string(),
176            _ => address.ip().to_string(),
177        };
178
179        Ok(Self {
180            method,
181            url,
182            headers,
183            get,
184            post,
185            ip,
186            body: partial_body,
187        })
188    }
189}
190
191/// Parse POST parameters to map
192fn parse_post(headers: &HashMap<String, &str>, body: &[u8]) -> Result<HashMap<String, Vec<u8>>> {
193    match headers.get("content-type") {
194        Some(&content_type_header) => {
195            let mut content_type_header = content_type_header.split(';').map(|s| s.trim());
196            let mut content_type = None;
197            let boundary = content_type_header.find_map(|s| {
198                if s.starts_with("boundary=") {
199                    return s.split('=').nth(1);
200                } else if content_type.is_none() {
201                    content_type = Some(s);
202                }
203                None
204            });
205            match content_type {
206                Some(content_type) => {
207                    if content_type == "multipart/form-data" {
208                        parse_post_upload(
209                            body,
210                            boundary.ok_or_else(|| Fail::new("post upload, but no boundary"))?,
211                        )
212                    } else {
213                        parse_parameters(&String::from_utf8(body.to_vec())?, |v| {
214                            v.as_bytes().to_vec()
215                        })
216                    }
217                }
218                None => parse_parameters(&String::from_utf8(body.to_vec())?, |v| {
219                    v.as_bytes().to_vec()
220                }),
221            }
222        }
223        None => parse_parameters(&String::from_utf8(body.to_vec())?, |v| {
224            v.as_bytes().to_vec()
225        }),
226    }
227}
228
229/// Parse POST upload to map
230fn parse_post_upload(body: &[u8], boundary: &str) -> Result<HashMap<String, Vec<u8>>> {
231    // parameters map
232    let mut params = HashMap::new();
233
234    // split body into sections
235    let mut sections = split(&body, format!("--{boundary}\r\n"));
236    sections.remove(0);
237    for mut section in sections {
238        // check if last section
239        let last_sep = format!("--{boundary}--\r\n");
240        if section.ends_with(last_sep.as_bytes()) {
241            // remove ending seperator from last section
242            section = &section[..(section.len() - last_sep.len() - 2)];
243        }
244        // split lines (max 3)
245        let lines = splitn(3, &section, b"\r\n");
246
247        // parse name
248        let name = String::from_utf8_lossy(lines[0])
249            .split(';')
250            .map(|s| s.trim())
251            .find_map(|s| {
252                if s.starts_with("name=") {
253                    let name = s.split('=').nth(1)?;
254                    Some(name[1..(name.len() - 1)].to_lowercase())
255                } else {
256                    None
257                }
258            })
259            .ok_or_else(|| Fail::new("missing name in post body section"))?;
260
261        // get value
262        let data_section = lines
263            .get(2)
264            .ok_or_else(|| Fail::new("broken section in post body"))?;
265        let data_lines = splitn(2, data_section, b"\r\n");
266        let next_data_line = data_lines
267            .first()
268            .ok_or_else(|| Fail::new("broken section in post body"))?;
269        let value = if let Some(file_data_line) = data_lines.get(1) {
270            if next_data_line.is_empty() {
271                file_data_line.to_vec()
272            } else if file_data_line.is_empty() {
273                next_data_line.to_vec()
274            } else {
275                [&next_data_line[..], &b"\r\n"[..], &file_data_line[..]]
276                    .concat()
277                    .to_vec()
278            }
279        } else {
280            next_data_line.to_vec()
281        };
282
283        // insert into map
284        params.insert(name, value);
285    }
286
287    // return parameters map
288    Ok(params)
289}
290
291/// Parse GET parameters to map
292fn parse_parameters<'a, V>(
293    raw: &'a str,
294    process_value: fn(&'a str) -> V,
295) -> Result<HashMap<String, V>> {
296    // parameters map
297    let mut params = HashMap::new();
298
299    // split parameters by ampersand
300    for p in raw.split('&') {
301        // split key and value and add to map
302        let mut ps = p.splitn(2, '=');
303        params.insert(
304            ps.next()
305                .ok_or_else(|| Fail::new("broken x-www-form-urlencoded parameters"))?
306                .trim()
307                .to_lowercase(), // trimmed key
308            // correct value type
309            process_value(if let Some(value) = ps.next() {
310                value.trim() // trimmed value
311            } else {
312                "" // no value, is option
313            }),
314        );
315    }
316
317    // return parameters map
318    Ok(params)
319}