1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
use crate::request;
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Read};
use std::net::TcpStream;

pub struct HttpParser {}

impl HttpParser {
    pub fn parse(stream: &TcpStream) -> Result<request::Request, String> {
        let mut reader = BufReader::new(stream);
        // Read first line
        let mut http_request_line = String::new();
        let _result = reader.by_ref().read_line(&mut http_request_line);
        let http_request_line_split: Vec<&str> = http_request_line.split_whitespace().collect();

        // Input validation
        if http_request_line_split.len() != 3 {
            return Err(format!(
                "Request line not correct syntax: {}",
                http_request_line
            ));
        }

        // Process headers and body
        let mut http_request_headers = HashMap::new();
        for line_result in reader.by_ref().lines() {
            let line = match line_result {
                Ok(line_string) => line_string,
                Err(_error) => String::from("ERROR"),
            };
            if line == "" {
                break;
            }
            if line == "ERROR" {
                return Err(String::from("Failed to read line from TCP stream"));
            }
            let mut iter = line.split(": ");
            let key = match iter.next() {
                Some(result) => result,
                None => "Error: no key",
            };
            if key == "Error: no key" {
                return Err(String::from("Faulty request syntax, could not parse"));
            }
            let value = match iter.next() {
                Some(result) => result,
                None => "Error: no value",
            };
            if value == "Error: no value" {
                return Err(String::from("Faulty request syntax, could not parse"));
            }
            http_request_headers.insert(key.to_lowercase(), String::from(value));
        }
        let mut body = String::from("");

        // If content lenght header is set we assume it has a body and try to read it
        if http_request_headers.contains_key("content-length") {
            let body_length: i32 = match http_request_headers["content-length"].parse() {
                Ok(result) => result,
                Err(_error) => -1,
            };
            if body_length < 0 {
                return Err(String::from("Invalid content-legth header"));
            }
            let mut body_bytes = vec![0; body_length as usize];
            let result = reader.by_ref().read_exact(&mut body_bytes);
            let fail = match result {
                Ok(_result) => false,
                Err(_error) => true,
            };
            if fail {
                return Err(String::from("Read request body failed"));
            }
            for char_byte in body_bytes {
                body.push(char_byte as char);
            }
        }

        // Get parameters from request
        let params_split: Vec<&str> = http_request_line_split[1].split("?").collect();
        let mut parameters = HashMap::new();
        // If the url has parameters set, get them
        if params_split.len() > 1 {
            let parameters_vec: Vec<&str> = params_split[1].split("&").collect();
            for parameter in parameters_vec {
                let parameter_split: Vec<&str> = parameter.split("=").collect();
                if parameter_split.len() > 1 {
                    parameters.insert(
                        String::from(parameter_split[0]),
                        String::from(parameter_split[1]),
                    );
                }
            }
        }
        // TODO: add the real version
        let mut http_version = String::from("1.1");
        let version_split: Vec<&str> = http_request_line_split[2].split("/").collect();
        if version_split.len() > 1 {
            http_version = String::from(version_split[1]);
        }
        // TODO: make sure the method is full caps
        Ok(request::Request::new(
            String::from(http_request_line_split[1]),
            parameters,
            body,
            http_version,
            String::from(http_request_line_split[0]),
            http_request_headers,
        ))
    }
}