MCPcopy Create free account
hub / github.com/NVIDIA/OpenShell / parse_http_request

Function parse_http_request

crates/openshell-supervisor-network/src/l7/rest.rs:138–246  ·  view source on GitHub ↗

Parse one HTTP/1.1 request from the stream. Reads one byte at a time to stop exactly at the `\r\n\r\n` header terminator. A multi-byte read could consume bytes belonging to a subsequent pipelined request, and those overflow bytes would be forwarded upstream without L7 policy evaluation -- a request smuggling vulnerability. Byte-at-a-time overhead is negligible for the typical 200-800 byte heade

(
    client: &mut C,
    canonicalize_options: &crate::l7::path::CanonicalizeOptions,
)

Source from the content-addressed store, hash-verified

136/// smuggling vulnerability. Byte-at-a-time overhead is negligible for
137/// the typical 200-800 byte headers on L7-inspected REST endpoints.
138async fn parse_http_request<C: AsyncRead + Unpin>(
139 client: &mut C,
140 canonicalize_options: &crate::l7::path::CanonicalizeOptions,
141) -> Result<Option<L7Request>> {
142 let mut buf = Vec::with_capacity(4096);
143
144 loop {
145 if buf.len() > MAX_HEADER_BYTES {
146 return Err(miette!(
147 "HTTP request headers exceed {MAX_HEADER_BYTES} bytes"
148 ));
149 }
150
151 let byte = match client.read_u8().await {
152 Ok(b) => b,
153 Err(e) if buf.is_empty() && is_benign_close(&e) => return Ok(None),
154 Err(e) if buf.is_empty() && e.kind() == std::io::ErrorKind::UnexpectedEof => {
155 return Ok(None); // Clean close before any data
156 }
157 Err(e) => return Err(miette::miette!("{e}")),
158 };
159 buf.push(byte);
160
161 // Check for end of headers -- `ends_with` is sufficient because
162 // we append exactly one byte per iteration.
163 if buf.ends_with(b"\r\n\r\n") {
164 break;
165 }
166 }
167
168 // Parse request line
169 let header_end = buf.windows(4).position(|w| w == b"\r\n\r\n").unwrap() + 4;
170
171 // Reject bare LF in headers (must use \r\n line endings per RFC 7230).
172 // Bare LF can cause parsing discrepancies between this proxy and upstream
173 // servers, enabling request smuggling via header injection.
174 for i in 0..header_end {
175 if buf[i] == b'\n' && (i == 0 || buf[i - 1] != b'\r') {
176 return Err(miette!(
177 "HTTP headers contain bare LF (line feed without carriage return)"
178 ));
179 }
180 }
181
182 // Strict UTF-8 validation. from_utf8_lossy would silently replace invalid
183 // bytes with U+FFFD, creating an interpretation gap between this proxy
184 // (which parses the lossy string) and upstream servers (which receive the
185 // raw bytes). This gap enables request smuggling via mutated header names.
186 let header_str = std::str::from_utf8(&buf[..header_end])
187 .map_err(|_| miette!("HTTP headers contain invalid UTF-8"))?;
188
189 let request_line = header_str
190 .lines()
191 .next()
192 .ok_or_else(|| miette!("Empty HTTP request"))?;
193
194 let mut parts = request_line.split_whitespace();
195 let method = parts

Calls 9

is_benign_closeFunction · 0.85
parse_body_lengthFunction · 0.85
parse_query_paramsFunction · 0.85
lenMethod · 0.80
pushMethod · 0.80
is_emptyMethod · 0.45
nextMethod · 0.45

Tested by

no test coverage detected