1use std::io;
34use std::pin::Pin;
35use std::task::{Context, Poll};
36use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
37
38fn needs_encoding(b: u8) -> bool {
49 matches!(b, b'"' | b'<' | b'>' | b'#') || b >= 0x80
50}
51
52const MAX_LINE: usize = 64 * 1024;
57
58const H2_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
73
74#[derive(Debug)]
75enum State {
76 Preface,
78 RequestLine,
80 Headers { content_length: u64, upgrade: bool },
86 Body(u64),
88 PassThrough,
90}
91
92#[derive(Debug)]
94pub struct LenientStream<T> {
95 inner: T,
96 state: State,
97 out: Vec<u8>,
99 out_pos: usize,
100 line: Vec<u8>,
102}
103
104impl<T> LenientStream<T> {
105 pub fn new(inner: T) -> Self {
107 Self {
108 inner,
109 state: State::Preface,
110 out: Vec::new(),
111 out_pos: 0,
112 line: Vec::new(),
113 }
114 }
115
116 fn give_up(&mut self) {
118 let line = std::mem::take(&mut self.line);
119 self.out.extend_from_slice(&line);
120 self.state = State::PassThrough;
121 }
122
123 fn process(&mut self, mut data: &[u8]) {
125 while !data.is_empty() {
126 match self.state {
127 State::PassThrough => {
128 self.out.extend_from_slice(data);
129 return;
130 }
131 State::Preface => {
132 let want = H2_PREFACE.len() - self.line.len();
136 let take = std::cmp::min(want, data.len());
137 self.line.extend_from_slice(&data[..take]);
138
139 if !H2_PREFACE.starts_with(&self.line) {
140 self.state = State::RequestLine;
143 let mut buffered = std::mem::take(&mut self.line);
144 buffered.extend_from_slice(&data[take..]);
145 self.process(&buffered);
146 return;
147 }
148
149 data = &data[take..];
150 if self.line.len() == H2_PREFACE.len() {
151 self.give_up();
152 }
153 }
154 State::Body(remaining) => {
155 let take = std::cmp::min(remaining, data.len() as u64) as usize;
156 self.out.extend_from_slice(&data[..take]);
157 data = &data[take..];
158 let left = remaining - take as u64;
159 self.state = if left == 0 {
160 State::RequestLine
161 } else {
162 State::Body(left)
163 };
164 }
165 State::RequestLine | State::Headers { .. } => {
166 match data.iter().position(|&b| b == b'\n') {
167 Some(idx) => {
168 self.line.extend_from_slice(&data[..=idx]);
169 data = &data[idx + 1..];
170 self.finish_line();
171 }
172 None => {
173 self.line.extend_from_slice(data);
174 data = &[];
175 if self.line.len() > MAX_LINE {
176 self.give_up();
177 }
178 }
179 }
180 }
181 }
182 }
183 }
184
185 fn finish_line(&mut self) {
187 let line = std::mem::take(&mut self.line);
188
189 match self.state {
190 State::RequestLine => {
191 self.out.extend_from_slice(&normalize_request_line(&line));
192 self.state = State::Headers {
193 content_length: 0,
194 upgrade: false,
195 };
196 }
197 State::Headers {
198 content_length,
199 upgrade,
200 } => {
201 let is_blank = matches!(line.as_slice(), b"\r\n" | b"\n");
202 if is_blank {
203 self.out.extend_from_slice(&line);
204 self.state = if upgrade {
213 State::PassThrough
214 } else if content_length == 0 {
215 State::RequestLine
216 } else {
217 State::Body(content_length)
218 };
219 return;
220 }
221
222 if header_is(&line, b"transfer-encoding") {
225 self.out.extend_from_slice(&line);
226 self.state = State::PassThrough;
227 return;
228 }
229
230 let content_length = match header_value(&line, b"content-length") {
231 Some(value) => match std::str::from_utf8(value)
232 .ok()
233 .and_then(|v| v.trim().parse::<u64>().ok())
234 {
235 Some(len) => len,
236 None => {
238 self.out.extend_from_slice(&line);
239 self.state = State::PassThrough;
240 return;
241 }
242 },
243 None => content_length,
244 };
245
246 self.out.extend_from_slice(&line);
247 self.state = State::Headers {
248 content_length,
249 upgrade: upgrade || header_is(&line, b"upgrade"),
250 };
251 }
252 State::Preface | State::Body(_) | State::PassThrough => {
253 unreachable!("not a line state")
254 }
255 }
256 }
257}
258
259fn header_is(line: &[u8], name: &[u8]) -> bool {
261 match line.iter().position(|&b| b == b':') {
262 Some(colon) => line[..colon].eq_ignore_ascii_case(name),
263 None => false,
264 }
265}
266
267fn header_value<'a>(line: &'a [u8], name: &[u8]) -> Option<&'a [u8]> {
269 let colon = line.iter().position(|&b| b == b':')?;
270 line[..colon]
271 .eq_ignore_ascii_case(name)
272 .then(|| &line[colon + 1..])
273}
274
275fn normalize_request_line(line: &[u8]) -> Vec<u8> {
280 let end = line.len() - trailing_newline_len(line);
281 let head = &line[..end];
282
283 let (Some(first), Some(last)) = (
284 head.iter().position(|&b| b == b' '),
285 head.iter().rposition(|&b| b == b' '),
286 ) else {
287 return line.to_vec();
288 };
289 if first == last {
290 return line.to_vec();
291 }
292
293 let target = &head[first + 1..last];
294 if !target.iter().copied().any(needs_encoding) {
295 return line.to_vec();
296 }
297
298 let mut out = Vec::with_capacity(line.len() + 16);
299 out.extend_from_slice(&head[..=first]);
300 for &b in target {
301 if needs_encoding(b) {
302 out.extend_from_slice(format!("%{:02X}", b).as_bytes());
303 } else {
304 out.push(b);
305 }
306 }
307 out.extend_from_slice(&head[last..]);
308 out.extend_from_slice(&line[end..]);
309 out
310}
311
312fn trailing_newline_len(line: &[u8]) -> usize {
313 if line.ends_with(b"\r\n") {
314 2
315 } else if line.ends_with(b"\n") {
316 1
317 } else {
318 0
319 }
320}
321
322impl<T: AsyncRead + Unpin> AsyncRead for LenientStream<T> {
323 fn poll_read(
324 self: Pin<&mut Self>,
325 cx: &mut Context<'_>,
326 dst: &mut ReadBuf<'_>,
327 ) -> Poll<io::Result<()>> {
328 let me = self.get_mut();
329
330 loop {
331 if me.out_pos < me.out.len() {
332 let take = std::cmp::min(dst.remaining(), me.out.len() - me.out_pos);
333 dst.put_slice(&me.out[me.out_pos..me.out_pos + take]);
334 me.out_pos += take;
335 if me.out_pos == me.out.len() {
336 me.out.clear();
337 me.out_pos = 0;
338 }
339 return Poll::Ready(Ok(()));
340 }
341
342 if matches!(me.state, State::PassThrough) && me.line.is_empty() {
345 return Pin::new(&mut me.inner).poll_read(cx, dst);
346 }
347
348 let mut scratch = [0u8; 8192];
349 let mut read = ReadBuf::new(&mut scratch);
350 match Pin::new(&mut me.inner).poll_read(cx, &mut read) {
351 Poll::Ready(Ok(())) => {
352 let filled = read.filled();
353 if filled.is_empty() {
354 if !me.line.is_empty() {
357 me.give_up();
358 continue;
359 }
360 return Poll::Ready(Ok(()));
361 }
362 let owned = filled.to_vec();
363 me.process(&owned);
364 }
365 Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
366 Poll::Pending => return Poll::Pending,
367 }
368 }
369 }
370}
371
372impl<T: AsyncWrite + Unpin> AsyncWrite for LenientStream<T> {
373 fn poll_write(
374 mut self: Pin<&mut Self>,
375 cx: &mut Context<'_>,
376 buf: &[u8],
377 ) -> Poll<io::Result<usize>> {
378 Pin::new(&mut self.inner).poll_write(cx, buf)
379 }
380
381 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
382 Pin::new(&mut self.inner).poll_flush(cx)
383 }
384
385 fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
386 Pin::new(&mut self.inner).poll_shutdown(cx)
387 }
388}
389
390pub struct LenientListener(pub tokio::net::TcpListener);
393
394impl axum::serve::Listener for LenientListener {
395 type Io = LenientStream<tokio::net::TcpStream>;
396 type Addr = std::net::SocketAddr;
397
398 async fn accept(&mut self) -> (Self::Io, Self::Addr) {
399 loop {
400 match self.0.accept().await {
401 Ok((stream, addr)) => return (LenientStream::new(stream), addr),
402 Err(e) => {
405 tracing::debug!("accept failed: {e}");
406 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
407 }
408 }
409 }
410 }
411
412 fn local_addr(&self) -> io::Result<Self::Addr> {
413 self.0.local_addr()
414 }
415}
416
417#[cfg(unix)]
424pub struct LenientUnixListener(pub tokio::net::UnixListener);
425
426#[cfg(unix)]
427impl axum::serve::Listener for LenientUnixListener {
428 type Io = LenientStream<tokio::net::UnixStream>;
429 type Addr = tokio::net::unix::SocketAddr;
430
431 async fn accept(&mut self) -> (Self::Io, Self::Addr) {
432 loop {
433 match self.0.accept().await {
434 Ok((stream, addr)) => return (LenientStream::new(stream), addr),
435 Err(e) => {
436 tracing::debug!("accept failed: {e}");
437 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
438 }
439 }
440 }
441 }
442
443 fn local_addr(&self) -> io::Result<Self::Addr> {
444 self.0.local_addr()
445 }
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451
452 fn run(chunks: &[&[u8]]) -> Vec<u8> {
453 let mut s = LenientStream::new(tokio::io::empty());
454 for chunk in chunks {
455 s.process(chunk);
456 }
457 s.out
458 }
459
460 #[test]
461 fn encodes_only_what_the_uri_parser_rejects() {
462 let out = run(&[b"GET /t?select=data->>id HTTP/1.1\r\n\r\n"]);
463 assert_eq!(
464 String::from_utf8(out).unwrap(),
465 "GET /t?select=data-%3E%3Eid HTTP/1.1\r\n\r\n"
466 );
467 }
468
469 #[test]
470 fn encodes_quotes() {
471 let out = run(&[b"GET /t?a=in.(\"x\") HTTP/1.1\r\n\r\n"]);
472 assert!(String::from_utf8(out).unwrap().contains("in.(%22x%22)"));
473 }
474
475 #[test]
476 fn leaves_an_ordinary_request_untouched() {
477 let raw: &[u8] = b"GET /t?a=eq.1 HTTP/1.1\r\nHost: x\r\n\r\n";
478 assert_eq!(run(&[raw]), raw.to_vec());
479 }
480
481 #[test]
482 fn percent_escapes_already_present_are_not_re_encoded() {
483 let raw: &[u8] = b"GET /t?select=data-%3E%3Eid HTTP/1.1\r\n\r\n";
484 assert_eq!(run(&[raw]), raw.to_vec());
485 }
486
487 #[test]
488 fn a_body_is_never_rewritten() {
489 let body = b"GET /evil?x=a>b HTTP/1.1";
491 let mut raw = Vec::new();
492 raw.extend_from_slice(b"POST /t HTTP/1.1\r\nContent-Length: 24\r\n\r\n");
493 raw.extend_from_slice(body);
494 let out = run(&[&raw]);
495 assert_eq!(out, raw, "body bytes were altered");
496 }
497
498 #[test]
499 fn the_request_after_a_body_is_still_rewritten() {
500 let mut raw = Vec::new();
501 raw.extend_from_slice(b"POST /t HTTP/1.1\r\nContent-Length: 2\r\n\r\nhi");
502 raw.extend_from_slice(b"GET /t?s=a->b HTTP/1.1\r\n\r\n");
503 let out = String::from_utf8(run(&[&raw])).unwrap();
504 assert!(out.contains("\r\n\r\nhi"), "body changed: {out}");
505 assert!(
506 out.contains("s=a-%3Eb"),
507 "second request not rewritten: {out}"
508 );
509 }
510
511 #[test]
512 fn a_chunked_body_stops_rewriting_rather_than_guessing() {
513 let mut raw = Vec::new();
514 raw.extend_from_slice(b"POST /t HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n");
515 raw.extend_from_slice(b"2\r\nhi\r\n0\r\n\r\nGET /t?s=a->b HTTP/1.1\r\n\r\n");
516 assert_eq!(run(&[&raw]), raw);
518 }
519
520 #[test]
521 fn a_request_split_across_reads_is_still_rewritten() {
522 let out = run(&[b"GET /t?s=da", b"ta->>id HT", b"TP/1.1\r\n\r\n"]);
523 assert_eq!(
524 String::from_utf8(out).unwrap(),
525 "GET /t?s=data-%3E%3Eid HTTP/1.1\r\n\r\n"
526 );
527 }
528
529 #[test]
530 fn keeps_pipelined_requests_aligned() {
531 let out = String::from_utf8(run(&[
532 b"GET /a?s=x->y HTTP/1.1\r\n\r\nGET /b?s=p->q HTTP/1.1\r\n\r\n",
533 ]))
534 .unwrap();
535 assert!(out.contains("/a?s=x-%3Ey"), "{out}");
536 assert!(out.contains("/b?s=p-%3Eq"), "{out}");
537 }
538
539 #[test]
540 fn a_malformed_request_line_is_left_for_hyper_to_reject() {
541 let raw: &[u8] = b"nonsense\r\n";
542 assert_eq!(run(&[raw]), raw.to_vec());
543 }
544
545 #[test]
548 fn an_http2_connection_is_passed_through_untouched() {
549 let mut raw = Vec::new();
550 raw.extend_from_slice(H2_PREFACE);
551 raw.extend_from_slice(b"\x00\x00\x00\x04\x00\x00\x00\x00\x00");
554 raw.extend_from_slice(b"\x82\x86\x84\x41\x8a > \n\xff\xfe HTTP/1.1\r\n");
555 assert_eq!(run(&[&raw]), raw);
556 }
557
558 #[test]
561 fn an_http2_preface_split_across_reads_is_still_recognised() {
562 let out = run(&[b"PRI * HTTP/2.0\r\n", b"\r\nSM\r\n\r\n", b"\xff\xfe->\n"]);
563 assert_eq!(
564 out,
565 [H2_PREFACE, b"\xff\xfe->\n"].concat(),
566 "an HTTP/2 connection was rewritten"
567 );
568 }
569
570 #[test]
575 fn a_websocket_upgrade_is_passed_through_after_its_headers() {
576 let mut raw = Vec::new();
577 raw.extend_from_slice(
578 b"GET /v1/graphql/ws HTTP/1.1\r\nHost: x\r\nConnection: Upgrade\r\n\
579 Upgrade: websocket\r\nSec-WebSocket-Version: 13\r\n\r\n",
580 );
581 raw.extend_from_slice(b"\x81\xfe\x00\x8a\x12\x34 a>b \xff\xfe HTTP/1.1");
582 assert_eq!(run(&[&raw]), raw, "frames after the upgrade were altered");
583 let (head, frame) = raw.split_at(raw.len() - 21);
585 assert_eq!(run(&[head, frame]), raw);
586 }
587
588 #[test]
590 fn an_upgrade_requests_target_is_still_rewritten() {
591 let out = run(&[b"GET /ws?s=a->b HTTP/1.1\r\nUpgrade: websocket\r\n\r\n\x81\x80>"]);
592 assert_eq!(
593 out,
594 b"GET /ws?s=a-%3Eb HTTP/1.1\r\nUpgrade: websocket\r\n\r\n\x81\x80>".to_vec()
595 );
596 }
597
598 #[test]
600 fn a_request_beginning_like_the_preface_is_read_as_http1() {
601 let out = run(&[b"PRI /t?s=a->b HTTP/1.1\r\n\r\n"]);
602 assert_eq!(
603 String::from_utf8(out).unwrap(),
604 "PRI /t?s=a-%3Eb HTTP/1.1\r\n\r\n"
605 );
606 }
607
608 #[test]
610 fn a_request_beginning_like_the_preface_survives_being_split() {
611 let out = run(&[b"P", b"OST /t?s=a->b HTTP/1.1\r\n", b"\r\n"]);
612 assert_eq!(
613 String::from_utf8(out).unwrap(),
614 "POST /t?s=a-%3Eb HTTP/1.1\r\n\r\n"
615 );
616 }
617}