From 4e1ae2bfa187ce92501565e8cc73f29d78698067 Mon Sep 17 00:00:00 2001 From: AustrianToast Date: Sun, 23 Feb 2025 16:19:25 +0100 Subject: [PATCH] create a response builder and other stuff --- src/main.rs | 127 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 108 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8966b98..02decb8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,32 +4,40 @@ use std::{ net::{TcpListener, TcpStream}, }; +#[derive(PartialEq, Eq, Debug)] +enum RequestMethods { + NULL = -1, // This is only to initialise the struct + GET, + HEAD, +} + #[derive(Debug)] -struct StartLine { - method: String, +struct RequestLine { + method: RequestMethods, target: String, version: String, } -impl StartLine { +impl RequestLine { pub fn new() -> Self { Self { - method: String::new(), + method: RequestMethods::NULL, target: String::new(), version: String::new(), } } + // https://datatracker.ietf.org/doc/html/rfc9110#name-methods pub fn is_valid_method(method: &String) -> bool { if method.trim().is_empty() { return false; } - let tmp = method.as_str(); + // Only GET and HEAD are required, the rest is optional if [ - "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", + "GET", "HEAD", /*, "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE",*/ ] - .contains(&tmp) + .contains(&method.trim()) { return true; } else { @@ -38,6 +46,7 @@ impl StartLine { } // TODO: make the checks less shit and actually correct + // https://datatracker.ietf.org/doc/html/rfc9112#name-request-target pub fn is_valid_target(target: &String) -> bool { if target.trim().is_empty() { return false; @@ -88,7 +97,7 @@ impl StartLine { return false; } }; - let minor = match version_numbers[2].parse::() { + let minor = match version_numbers[1].parse::() { Ok(val) => val, Err(_) => { return false; @@ -106,30 +115,38 @@ impl StartLine { } } -fn parse_start_line(input: String) -> Option { +fn parse_start_line(input: String) -> Option { if input.ends_with(" ") { return None; } - let mut start_line = StartLine::new(); + let mut start_line = RequestLine::new(); let vec = input.trim().split(" ").collect::>(); if vec.len() != 3 { return None; } + // start_line.method will remain RequestMethods::NULL if it is not supported. let method = String::from(vec[0]); - if StartLine::is_valid_method(&method) { - start_line.method = method; + if RequestLine::is_valid_method(&method) { + // TODO: Change to a switch-case if I ever support more methods + + if method == String::from("GET") { + start_line.method = RequestMethods::GET; + } + if method == String::from("HEAD") { + start_line.method = RequestMethods::HEAD; + } } let target = String::from(vec[1]); - if StartLine::is_valid_target(&target) { + if RequestLine::is_valid_target(&target) { start_line.target = target; } let version = String::from(vec[2]); - if StartLine::is_valid_version(&version) { + if RequestLine::is_valid_version(&version) { if version.trim() == "HTTP/1.1" || version.trim() == "HTTP/1.0" { start_line.version = version; } @@ -179,6 +196,62 @@ fn parse_field_lines(reader: &mut BufReader<&mut TcpStream>) -> Option, + body: Vec, +) -> Vec { + let mut response: Vec = vec![]; + + // TODO: replaces with eiter Option or Result + if status_line.is_empty() { + return response; + } + + for byte in status_line.as_bytes().iter() { + response.push(*byte); + } + response.push(b'\r'); + response.push(b'\n'); + + if !field_lines.is_empty() { + for field_line in field_lines.iter() { + for byte in field_line.0.as_bytes().iter() { + response.push(*byte); + } + + response.push(b':'); + response.push(b' '); + + for byte in field_line.1.as_bytes().iter() { + response.push(*byte); + } + + response.push(b'\r'); + response.push(b'\n'); + } + } + + // Mandatory empty line between header and body + response.push(b'\r'); + response.push(b'\n'); + + if body.is_empty() { + return response; + } + + if method != RequestMethods::HEAD && method != RequestMethods::NULL { + for byte in body.iter() { + response.push(*byte); + } + response.push(b'\r'); + response.push(b'\n'); + } + + return response; +} + fn handle_request(mut stream: TcpStream) { let mut line = String::new(); let mut reader = BufReader::new(&mut stream); @@ -199,7 +272,7 @@ fn handle_request(mut stream: TcpStream) { return; } - // I will not support ignoring the CR + // All lines MUST end with a CRLF if !line.ends_with("\r\n") { stream .write_all(b"HTTP/1.1 400 Bad Request\r\n\r\nLines must end with CRLF") @@ -226,7 +299,7 @@ fn handle_request(mut stream: TcpStream) { }; dbg!(&start_line); - if start_line.method != "GET" { + if start_line.method == RequestMethods::NULL { stream .write_all(b"HTTP/1.1 501 Not Implemented\r\n\r\nServer currently only supports GET") .unwrap(); @@ -244,13 +317,29 @@ fn handle_request(mut stream: TcpStream) { }; dbg!(&field_lines); + // TODO: Read the body // 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(); + // TODO: Act upon the request + + // TODO: Figure out why this doesn't work' + let header: HashMap = HashMap::new(); + let body: Vec = vec![]; + + let response = response_builder( + start_line.method, + String::from("HTTP/1.1 200 OK"), + header, + body, + ); + + stream.write_all(&response).unwrap(); + + // stream + // .write_all(b"HTTP/1.1 200 OK\r\n\r\nHello, World!\r\n") + // .unwrap(); } fn main() {