postrust_server/
lenient_uri.rs1use std::io;
33use std::pin::Pin;
34use std::task::{Context, Poll};
35use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
36
37fn needs_encoding(b: u8) -> bool {
48 matches!(b, b'"' | b'<' | b'>' | b'#') || b >= 0x80
49}
50
51const MAX_LINE: usize = 64 * 1024;
56
57const H2_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
72
73#[derive(Debug)]
74enum State {
75 Preface,
77 RequestLine,
79 Headers { content_length: u64 },
81 Body(u64),
83 PassThrough,
85}
86
87#[derive(Debug)]
89pub struct LenientStream<T> {
90 inner: T,
91 state: State,
92 out: Vec<u8>,
94 out_pos: usize,
95 line: Vec<u8>,
97}
98
99impl<T> LenientStream<T> {
100 pub fn new(inner: T) -> Self {
102 Self {
103 inner,
104 state: State::Preface,
105 out: Vec::new(),
106 out_pos: 0,
107 line: Vec::new(),
108 }
109 }
110
111 fn give_up(&mut self) {
113 let line = std::mem::take(&mut self.line);
114 self.out.extend_from_slice(&line);
115 self.state = State::PassThrough;
116 }
117
118 fn process(&mut self, mut data: &[u8]) {
120 while !data.is_empty() {
121 match self.state {
122 State::PassThrough => {
123 self.out.extend_from_slice(data);
124 return;
125 }
126 State::Preface => {
127 let want = H2_PREFACE.len() - self.line.len();
131 let take = std::cmp::min(want, data.len());
132 self.line.extend_from_slice(&data[..take]);
133
134 if !H2_PREFACE.starts_with(&self.line) {
135 self.state = State::RequestLine;
138 let mut buffered = std::mem::take(&mut self.line);
139 buffered.extend_from_slice(&data[take..]);
140 self.process(&buffered);
141 return;
142 }
143
144 data = &data[take..];
145 if self.line.len() == H2_PREFACE.len() {
146 self.give_up();
147 }
148 }
149 State::Body(remaining) => {
150 let take = std::cmp::min(remaining, data.len() as u64) as usize;
151 self.out.extend_from_slice(&data[..take]);
152 data = &data[take..];
153 let left = remaining - take as u64;
154 self.state = if left == 0 {
155 State::RequestLine
156 } else {
157 State::Body(left)
158 };
159 }
160 State::RequestLine | State::Headers { .. } => {
161 match data.iter().position(|&b| b == b'\n') {
162 Some(idx) => {
163 self.line.extend_from_slice(&data[..=idx]);
164 data = &data[idx + 1..];
165 self.finish_line();
166 }
167 None => {
168 self.line.extend_from_slice(data);
169 data = &[];
170 if self.line.len() > MAX_LINE {
171 self.give_up();
172 }
173 }
174 }
175 }
176 }
177 }
178 }
179
180 fn finish_line(&mut self) {
182 let line = std::mem::take(&mut self.line);
183
184 match self.state {
185 State::RequestLine => {
186 self.out.extend_from_slice(&normalize_request_line(&line));
187 self.state = State::Headers { content_length: 0 };
188 }
189 State::Headers { content_length } => {
190 let is_blank = matches!(line.as_slice(), b"\r\n" | b"\n");
191 if is_blank {
192 self.out.extend_from_slice(&line);
193 self.state = if content_length == 0 {
194 State::RequestLine
195 } else {
196 State::Body(content_length)
197 };
198 return;
199 }
200
201 if header_is(&line, b"transfer-encoding") {
204 self.out.extend_from_slice(&line);
205 self.state = State::PassThrough;
206 return;
207 }
208
209 let content_length = match header_value(&line, b"content-length") {
210 Some(value) => match std::str::from_utf8(value)
211 .ok()
212 .and_then(|v| v.trim().parse::<u64>().ok())
213 {
214 Some(len) => len,
215 None => {
217 self.out.extend_from_slice(&line);
218 self.state = State::PassThrough;
219 return;
220 }
221 },
222 None => content_length,
223 };
224
225 self.out.extend_from_slice(&line);
226 self.state = State::Headers { content_length };
227 }
228 State::Preface | State::Body(_) | State::PassThrough => {
229 unreachable!("not a line state")
230 }
231 }
232 }
233}
234
235fn header_is(line: &[u8], name: &[u8]) -> bool {
237 match line.iter().position(|&b| b == b':') {
238 Some(colon) => line[..colon].eq_ignore_ascii_case(name),
239 None => false,
240 }
241}
242
243fn header_value<'a>(line: &'a [u8], name: &[u8]) -> Option<&'a [u8]> {
245 let colon = line.iter().position(|&b| b == b':')?;
246 line[..colon]
247 .eq_ignore_ascii_case(name)
248 .then(|| &line[colon + 1..])
249}
250
251fn normalize_request_line(line: &[u8]) -> Vec<u8> {
256 let end = line.len() - trailing_newline_len(line);
257 let head = &line[..end];
258
259 let (Some(first), Some(last)) = (
260 head.iter().position(|&b| b == b' '),
261 head.iter().rposition(|&b| b == b' '),
262 ) else {
263 return line.to_vec();
264 };
265 if first == last {
266 return line.to_vec();
267 }
268
269 let target = &head[first + 1..last];
270 if !target.iter().copied().any(needs_encoding) {
271 return line.to_vec();
272 }
273
274 let mut out = Vec::with_capacity(line.len() + 16);
275 out.extend_from_slice(&head[..=first]);
276 for &b in target {
277 if needs_encoding(b) {
278 out.extend_from_slice(format!("%{:02X}", b).as_bytes());
279 } else {
280 out.push(b);
281 }
282 }
283 out.extend_from_slice(&head[last..]);
284 out.extend_from_slice(&line[end..]);
285 out
286}
287
288fn trailing_newline_len(line: &[u8]) -> usize {
289 if line.ends_with(b"\r\n") {
290 2
291 } else if line.ends_with(b"\n") {
292 1
293 } else {
294 0
295 }
296}
297
298impl<T: AsyncRead + Unpin> AsyncRead for LenientStream<T> {
299 fn poll_read(
300 self: Pin<&mut Self>,
301 cx: &mut Context<'_>,
302 dst: &mut ReadBuf<'_>,
303 ) -> Poll<io::Result<()>> {
304 let me = self.get_mut();
305
306 loop {
307 if me.out_pos < me.out.len() {
308 let take = std::cmp::min(dst.remaining(), me.out.len() - me.out_pos);
309 dst.put_slice(&me.out[me.out_pos..me.out_pos + take]);
310 me.out_pos += take;
311 if me.out_pos == me.out.len() {
312 me.out.clear();
313 me.out_pos = 0;
314 }
315 return Poll::Ready(Ok(()));
316 }
317
318 if matches!(me.state, State::PassThrough) && me.line.is_empty() {
321 return Pin::new(&mut me.inner).poll_read(cx, dst);
322 }
323
324 let mut scratch = [0u8; 8192];
325 let mut read = ReadBuf::new(&mut scratch);
326 match Pin::new(&mut me.inner).poll_read(cx, &mut read) {
327 Poll::Ready(Ok(())) => {
328 let filled = read.filled();
329 if filled.is_empty() {
330 if !me.line.is_empty() {
333 me.give_up();
334 continue;
335 }
336 return Poll::Ready(Ok(()));
337 }
338 let owned = filled.to_vec();
339 me.process(&owned);
340 }
341 Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
342 Poll::Pending => return Poll::Pending,
343 }
344 }
345 }
346}
347
348impl<T: AsyncWrite + Unpin> AsyncWrite for LenientStream<T> {
349 fn poll_write(
350 mut self: Pin<&mut Self>,
351 cx: &mut Context<'_>,
352 buf: &[u8],
353 ) -> Poll<io::Result<usize>> {
354 Pin::new(&mut self.inner).poll_write(cx, buf)
355 }
356
357 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
358 Pin::new(&mut self.inner).poll_flush(cx)
359 }
360
361 fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
362 Pin::new(&mut self.inner).poll_shutdown(cx)
363 }
364}
365
366pub struct LenientListener(pub tokio::net::TcpListener);
369
370impl axum::serve::Listener for LenientListener {
371 type Io = LenientStream<tokio::net::TcpStream>;
372 type Addr = std::net::SocketAddr;
373
374 async fn accept(&mut self) -> (Self::Io, Self::Addr) {
375 loop {
376 match self.0.accept().await {
377 Ok((stream, addr)) => return (LenientStream::new(stream), addr),
378 Err(e) => {
381 tracing::debug!("accept failed: {e}");
382 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
383 }
384 }
385 }
386 }
387
388 fn local_addr(&self) -> io::Result<Self::Addr> {
389 self.0.local_addr()
390 }
391}
392
393#[cfg(unix)]
400pub struct LenientUnixListener(pub tokio::net::UnixListener);
401
402#[cfg(unix)]
403impl axum::serve::Listener for LenientUnixListener {
404 type Io = LenientStream<tokio::net::UnixStream>;
405 type Addr = tokio::net::unix::SocketAddr;
406
407 async fn accept(&mut self) -> (Self::Io, Self::Addr) {
408 loop {
409 match self.0.accept().await {
410 Ok((stream, addr)) => return (LenientStream::new(stream), addr),
411 Err(e) => {
412 tracing::debug!("accept failed: {e}");
413 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
414 }
415 }
416 }
417 }
418
419 fn local_addr(&self) -> io::Result<Self::Addr> {
420 self.0.local_addr()
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427
428 fn run(chunks: &[&[u8]]) -> Vec<u8> {
429 let mut s = LenientStream::new(tokio::io::empty());
430 for chunk in chunks {
431 s.process(chunk);
432 }
433 s.out
434 }
435
436 #[test]
437 fn encodes_only_what_the_uri_parser_rejects() {
438 let out = run(&[b"GET /t?select=data->>id HTTP/1.1\r\n\r\n"]);
439 assert_eq!(
440 String::from_utf8(out).unwrap(),
441 "GET /t?select=data-%3E%3Eid HTTP/1.1\r\n\r\n"
442 );
443 }
444
445 #[test]
446 fn encodes_quotes() {
447 let out = run(&[b"GET /t?a=in.(\"x\") HTTP/1.1\r\n\r\n"]);
448 assert!(String::from_utf8(out).unwrap().contains("in.(%22x%22)"));
449 }
450
451 #[test]
452 fn leaves_an_ordinary_request_untouched() {
453 let raw: &[u8] = b"GET /t?a=eq.1 HTTP/1.1\r\nHost: x\r\n\r\n";
454 assert_eq!(run(&[raw]), raw.to_vec());
455 }
456
457 #[test]
458 fn percent_escapes_already_present_are_not_re_encoded() {
459 let raw: &[u8] = b"GET /t?select=data-%3E%3Eid HTTP/1.1\r\n\r\n";
460 assert_eq!(run(&[raw]), raw.to_vec());
461 }
462
463 #[test]
464 fn a_body_is_never_rewritten() {
465 let body = b"GET /evil?x=a>b HTTP/1.1";
467 let mut raw = Vec::new();
468 raw.extend_from_slice(b"POST /t HTTP/1.1\r\nContent-Length: 24\r\n\r\n");
469 raw.extend_from_slice(body);
470 let out = run(&[&raw]);
471 assert_eq!(out, raw, "body bytes were altered");
472 }
473
474 #[test]
475 fn the_request_after_a_body_is_still_rewritten() {
476 let mut raw = Vec::new();
477 raw.extend_from_slice(b"POST /t HTTP/1.1\r\nContent-Length: 2\r\n\r\nhi");
478 raw.extend_from_slice(b"GET /t?s=a->b HTTP/1.1\r\n\r\n");
479 let out = String::from_utf8(run(&[&raw])).unwrap();
480 assert!(out.contains("\r\n\r\nhi"), "body changed: {out}");
481 assert!(
482 out.contains("s=a-%3Eb"),
483 "second request not rewritten: {out}"
484 );
485 }
486
487 #[test]
488 fn a_chunked_body_stops_rewriting_rather_than_guessing() {
489 let mut raw = Vec::new();
490 raw.extend_from_slice(b"POST /t HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n");
491 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");
492 assert_eq!(run(&[&raw]), raw);
494 }
495
496 #[test]
497 fn a_request_split_across_reads_is_still_rewritten() {
498 let out = run(&[b"GET /t?s=da", b"ta->>id HT", b"TP/1.1\r\n\r\n"]);
499 assert_eq!(
500 String::from_utf8(out).unwrap(),
501 "GET /t?s=data-%3E%3Eid HTTP/1.1\r\n\r\n"
502 );
503 }
504
505 #[test]
506 fn keeps_pipelined_requests_aligned() {
507 let out = String::from_utf8(run(&[
508 b"GET /a?s=x->y HTTP/1.1\r\n\r\nGET /b?s=p->q HTTP/1.1\r\n\r\n",
509 ]))
510 .unwrap();
511 assert!(out.contains("/a?s=x-%3Ey"), "{out}");
512 assert!(out.contains("/b?s=p-%3Eq"), "{out}");
513 }
514
515 #[test]
516 fn a_malformed_request_line_is_left_for_hyper_to_reject() {
517 let raw: &[u8] = b"nonsense\r\n";
518 assert_eq!(run(&[raw]), raw.to_vec());
519 }
520
521 #[test]
524 fn an_http2_connection_is_passed_through_untouched() {
525 let mut raw = Vec::new();
526 raw.extend_from_slice(H2_PREFACE);
527 raw.extend_from_slice(b"\x00\x00\x00\x04\x00\x00\x00\x00\x00");
530 raw.extend_from_slice(b"\x82\x86\x84\x41\x8a > \n\xff\xfe HTTP/1.1\r\n");
531 assert_eq!(run(&[&raw]), raw);
532 }
533
534 #[test]
537 fn an_http2_preface_split_across_reads_is_still_recognised() {
538 let out = run(&[b"PRI * HTTP/2.0\r\n", b"\r\nSM\r\n\r\n", b"\xff\xfe->\n"]);
539 assert_eq!(
540 out,
541 [H2_PREFACE, b"\xff\xfe->\n"].concat(),
542 "an HTTP/2 connection was rewritten"
543 );
544 }
545
546 #[test]
548 fn a_request_beginning_like_the_preface_is_read_as_http1() {
549 let out = run(&[b"PRI /t?s=a->b HTTP/1.1\r\n\r\n"]);
550 assert_eq!(
551 String::from_utf8(out).unwrap(),
552 "PRI /t?s=a-%3Eb HTTP/1.1\r\n\r\n"
553 );
554 }
555
556 #[test]
558 fn a_request_beginning_like_the_preface_survives_being_split() {
559 let out = run(&[b"P", b"OST /t?s=a->b HTTP/1.1\r\n", b"\r\n"]);
560 assert_eq!(
561 String::from_utf8(out).unwrap(),
562 "POST /t?s=a-%3Eb HTTP/1.1\r\n\r\n"
563 );
564 }
565}