fn read_until_whitespace( stream_buffer: &mut [u8], stream_initial_index: usize, ) -> super::Result { let buffer = String::from_utf8( (stream_initial_index..stream_buffer.len()) .map_while(|stream_index| { let byte = stream_buffer[stream_index]; if byte == b' ' || byte == b'\r' && stream_buffer[stream_index + 1] == b'\n' { return None; } Some(byte) }) .collect(), )?; Ok(buffer) } pub struct HttpStart { pub method: String, pub path: String, pub version: String, pub total_length: usize, } pub fn http_start(stream_buffer: &mut [u8]) -> super::Result { let mut total_length = 0; let method = read_until_whitespace(stream_buffer, total_length)?; total_length += method.len() + 1; let path = read_until_whitespace(stream_buffer, total_length)?; total_length += path.len() + 1; let version = read_until_whitespace(stream_buffer, total_length)?; total_length += version.len() + 2; // CRLF Ok(HttpStart { method, path, version, total_length, }) } pub struct HttpHeader { pub key: String, pub content: String, pub total_length: usize, } pub fn http_header( stream_buffer: &mut [u8], stream_initial_index: usize, ) -> super::Result { if read_until_whitespace(stream_buffer, stream_initial_index)?.is_empty() { return Err(super::Error::InvalidHeader); } let key = String::from_utf8( (stream_initial_index..stream_buffer.len()) .map_while(|stream_index| { let byte = stream_buffer[stream_index]; if byte == b':' { None } else { Some(byte) } }) .collect(), )?; let content = String::from_utf8( (stream_initial_index + key.len() + 2..stream_buffer.len()) .map_while(|stream_index| { let byte = stream_buffer[stream_index]; if byte == b'\r' && stream_buffer[stream_index + 1] == b'\n' { None } else { Some(byte) } }) .collect(), )?; let total_length = key.len() + content.len() + 4; // ': ' + '\r\n' Ok(HttpHeader { key, content, total_length, }) } #[cfg(test)] mod test { use super::*; #[test] fn valid_http_header() { let mut buffer = b"Content-Type: application/json\r\n".to_owned(); let len = buffer.len(); let header = http_header(&mut buffer, 0).expect("should not fail with valid input"); assert_eq!(header.total_length, len, "total length should be {len}"); assert_eq!( &header.content, "application/json", "header content should be application/json" ); assert_eq!( &header.key, "Content-Type", "header key should be Content-Type" ); } #[test] fn invalid_http_header() { let mut buffer = b"\r\n".to_owned(); let result = http_header(&mut buffer, 0) .err() .expect("should fail with invalid input"); assert_eq!( result, crate::parse::Error::InvalidHeader, "should return ParseError::InvalidHeader on invalid header" ); } #[test] fn valid_http_start() { let mut buffer = b"GET /resource HTTP/1.1\r\n".to_owned(); let len = buffer.len(); let start = http_start(&mut buffer).expect("should not fail with valid input"); assert_eq!(start.total_length, len, "total length should be {len}"); assert_eq!(&start.method, "GET", "method should be GET"); assert_eq!(&start.path, "/resource", "path should be /resource"); assert_eq!(&start.version, "HTTP/1.1", "version should be HTTP/1.1"); } }