diff --git a/.gitignore b/.gitignore index 4fffb2f..6aa9b21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +*.swp + /target /Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 8b1c9f1..a41494c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +lazy_static = "1.4.0" +regex = "1" diff --git a/src/lib.rs b/src/lib.rs index 7d12d9a..dbee0cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,11 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} +//! http parses the request according to the HTTP message specifications +//! it also includes `HTTPResponse` to build an HTTPResponse +//! +//! see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages +//! NOTE: only few parts of the specification has been implemented -#[cfg(test)] -mod tests { - use super::*; +mod start_line; +mod version; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub use start_line::HTTPStartLine; +pub use version::HTTPVersion; diff --git a/src/request.rs b/src/request.rs new file mode 100644 index 0000000..c3678ea --- /dev/null +++ b/src/request.rs @@ -0,0 +1,6 @@ +use std::collections::VecDeque; + +type RequestParts = (String, VecDeque, String); + +const HTTP_REQUEST_SEPARATOR: &'static str = "\r\n"; +const NULL_CHAR: &'static str = "\0"; diff --git a/src/start_line.rs b/src/start_line.rs new file mode 100644 index 0000000..6c76620 --- /dev/null +++ b/src/start_line.rs @@ -0,0 +1,91 @@ +use lazy_static::lazy_static; +use regex::Regex; + +use crate::version::HTTPVersion; + +lazy_static! { + static ref HTTP_VERSION_REGEX: Regex = Regex::new("^HTTP/(1.1|1.0|2)$").unwrap(); +} + +#[derive(Debug)] +pub struct HTTPStartLine<'a> { + method: &'a str, + target: &'a str, + version: HTTPVersion, +} + +impl<'a> HTTPStartLine<'a> { + fn new(method: &'a str, target: &'a str, version: HTTPVersion) -> Self { + HTTPStartLine { + method: &method, + target: &target, + version: version, + } + } + + fn parse(start_line: &'a str) -> Result { + let parts: Vec<&str> = start_line.split(" ").collect(); + if parts.len() < 3 { + return Err("unable to parse the start correctly"); + } + + let (method, target, version) = (parts[0], parts[1], parts[2]); + + if !Self::check_version(version) { + return Err("http version validation failed, unknown version"); + } + + Ok(HTTPStartLine::new( + method, + target, + HTTPVersion::from(version), + )) + } + + fn check_version(version: &str) -> bool { + HTTP_VERSION_REGEX.is_match(version) + } +} + +#[test] +fn test_start_line() { + struct Expect<'a> { + method: &'a str, + target: &'a str, + version: HTTPVersion, + } + + let test_cases: [(&str, Expect, bool); 2] = [ + ( + "POST /get/ HTTP/1.1", + Expect { + method: "POST", + target: "/get/", + version: HTTPVersion::Http1_1, + }, + true, + ), + ( + "POST /get/ HTTP/1.1 blablablalfjhskfh", + Expect { + method: "", + target: "", + version: HTTPVersion::Unknown, + }, + false, + ), + ]; + + for (start_line, expect, is_valid) in test_cases { + let res = HTTPStartLine::parse(start_line); + if is_valid { + assert!(res.is_ok()); + + let st = res.unwrap(); + + assert_eq!(expect.method, st.method); + assert_eq!(expect.target, st.target); + assert_eq!(expect.target, st.target); + } + } +} diff --git a/src/version.rs b/src/version.rs new file mode 100644 index 0000000..b8d656b --- /dev/null +++ b/src/version.rs @@ -0,0 +1,29 @@ +#[derive(Debug)] +pub enum HTTPVersion { + Http1_0, + Http1_1, + Http2, + Unknown, +} + +impl Into for HTTPVersion { + fn into(self) -> String { + match self { + Self::Http1_0 => "HTTP/1.0".to_string(), + Self::Http1_1 => "HTTP/1.1".to_string(), + Self::Http2 => "HTTP/2".to_string(), + Self::Unknown => "UNKNOWN".to_string(), + } + } +} + +impl From<&str> for HTTPVersion { + fn from(http_version: &str) -> Self { + match http_version { + "HTTP/1.0" => Self::Http1_0, + "HTTP/1.1" => Self::Http1_1, + "HTTP/2" => Self::Http2, + _ => Self::Unknown, + } + } +}