ic_testkit/testing/
mod.rs1use std::{
4 io::{self, Read as _},
5 net::TcpStream,
6 time::{Duration, Instant},
7};
8
9pub fn read_http_request(
17 stream: &mut TcpStream,
18 timeout: Duration,
19 max_header_bytes: usize,
20 max_body_bytes: usize,
21) -> io::Result<Vec<u8>> {
22 let invalid = || io::Error::from(io::ErrorKind::InvalidData);
23 let capacity = max_header_bytes
24 .checked_add(max_body_bytes)
25 .filter(|_| max_header_bytes >= 4)
26 .ok_or_else(invalid)?;
27 let deadline = Instant::now().checked_add(timeout).ok_or_else(invalid)?;
28 stream.set_nonblocking(false)?;
29 let mut bytes = Vec::new();
30 let mut chunk = [0; 4096];
31 loop {
32 let remaining = deadline
33 .checked_duration_since(Instant::now())
34 .filter(|remaining| !remaining.is_zero())
35 .ok_or(io::ErrorKind::TimedOut)?;
36 stream.set_read_timeout(Some(remaining))?;
37 let window = (capacity - bytes.len()).min(chunk.len());
38 if window == 0 {
39 return Err(invalid());
40 }
41 match stream.read(&mut chunk[..window]) {
42 Ok(0) => return Err(io::ErrorKind::UnexpectedEof.into()),
43 Ok(count) => bytes.extend_from_slice(&chunk[..count]),
44 Err(error) if error.kind() == io::ErrorKind::Interrupted => continue,
45 Err(error) => return Err(error),
46 }
47 if let Some(length) = request_length(&bytes, max_header_bytes, max_body_bytes)? {
48 if bytes.len() == length {
49 return Ok(bytes);
50 }
51 if bytes.len() > length {
52 return Err(invalid());
53 }
54 }
55 }
56}
57
58fn request_length(
59 bytes: &[u8],
60 max_header_bytes: usize,
61 max_body_bytes: usize,
62) -> io::Result<Option<usize>> {
63 let invalid = || io::Error::from(io::ErrorKind::InvalidData);
64 let Some(end) = bytes.windows(4).position(|part| part == b"\r\n\r\n") else {
65 return if bytes.len() > max_header_bytes {
66 Err(invalid())
67 } else {
68 Ok(None)
69 };
70 };
71 if end + 4 > max_header_bytes {
72 return Err(invalid());
73 }
74 let header = std::str::from_utf8(&bytes[..end]).map_err(|_| invalid())?;
75 let mut length = None;
76 for line in header.lines().skip(1) {
77 let (name, value) = line.split_once(':').ok_or_else(invalid)?;
78 if name.eq_ignore_ascii_case("transfer-encoding") {
79 return Err(invalid());
80 }
81 if name.eq_ignore_ascii_case("content-length") {
82 if length.is_some()
83 || value.trim().is_empty()
84 || !value.trim().bytes().all(|byte| byte.is_ascii_digit())
85 {
86 return Err(invalid());
87 }
88 length = Some(value.trim().parse::<usize>().map_err(|_| invalid())?);
89 }
90 }
91 let length = length.unwrap_or(0);
92 if length > max_body_bytes {
93 return Err(invalid());
94 }
95 end.checked_add(4)
96 .and_then(|end| end.checked_add(length))
97 .map(Some)
98 .ok_or_else(invalid)
99}
100
101#[cfg(test)]
102mod tests;