diff --git a/src/main.rs b/src/main.rs index 1331867..8966b98 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,10 +4,6 @@ use std::{ net::{TcpListener, TcpStream}, }; -const VALID_METHODS: [&str; 8] = [ - "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", -]; - #[derive(Debug)] struct StartLine { method: String, @@ -30,19 +26,47 @@ impl StartLine { } let tmp = method.as_str(); - if VALID_METHODS.contains(&tmp) { + if [ + "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", + ] + .contains(&tmp) + { return true; } else { return false; } } + // TODO: make the checks less shit and actually correct pub fn is_valid_target(target: &String) -> bool { if target.trim().is_empty() { return false; } - return true; + // origin-form + if target.starts_with("/") { + if target.contains("?") && target.split("?").count() != 2 { + return false; + } + return true; + } + + // absolute-form + if target.starts_with("http://") { + return true; + } + + // authority-form + if target.contains(":") && target.split(":").count() == 2 { + return true; + } + + // asterisk-form + if target.trim() == "*" { + return true; + } + + return false; } pub fn is_valid_version(version: &String) -> bool { @@ -50,11 +74,43 @@ impl StartLine { return false; } - return true; + let http_name = version.trim(); + + if http_name.starts_with("HTTP/") { + let version_numbers = http_name.trim_start_matches("HTTP/"); + let version_numbers = version_numbers.split(".").collect::>(); + if version_numbers.len() != 2 { + return false; + } + let major = match version_numbers[0].parse::() { + Ok(val) => val, + Err(_) => { + return false; + } + }; + let minor = match version_numbers[2].parse::() { + Ok(val) => val, + Err(_) => { + return false; + } + }; + + if major <= 9 && minor <= 9 { + return true; + } else { + return false; + } + } else { + return false; + } } } fn parse_start_line(input: String) -> Option { + if input.ends_with(" ") { + return None; + } + let mut start_line = StartLine::new(); let vec = input.trim().split(" ").collect::>(); @@ -74,12 +130,55 @@ fn parse_start_line(input: String) -> Option { let version = String::from(vec[2]); if StartLine::is_valid_version(&version) { - start_line.version = version; + if version.trim() == "HTTP/1.1" || version.trim() == "HTTP/1.0" { + start_line.version = version; + } } return Some(start_line); } +fn parse_field_lines(reader: &mut BufReader<&mut TcpStream>) -> Option> { + let mut line: String; + let mut field_lines: HashMap = HashMap::new(); + let mut is_first_line = true; + + // Read field-lines till I hit an empty line + loop { + line = String::new(); + reader.read_line(&mut line).unwrap(); + + if line.starts_with(" ") && is_first_line { + return None; + } + + if !line.ends_with("\r\n") { + return None; + } + + if line.trim().is_empty() { + break; + } + + let field_line = line.split_once(":").expect("Shits fucked: {}"); + + // Check if client has send more than one Host line + if field_lines.contains_key(&String::from("Host")) && field_line.0 == "Host" { + return None; + } + + field_lines.insert(field_line.0.to_owned(), field_line.1.trim().to_owned()); + + is_first_line = false; + } + + if !field_lines.contains_key(&String::from("Host")) { + return None; + } + + return Some(field_lines); +} + fn handle_request(mut stream: TcpStream) { let mut line = String::new(); let mut reader = BufReader::new(&mut stream); @@ -120,12 +219,11 @@ fn handle_request(mut stream: TcpStream) { Some(val) => val, None => { stream - .write_all(b"HTTP/1.1 400 Bad Request\r\n\r\nInvalid start-line") + .write_all(b"HTTP/1.1 400 Bad Request\r\n\r\nInvalid Header") .unwrap(); return; } }; - dbg!(&start_line); if start_line.method != "GET" { @@ -135,44 +233,21 @@ fn handle_request(mut stream: TcpStream) { return; } - let mut is_first_line = true; - let mut field_lines: HashMap = HashMap::new(); - - loop { - let mut line = String::new(); - reader.read_line(&mut line).unwrap(); - // print!("{line}"); - - if line.starts_with(" ") { - if !is_first_line { - continue; - } + let field_lines = match parse_field_lines(&mut reader) { + Some(val) => val, + None => { stream - .write_all(b"HTTP/1.1 400 Bad Request\r\n\r\nCannot have whitespace between the start-line and field-lines") + .write_all(b"HTTP/1.1 400 Bad Request\r\n\r\nInvalid Header") .unwrap(); return; } - - // I will not support ignoring the CR - if !line.ends_with("\r\n") { - stream - .write_all(b"HTTP/1.1 400 Bad Request\r\n\r\nLines must end with CRLF") - .unwrap(); - return; - } - - if line.trim().is_empty() { - break; - } - - let field_line = line.split_once(":").expect("Shits fucked: {}"); - field_lines.insert(field_line.0.to_owned(), field_line.1.trim().to_owned()); - - is_first_line = false; - } - + }; dbg!(&field_lines); + // let mut body: Vec = vec![]; + // reader.read_to_end(&mut body).unwrap(); + // dbg!(&body); + stream .write_all(b"HTTP/1.1 200 OK\r\n\r\nHello, World!\r\n") .unwrap();