Skip to main content

io_http/rfc1945/
send.rs

1//! I/O-free coroutine sending an HTTP/1.0 request and receiving its response
2//! ([RFC 1945]).
3//!
4//! Same shape as [`crate::rfc9112::send::Http11Send`] but with the HTTP/1.0
5//! wire format: no chunked transfer coding, connections close by default unless
6//! the server returns `Connection: keep-alive`.
7//!
8//! | Strategy     | Trigger               |
9//! |--------------|-----------------------|
10//! | Fixed-length | `Content-Length: <n>` |
11//! | Read-to-EOF  | Header absent         |
12//!
13//! # Example
14//!
15//! ```rust,no_run
16//! use std::{io::{Read, Write}, net::TcpStream};
17//!
18//! use io_http::{
19//!     coroutine::*,
20//!     rfc1945::send::Http10Send,
21//!     rfc9110::{request::HttpRequest, send::HttpSendYield},
22//! };
23//! use url::Url;
24//!
25//! let url = Url::parse("http://example.com/").unwrap();
26//! let request = HttpRequest::get(url.clone())
27//!     .header("Host", url.host_str().unwrap());
28//!
29//! let mut stream = TcpStream::connect("example.com:80").unwrap();
30//! let mut send = Http10Send::new(request);
31//! let mut arg: Option<&[u8]> = None;
32//! let mut buf = [0u8; 4096];
33//!
34//! let response = loop {
35//!     match send.resume(arg.take()) {
36//!         HttpCoroutineState::Complete(Ok(out)) => break out.response,
37//!         HttpCoroutineState::Complete(Err(err)) => panic!("{err}"),
38//!         HttpCoroutineState::Yielded(HttpSendYield::WantsWrite(bytes)) => {
39//!             stream.write_all(&bytes).unwrap();
40//!         }
41//!         HttpCoroutineState::Yielded(HttpSendYield::WantsRead) => {
42//!             let n = stream.read(&mut buf).unwrap();
43//!             arg = Some(&buf[..n]);
44//!         }
45//!         HttpCoroutineState::Yielded(HttpSendYield::WantsRedirect { .. }) => unimplemented!(),
46//!     }
47//! };
48//!
49//! println!("{}", *response.status);
50//! ```
51//!
52//! [RFC 1945]: https://www.rfc-editor.org/rfc/rfc1945
53
54use core::mem;
55
56use alloc::{borrow::ToOwned, string::String, vec::Vec};
57
58use httparse::Error as HttparseError;
59use log::{debug, trace};
60use thiserror::Error;
61use url::Url;
62
63use crate::{
64    coroutine::*,
65    rfc9110::{
66        headers::{HTTP_CONTENT_LENGTH, HTTP_LOCATION},
67        request::HttpRequest,
68        response::HttpResponse,
69        send::{HTTP_DEFAULT_MAX_BODY_LEN, HttpSendOutput, HttpSendYield},
70    },
71    rfc9112::read_headers::{
72        HTTP_DEFAULT_MAX_HEADERS_LEN, Http11HeadersRead, Http11HeadersReadError,
73    },
74};
75
76/// Failure causes during the HTTP/1.0 send flow.
77#[derive(Debug, Error)]
78pub enum Http10SendError {
79    /// The stream reached EOF before the response was complete.
80    #[error("HTTP/1.0 send failed: reached unexpected EOF")]
81    Eof,
82    /// The response head could not be parsed.
83    #[error("HTTP/1.0 send failed: parse response headers: {0}")]
84    ParseResponseHeaders(HttparseError),
85    /// The content length header value is not a valid integer.
86    #[error("HTTP/1.0 send failed: invalid content length `{0}`")]
87    InvalidContentLength(String),
88    /// The response head exceeds [`Http10Send::max_headers_len`].
89    #[error("HTTP/1.0 send failed: response head exceeds {0} bytes")]
90    HeadersTooLarge(usize),
91    /// The response body exceeds [`Http10Send::max_body_len`].
92    #[error("HTTP/1.0 send failed: response body exceeds {0} bytes")]
93    BodyTooLarge(usize),
94}
95
96impl From<Http11HeadersReadError> for Http10SendError {
97    fn from(err: Http11HeadersReadError) -> Self {
98        match err {
99            Http11HeadersReadError::Eof => Self::Eof,
100            Http11HeadersReadError::ParseResponseHeaders(e) => Self::ParseResponseHeaders(e),
101            Http11HeadersReadError::TooLarge(limit) => Self::HeadersTooLarge(limit),
102        }
103    }
104}
105
106/// I/O-free coroutine to send an HTTP/1.0 request and receive its response.
107#[derive(Debug)]
108pub struct Http10Send {
109    /// Maximum size in bytes of the response head. Defaults to
110    /// [`HTTP_DEFAULT_MAX_HEADERS_LEN`].
111    pub max_headers_len: usize,
112    /// Maximum size in bytes of the response body, whatever its
113    /// framing (fixed-length or read-to-EOF). Defaults to
114    /// [`HTTP_DEFAULT_MAX_BODY_LEN`].
115    pub max_body_len: usize,
116    request_url: Url,
117    state: State,
118    wants_write: Option<Vec<u8>>,
119    keep_alive: bool,
120    response: Option<HttpResponse>,
121    buf: Vec<u8>,
122}
123
124impl Http10Send {
125    /// Creates a new coroutine that will send the given request and
126    /// receive its response.
127    pub fn new(req: HttpRequest) -> Self {
128        debug!("prepare request to send");
129        trace!("{req:?}");
130
131        let request_url = req.url.clone();
132        let bytes = req.to_http_10_vec();
133
134        Self {
135            max_headers_len: HTTP_DEFAULT_MAX_HEADERS_LEN,
136            max_body_len: HTTP_DEFAULT_MAX_BODY_LEN,
137            request_url,
138            state: State::ReadHeaders(Http11HeadersRead::default()),
139            wants_write: Some(bytes),
140            keep_alive: false,
141            response: None,
142            buf: Vec::new(),
143        }
144    }
145
146    fn finish(
147        &self,
148        response: HttpResponse,
149        remaining: Vec<u8>,
150    ) -> HttpCoroutineState<HttpSendYield, Result<HttpSendOutput, Http10SendError>> {
151        let keep_alive = self.keep_alive;
152
153        if response.status.is_redirection()
154            && let Some(location) = response.header(HTTP_LOCATION)
155            && let Ok(url) = self.request_url.join(location)
156        {
157            let same_scheme = self.request_url.scheme() == url.scheme();
158            let same_host =
159                self.request_url.host() == url.host() && self.request_url.port() == url.port();
160            let same_origin = same_scheme && same_host;
161
162            return HttpCoroutineState::Yielded(HttpSendYield::WantsRedirect {
163                url,
164                response,
165                keep_alive,
166                same_origin,
167            });
168        }
169
170        HttpCoroutineState::Complete(Ok(HttpSendOutput {
171            response,
172            remaining,
173            keep_alive,
174        }))
175    }
176}
177
178impl HttpCoroutine for Http10Send {
179    type Yield = HttpSendYield;
180    type Return = Result<HttpSendOutput, Http10SendError>;
181
182    fn resume(&mut self, mut arg: Option<&[u8]>) -> HttpCoroutineState<Self::Yield, Self::Return> {
183        loop {
184            if let Some(bytes) = self.wants_write.take() {
185                return HttpCoroutineState::Yielded(HttpSendYield::WantsWrite(bytes));
186            }
187
188            if let State::ReadHeaders(rh) = &mut self.state {
189                rh.max_len = self.max_headers_len;
190            }
191
192            match &mut self.state {
193                State::ReadHeaders(rh) => match rh.resume(arg.take()) {
194                    HttpCoroutineState::Yielded(HttpYield::WantsRead) => {
195                        return HttpCoroutineState::Yielded(HttpSendYield::WantsRead);
196                    }
197                    HttpCoroutineState::Yielded(HttpYield::WantsWrite(_)) => {
198                        unreachable!("Http11HeadersRead never writes");
199                    }
200                    HttpCoroutineState::Complete(Err(err)) => {
201                        return HttpCoroutineState::Complete(Err(err.into()));
202                    }
203                    HttpCoroutineState::Complete(Ok(out)) => {
204                        let response = out.response;
205                        self.keep_alive = out.keep_alive;
206                        let status = *response.status;
207
208                        if status == 204 || status == 304 {
209                            return self.finish(response, out.remaining);
210                        }
211
212                        if let Some(len_str) = response.header(HTTP_CONTENT_LENGTH) {
213                            let len_str = len_str.trim();
214                            let is_digits = len_str.bytes().all(|b| b.is_ascii_digit());
215
216                            let Some(len) =
217                                is_digits.then(|| len_str.parse::<usize>().ok()).flatten()
218                            else {
219                                let err = Http10SendError::InvalidContentLength(len_str.to_owned());
220                                return HttpCoroutineState::Complete(Err(err));
221                            };
222
223                            if len > self.max_body_len {
224                                let err = Http10SendError::BodyTooLarge(self.max_body_len);
225                                return HttpCoroutineState::Complete(Err(err));
226                            }
227
228                            self.buf = out.remaining;
229                            self.response = Some(response);
230                            debug!("reading body of length {len}");
231                            self.state = State::BodyLength(len);
232                            continue;
233                        }
234
235                        self.buf = out.remaining;
236                        self.response = Some(response);
237                        debug!("reading body until connection closes");
238                        self.state = State::BodyEof;
239                    }
240                },
241                State::BodyLength(len) => {
242                    match arg.take() {
243                        Some(&[]) if *len > self.buf.len() => {
244                            return HttpCoroutineState::Complete(Err(Http10SendError::Eof));
245                        }
246                        Some(data) => self.buf.extend_from_slice(data),
247                        None => {}
248                    }
249
250                    if *len > self.buf.len() {
251                        trace!("received incomplete body {len}/{}", self.buf.len());
252                        return HttpCoroutineState::Yielded(HttpSendYield::WantsRead);
253                    }
254
255                    let body = self.buf.drain(..*len).collect();
256                    let remaining = mem::take(&mut self.buf);
257                    let mut response = self.response.take().expect("response missing");
258                    response.body = body;
259                    return self.finish(response, remaining);
260                }
261                State::BodyEof => match arg.take() {
262                    _ if self.buf.len() > self.max_body_len => {
263                        let err = Http10SendError::BodyTooLarge(self.max_body_len);
264                        return HttpCoroutineState::Complete(Err(err));
265                    }
266                    Some(&[]) => {
267                        let buf = mem::take(&mut self.buf);
268                        let mut response = self.response.take().expect("response missing");
269                        response.body = buf;
270                        return self.finish(response, Vec::new());
271                    }
272                    Some(data) if data.len() > self.max_body_len.saturating_sub(self.buf.len()) => {
273                        let err = Http10SendError::BodyTooLarge(self.max_body_len);
274                        return HttpCoroutineState::Complete(Err(err));
275                    }
276                    Some(data) => {
277                        self.buf.extend_from_slice(data);
278                        return HttpCoroutineState::Yielded(HttpSendYield::WantsRead);
279                    }
280                    None => {
281                        return HttpCoroutineState::Yielded(HttpSendYield::WantsRead);
282                    }
283                },
284            }
285        }
286    }
287}
288
289#[derive(Debug)]
290enum State {
291    ReadHeaders(Http11HeadersRead),
292    BodyLength(usize),
293    BodyEof,
294}
295
296#[cfg(test)]
297mod tests {
298    use crate::rfc1945::send::*;
299
300    #[test]
301    fn body_length_completes() {
302        let req = HttpRequest::get("http://example.com".try_into().unwrap());
303        let mut coroutine = Http10Send::new(req);
304
305        let bytes = expect_wants_write(&mut coroutine, None);
306        assert_eq!(bytes, b"GET / HTTP/1.0\r\ncontent-length: 0\r\n\r\n");
307
308        expect_wants_read(&mut coroutine, None);
309
310        let reply = b"HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nhello";
311        let out = expect_complete_ok(&mut coroutine, Some(reply));
312        assert_eq!(out.response.version, "HTTP/1.0");
313        assert_eq!(*out.response.status, 200);
314        assert_eq!(out.response.body, b"hello");
315        assert!(!out.keep_alive);
316    }
317
318    #[test]
319    fn body_eof_completes() {
320        let req = HttpRequest::get("http://example.com".try_into().unwrap());
321        let mut coroutine = Http10Send::new(req);
322
323        expect_wants_write(&mut coroutine, None);
324        expect_wants_read(&mut coroutine, None);
325        expect_wants_read(&mut coroutine, Some(b"HTTP/1.0 200 OK\r\n\r\nhello "));
326        expect_wants_read(&mut coroutine, Some(b"world"));
327
328        let out = expect_complete_ok(&mut coroutine, Some(b""));
329        assert_eq!(out.response.body, b"hello world");
330        assert!(!out.keep_alive);
331    }
332
333    #[test]
334    fn keep_alive_when_server_says_so() {
335        let req = HttpRequest::get("http://example.com".try_into().unwrap());
336        let mut coroutine = Http10Send::new(req);
337
338        expect_wants_write(&mut coroutine, None);
339        expect_wants_read(&mut coroutine, None);
340
341        let reply = b"HTTP/1.0 200 OK\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
342        let out = expect_complete_ok(&mut coroutine, Some(reply));
343        assert!(out.keep_alive);
344    }
345
346    #[test]
347    fn invalid_content_length_errors() {
348        let req = HttpRequest::get("http://example.com".try_into().unwrap());
349        let mut coroutine = Http10Send::new(req);
350
351        expect_wants_write(&mut coroutine, None);
352        expect_wants_read(&mut coroutine, None);
353
354        let reply = b"HTTP/1.0 200 OK\r\nContent-Length: notanumber\r\n\r\n";
355        let err = expect_complete_err(&mut coroutine, Some(reply));
356        let Http10SendError::InvalidContentLength(s) = err else {
357            panic!("expected InvalidContentLength, got {err:?}");
358        };
359        assert_eq!(s, "notanumber");
360    }
361
362    fn expect_wants_write(cor: &mut Http10Send, arg: Option<&[u8]>) -> Vec<u8> {
363        match cor.resume(arg) {
364            HttpCoroutineState::Yielded(HttpSendYield::WantsWrite(bytes)) => bytes,
365            state => panic!("expected WantsWrite, got {state:?}"),
366        }
367    }
368
369    fn expect_wants_read(cor: &mut Http10Send, arg: Option<&[u8]>) {
370        match cor.resume(arg) {
371            HttpCoroutineState::Yielded(HttpSendYield::WantsRead) => {}
372            state => panic!("expected WantsRead, got {state:?}"),
373        }
374    }
375
376    fn expect_complete_ok(cor: &mut Http10Send, arg: Option<&[u8]>) -> HttpSendOutput {
377        match cor.resume(arg) {
378            HttpCoroutineState::Complete(Ok(out)) => out,
379            state => panic!("expected Complete(Ok), got {state:?}"),
380        }
381    }
382
383    fn expect_complete_err(cor: &mut Http10Send, arg: Option<&[u8]>) -> Http10SendError {
384        match cor.resume(arg) {
385            HttpCoroutineState::Complete(Err(err)) => err,
386            state => panic!("expected Complete(Err), got {state:?}"),
387        }
388    }
389}