use crate::http; use crate::http::Status; pub fn parse_request(stream: &mut http::Stream) -> Result, String> { let mut buf = [0; 4096]; let size = stream.peek(&mut buf).unwrap(); if size == 0 { return Ok(None); } let mut parser = Parser::new_request_parser(&buf[..size]); let header_size = parser.parse()?; let mut header_fields = Vec::new(); for (name, value) in parser.headers { header_fields.push(http::HeaderField { name: String::from(name), value: String::from(value), }); } let request = http::Request { version: String::from(parser.http_version.unwrap()), method: http::Method::from_str(parser.method.unwrap()), uri: String::from(parser.uri.unwrap()), header_fields, }; stream.read_exact(&mut buf[..header_size]).unwrap(); Ok(Some(request)) } pub fn parse_response(stream: &mut http::Stream) -> Result { let mut buf = [0; 4096]; let size = stream.peek(&mut buf).unwrap(); let mut parser = Parser::new_request_parser(&buf[..size]); let header_size = parser.parse().unwrap(); let status_code = parser.status_code.unwrap(); let status_code = match status_code.parse() { Ok(v) => v, Err(e) => return Err(format!("{}", e)), }; let mut header_fields = Vec::new(); for (name, value) in parser.headers { header_fields.push(http::HeaderField { name: String::from(name), value: String::from(value), }); } let response = http::Response { version: String::from(parser.http_version.unwrap()), status: Status::new_custom(status_code, parser.status_message.unwrap()), header_fields, }; stream.read_exact(&mut buf[..header_size]).unwrap(); Ok(response) } #[derive(Copy, Clone)] enum State<'a> { Method, Uri, Http(&'a State<'a>), HttpVersion(&'a State<'a>), StatusCode, StatusMessage, HeaderName, HeaderValue, Finish, CRLF(&'a State<'a>), Error, } struct Parser<'a> { state: State<'a>, buf: &'a [u8], str_start: usize, header_size: usize, method: Option<&'a str>, uri: Option<&'a str>, http_version: Option<&'a str>, status_code: Option<&'a str>, status_message: Option<&'a str>, headers: Vec<(&'a str, &'a str)>, } impl Parser<'_> { fn new_request_parser(buf: &[u8]) -> Parser { Parser { state: State::Method, buf, str_start: 0, header_size: 0, method: None, uri: None, http_version: None, status_code: None, status_message: None, headers: Vec::new(), } } fn new_response_parser(buf: &[u8]) -> Parser { Parser { state: State::Http(&State::StatusCode), buf, str_start: 0, header_size: 0, method: None, uri: None, http_version: None, status_code: None, status_message: None, headers: Vec::new(), } } fn parse(&mut self) -> Result { for char in self.buf { self.next(*char); match self.state { State::Finish => return Ok(self.header_size), State::Error => { return Err(format!( "invalid character at position {}", self.header_size - 1 )) } _ => {} } } return Err(String::from("input too short")); } fn next(&mut self, char: u8) { self.header_size += 1; let get_str = || std::str::from_utf8(&self.buf[self.str_start..self.header_size - 1]).unwrap(); self.state = match &self.state { State::Error => State::Error, State::Finish => State::Error, State::Method => match char { 0x41..=0x5A => State::Method, 0x20 => { self.method = Some(get_str()); self.str_start = self.header_size; State::Uri } _ => State::Error, }, State::Uri => match char { 0x21..=0x7E => State::Uri, 0x20 => { self.uri = Some(get_str()); self.str_start = self.header_size; State::Http(&State::HeaderName) } _ => State::Error, }, State::Http(next) => match char { 0x48 | 0x54 | 0x50 => State::Http(next), 0x2F => { let http = get_str(); self.str_start = self.header_size; if http != "HTTP" { State::Error } else { State::HttpVersion(next) } } _ => State::Error, }, State::HttpVersion(next) => match char { 0x30..=0x39 | 0x2E => State::HttpVersion(next), 0x0D => match next { State::HeaderName => { self.http_version = Some(get_str()); State::CRLF(next) } _ => State::Error, }, 0x20 => match next { State::StatusCode => { self.http_version = Some(get_str()); self.str_start = self.header_size; State::StatusCode } _ => State::Error, }, _ => State::Error, }, State::StatusCode => match char { 0x30..=0x39 => State::StatusCode, 0x20 => { self.status_code = Some(get_str()); self.str_start = self.header_size; State::StatusMessage } _ => State::Error, }, State::StatusMessage => match char { 0x20..=0x7E => State::StatusMessage, 0x0D => { self.status_message = Some(get_str()); State::CRLF(&State::HeaderName) } _ => State::Error, }, State::HeaderName => match char { 0x0D => { if self.header_size == self.str_start + 1 { State::CRLF(&State::Finish) } else { State::Error } } 0x3A => { let header_name = get_str(); self.headers.push((header_name, "")); self.str_start = self.header_size; State::HeaderValue } 0x00..=0x1F | 0x7F | 0x80..=0xFF | 0x20 | 0x28 | 0x29 | 0x2C | 0x2F | 0x3A..=0x40 | 0x5B..=0x5D | 0x7B | 0x7D => State::Error, _ => State::HeaderName, }, State::HeaderValue => match char { 0x20..=0x7E | 0x09 => State::HeaderValue, 0x0D => { self.headers.last_mut().unwrap().1 = get_str().trim(); State::CRLF(&State::HeaderName) } _ => State::Error, }, State::CRLF(next) => match char { 0x0A => { self.str_start = self.header_size; *next.clone() } _ => State::Error, }, } } } #[cfg(test)] mod tests { #[test] fn simple_request() { let request: &str = "GET /index.html HTTP/1.1\r\n\ Host: www.example.com\r\n\ \r\n"; let mut parser = super::Parser::new_request_parser(request.as_bytes()); let size = parser.parse().unwrap(); assert_eq!(51, size); assert_eq!("GET", parser.method.unwrap()); assert_eq!("/index.html", parser.uri.unwrap()); assert_eq!("1.1", parser.http_version.unwrap()); assert_eq!(None, parser.status_code); assert_eq!(None, parser.status_message); assert_eq!(1, parser.headers.len()); assert_eq!(("Host", "www.example.com"), parser.headers[0]); } #[test] fn complex_request() { let request: &str = "POST /upload/file.txt HTTP/1.3\r\n\ Host: www.example.com \r\n\ Content-Length: 13 \r\n\ User-Agent: Mozilla/5.0 (X11; Linux x86_64) \r\n\ \r\n\ username=test"; let mut parser = super::Parser::new_request_parser(request.as_bytes()); let size = parser.parse().unwrap(); assert_eq!(129, size); assert_eq!("POST", parser.method.unwrap()); assert_eq!("/upload/file.txt", parser.uri.unwrap()); assert_eq!("1.3", parser.http_version.unwrap()); assert_eq!(None, parser.status_code); assert_eq!(None, parser.status_message); assert_eq!(3, parser.headers.len()); assert_eq!(("Host", "www.example.com"), parser.headers[0]); assert_eq!(("Content-Length", "13"), parser.headers[1]); assert_eq!( ("User-Agent", "Mozilla/5.0 (X11; Linux x86_64)"), parser.headers[2] ); assert_eq!("username=test", &request[size..]); } #[test] fn invalid_request_1() { let request: &str = "GET /files/größe.txt HTTP/1.1\r\n\r\n"; let mut parser = super::Parser::new_request_parser(request.as_bytes()); match parser.parse() { Ok(_v) => panic!("should fail"), Err(e) => assert_eq!("invalid character at position 13", e), } } #[test] fn invalid_request_2() { let request: &str = "GET /index.html HTT"; let mut parser = super::Parser::new_request_parser(request.as_bytes()); match parser.parse() { Ok(_v) => panic!("should fail"), Err(e) => assert_eq!("input too short", e), } } #[test] fn simple_response() { let response: &str = "HTTP/1.1 200 OK\r\n\ Content-Length: 12\r\n\ Content-Type: text/plain; charset=us-ascii\r\n\ \r\n\ Hello world!"; let mut parser = super::Parser::new_response_parser(response.as_bytes()); let size = parser.parse().unwrap(); assert_eq!(83, size); assert_eq!("200", parser.status_code.unwrap()); assert_eq!("OK", parser.status_message.unwrap()); assert_eq!("1.1", parser.http_version.unwrap()); assert_eq!(None, parser.method); assert_eq!(None, parser.uri); assert_eq!(2, parser.headers.len()); assert_eq!(("Content-Length", "12"), parser.headers[0]); assert_eq!( ("Content-Type", "text/plain; charset=us-ascii"), parser.headers[1] ); assert_eq!("Hello world!", &response[size..]); } }