use std::convert::From; use std::default::Default; use std::fmt; use std::io::{self, BufRead, BufReader, Cursor, Read}; use std::mem; use std::net::TcpStream; use std::str; #[derive(Debug)] pub struct Request { pub version: (u8, u8), pub method: String, pub path: String, pub headers: Vec<(String, String)>, pub body: Vec, error: Option, is_parsed: bool, last_header_field: Option, last_header_value: Option, } impl Request { pub fn is_head(&self) -> bool { self.method == "HEAD" } #[allow(clippy::missing_const_for_fn)] pub fn is_ok(&self) -> bool { self.error.is_none() } #[allow(clippy::missing_const_for_fn)] pub fn is_err(&self) -> bool { self.error.is_some() } #[allow(clippy::missing_const_for_fn)] pub fn error(&self) -> Option<&String> { self.error.as_ref() } pub fn find_header_values(&self, searched_field: &str) -> Vec<&str> { self.headers .iter() .filter_map(|(field, value)| { if field == searched_field { Some(value.as_str()) } else { None } }) .collect() } fn record_last_header(&mut self) { if self.last_header_field.is_some() && self.last_header_value.is_some() { let last_header_field = mem::replace(&mut self.last_header_field, None).unwrap(); let last_header_value = mem::replace(&mut self.last_header_value, None).unwrap(); self.headers .push((last_header_field.to_lowercase(), last_header_value)); } } fn content_length(&self) -> Option { use std::str::FromStr; self.find_header_values("content-length") .first() .and_then(|len| usize::from_str(*len).ok()) } fn has_chunked_body(&self) -> bool { self.headers .iter() .filter(|(name, _)| name == "transfer-encoding") .any(|(_, value)| value.contains("chunked")) } fn read_request_body(&mut self, mut raw_body_rd: impl Read) -> io::Result<()> { if let Some(content_length) = self.content_length() { let mut rd = raw_body_rd.take(content_length as u64); return io::copy(&mut rd, &mut self.body).map(|_| ()); } if self.has_chunked_body() { let mut chunk_size_buf = String::new(); let mut reader = BufReader::new(raw_body_rd); loop { reader.read_line(&mut chunk_size_buf)?; let chunk_size = { let chunk_size_str = chunk_size_buf.trim_matches(|c| c == '\r' || c == '\n'); u64::from_str_radix(chunk_size_str, 16) .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))? }; if chunk_size == 0 { break; } io::copy(&mut (&mut reader).take(chunk_size), &mut self.body)?; chunk_size_buf.clear(); reader.read_line(&mut chunk_size_buf)?; } return Ok(()); } if self.version == (1, 0) { return io::copy(&mut raw_body_rd, &mut self.body).map(|_| ()); } Ok(()) } } impl Default for Request { fn default() -> Self { Self { version: (1, 1), method: String::new(), path: String::new(), headers: Vec::new(), body: Vec::new(), error: None, is_parsed: false, last_header_field: None, last_header_value: None, } } } impl<'a> From<&'a TcpStream> for Request { fn from(mut stream: &TcpStream) -> Self { let mut request = Self::default(); let mut all_buf = Vec::new(); loop { if request.is_parsed { break; } let mut headers = [httparse::EMPTY_HEADER; 16]; let mut req = httparse::Request::new(&mut headers); let mut buf = [0; 1024]; let rlen = match stream.read(&mut buf) { Err(e) => Err(e.to_string()), Ok(0) => Err("Nothing to read.".into()), Ok(i) => Ok(i), } .map_err(|e| request.error = Some(e)) .unwrap_or(0); if rlen == 0 { break; } all_buf.extend_from_slice(&buf[..rlen]); let _ = req .parse(&all_buf) .map_err(|e| { request.error = Some(e.to_string()); request.is_parsed = true; }) .map(|status| match status { httparse::Status::Complete(head_length) => { if let Some(a @ 0..=1) = req.version { request.version = (1, a); } if let Some(a) = req.method { request.method += a; } if let Some(a) = req.path { request.path += a } for h in req.headers { request.last_header_field = Some(h.name.to_lowercase()); request.last_header_value = Some(String::from_utf8_lossy(h.value).to_string()); request.record_last_header(); } let raw_body_rd = Cursor::new(&all_buf[head_length..]).chain(stream); if let Err(err) = request.read_request_body(raw_body_rd) { request.error = Some(err.to_string()); } request.is_parsed = true; } httparse::Status::Partial => (), }); } request } } impl fmt::Display for Request { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "\r\n{} {}\r\n", &self.method, &self.path)?; for &(ref key, ref value) in &self.headers { writeln!(f, "{}: {}\r", key, value)?; } if self.body.is_empty() { write!(f, "") } else { writeln!(f, "{}\r", &String::from_utf8_lossy(&self.body)) } } }