Skip to main content

io_http/rfc9112/
read_headers.rs

1//! I/O-free coroutine reading and parsing an HTTP/1.X response head
2//! ([RFC 9112 §6]). Composed by `Http10Send`, `Http11Send`, and the SSE
3//! bootstrap.
4//!
5//! [RFC 9112 §6]: https://www.rfc-editor.org/rfc/rfc9112#section-6
6
7use alloc::vec::Vec;
8
9use httparse::{EMPTY_HEADER, Error as HttparseError, Response, Status};
10use log::{debug, trace};
11use thiserror::Error;
12
13use crate::{
14    coroutine::*,
15    rfc1945::version::HTTP_10,
16    rfc9110::{
17        headers::HTTP_CONNECTION,
18        response::{HttpResponse, HttpResponseBuilder},
19        status::HttpStatusCode,
20    },
21    rfc9112::version::HTTP_11,
22};
23
24/// Failure causes during the HTTP/1.X read-headers flow.
25#[derive(Debug, Error)]
26pub enum Http11HeadersReadError {
27    /// The stream reached EOF before the head was complete.
28    #[error("HTTP/1.X read headers failed: reached unexpected EOF before headers were complete")]
29    Eof,
30    /// The response head could not be parsed.
31    #[error("HTTP/1.X read headers failed: parse response headers: {0}")]
32    ParseResponseHeaders(HttparseError),
33    /// The response head exceeds [`Http11HeadersRead::max_len`].
34    #[error("HTTP/1.X read headers failed: response head exceeds {0} bytes")]
35    TooLarge(usize),
36}
37
38/// Terminal output of [`Http11HeadersRead`]; the response body is
39/// empty at this stage.
40#[derive(Debug)]
41pub struct Http11HeadersReadOutput {
42    /// The parsed response, head only.
43    pub response: HttpResponse,
44    /// Bytes already buffered past the end of the head.
45    pub remaining: Vec<u8>,
46    /// Whether the server signalled the connection can be reused.
47    pub keep_alive: bool,
48}
49
50/// Default value of [`Http11HeadersRead::max_len`]: 64 KiB.
51pub const HTTP_DEFAULT_MAX_HEADERS_LEN: usize = 64 * 1024;
52
53/// I/O-free coroutine to read and parse an HTTP/1.X response head.
54#[derive(Debug)]
55pub struct Http11HeadersRead {
56    /// Maximum size in bytes of the response head, status line and
57    /// terminating empty line included. A peer exceeding it fails the
58    /// read with [`Http11HeadersReadError::TooLarge`] instead of growing
59    /// the buffer without bound.
60    pub max_len: usize,
61    buf: Vec<u8>,
62}
63
64impl Default for Http11HeadersRead {
65    fn default() -> Self {
66        Self {
67            max_len: HTTP_DEFAULT_MAX_HEADERS_LEN,
68            buf: Vec::new(),
69        }
70    }
71}
72
73impl HttpCoroutine for Http11HeadersRead {
74    type Yield = HttpYield;
75    type Return = Result<Http11HeadersReadOutput, Http11HeadersReadError>;
76
77    fn resume(&mut self, arg: Option<&[u8]>) -> HttpCoroutineState<Self::Yield, Self::Return> {
78        match arg {
79            Some(&[]) => {
80                return HttpCoroutineState::Complete(Err(Http11HeadersReadError::Eof));
81            }
82            Some(data) => self.buf.extend_from_slice(data),
83            None => {}
84        }
85
86        let mut headers = [EMPTY_HEADER; 64];
87        let mut parsed = Response::new(&mut headers);
88
89        let header_end = match parsed.parse(&self.buf) {
90            Ok(Status::Complete(n)) if n > self.max_len => {
91                let err = Http11HeadersReadError::TooLarge(self.max_len);
92                return HttpCoroutineState::Complete(Err(err));
93            }
94            Ok(Status::Complete(n)) => n,
95            Ok(Status::Partial) if self.buf.len() > self.max_len => {
96                let err = Http11HeadersReadError::TooLarge(self.max_len);
97                return HttpCoroutineState::Complete(Err(err));
98            }
99            Ok(Status::Partial) => {
100                trace!("received incomplete headers");
101                return HttpCoroutineState::Yielded(HttpYield::WantsRead);
102            }
103            Err(err) => {
104                return HttpCoroutineState::Complete(Err(
105                    Http11HeadersReadError::ParseResponseHeaders(err),
106                ));
107            }
108        };
109
110        let mut builder = HttpResponseBuilder::default();
111        let is_http10 = matches!(parsed.version, Some(0));
112        builder.version = if is_http10 { HTTP_10 } else { HTTP_11 }.into();
113        if let Some(code) = parsed.code {
114            builder.status = Some(HttpStatusCode(code));
115        }
116        for header in parsed.headers.iter() {
117            builder.header(header.name, header.value);
118        }
119
120        let keep_alive = match builder.get_header(HTTP_CONNECTION) {
121            Some(conn) => !conn.eq_ignore_ascii_case("close"),
122            None => !is_http10,
123        };
124
125        debug!("received complete headers");
126        trace!("{builder:?}");
127
128        let response = builder.build(Vec::new());
129        let remaining = self.buf.split_off(header_end);
130
131        HttpCoroutineState::Complete(Ok(Http11HeadersReadOutput {
132            response,
133            remaining,
134            keep_alive,
135        }))
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use crate::rfc9112::read_headers::*;
142
143    #[test]
144    fn parses_complete_head() {
145        let mut coroutine = Http11HeadersRead::default();
146        let reply = b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nbody";
147
148        let out = expect_complete_ok(&mut coroutine, Some(reply));
149        assert_eq!(*out.response.status, 200);
150        assert_eq!(out.response.version, "HTTP/1.1");
151        assert_eq!(out.response.header("content-type"), Some("text/plain"));
152        assert_eq!(out.remaining, b"body");
153    }
154
155    #[test]
156    fn incomplete_head_wants_read() {
157        let mut coroutine = Http11HeadersRead::default();
158        expect_wants_read(&mut coroutine, Some(b"HTTP/1.1 200 OK\r\n"));
159    }
160
161    #[test]
162    fn eof_returns_eof_error() {
163        let mut coroutine = Http11HeadersRead::default();
164        let err = expect_complete_err(&mut coroutine, Some(b""));
165        assert!(matches!(err, Http11HeadersReadError::Eof));
166    }
167
168    #[test]
169    fn http10_keep_alive_defaults_false() {
170        let mut coroutine = Http11HeadersRead::default();
171        let reply = b"HTTP/1.0 200 OK\r\n\r\n";
172        let out = expect_complete_ok(&mut coroutine, Some(reply));
173        assert!(!out.keep_alive);
174        assert_eq!(out.response.version, "HTTP/1.0");
175    }
176
177    #[test]
178    fn http11_keep_alive_defaults_true() {
179        let mut coroutine = Http11HeadersRead::default();
180        let reply = b"HTTP/1.1 200 OK\r\n\r\n";
181        let out = expect_complete_ok(&mut coroutine, Some(reply));
182        assert!(out.keep_alive);
183    }
184
185    #[test]
186    fn connection_close_overrides_default() {
187        let mut coroutine = Http11HeadersRead::default();
188        let reply = b"HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n";
189        let out = expect_complete_ok(&mut coroutine, Some(reply));
190        assert!(!out.keep_alive);
191    }
192
193    #[test]
194    fn partial_head_over_limit_errors() {
195        let mut coroutine = Http11HeadersRead {
196            max_len: 32,
197            ..Http11HeadersRead::default()
198        };
199        expect_wants_read(&mut coroutine, Some(b"HTTP/1.1 200 OK\r\n"));
200        let err = expect_complete_err(&mut coroutine, Some(b"X-Filler: aaaaaaaaaaaaaaaa\r\n"));
201        assert!(matches!(err, Http11HeadersReadError::TooLarge(32)));
202    }
203
204    #[test]
205    fn complete_head_over_limit_errors() {
206        let mut coroutine = Http11HeadersRead {
207            max_len: 16,
208            ..Http11HeadersRead::default()
209        };
210        let err = expect_complete_err(&mut coroutine, Some(b"HTTP/1.1 200 OK\r\n\r\n"));
211        assert!(matches!(err, Http11HeadersReadError::TooLarge(16)));
212    }
213
214    fn expect_wants_read(cor: &mut Http11HeadersRead, arg: Option<&[u8]>) {
215        match cor.resume(arg) {
216            HttpCoroutineState::Yielded(HttpYield::WantsRead) => {}
217            state => panic!("expected WantsRead, got {state:?}"),
218        }
219    }
220
221    fn expect_complete_ok(
222        cor: &mut Http11HeadersRead,
223        arg: Option<&[u8]>,
224    ) -> Http11HeadersReadOutput {
225        match cor.resume(arg) {
226            HttpCoroutineState::Complete(Ok(out)) => out,
227            state => panic!("expected Complete(Ok), got {state:?}"),
228        }
229    }
230
231    fn expect_complete_err(
232        cor: &mut Http11HeadersRead,
233        arg: Option<&[u8]>,
234    ) -> Http11HeadersReadError {
235        match cor.resume(arg) {
236            HttpCoroutineState::Complete(Err(err)) => err,
237            state => panic!("expected Complete(Err), got {state:?}"),
238        }
239    }
240}