Skip to main content

go_http/parse/
request.rs

1// SPDX-License-Identifier: Apache-2.0
2
3/// HTTP/1.1 request parser — port of Go net/http `readRequest`.
4use std::io::Read;
5
6
7use super::{read_headers, read_line, ParseError};
8use crate::header::Header;
9use crate::method;
10use crate::parse::transfer::{resolve_body, Body, MessageKind};
11
12/// Default maximum total header bytes, matching Go's `DefaultMaxHeaderBytes`.
13pub const DEFAULT_MAX_HEADER_BYTES: usize = 1 << 20; // 1 MiB
14
15/// The result of parsing an HTTP/1.1 request line + headers.
16/// The body reader is attached but not yet consumed.
17pub struct ParsedRequest {
18    pub method:            String,
19    pub request_uri:       String,
20    pub proto:             String,
21    pub proto_major:       u8,
22    pub proto_minor:       u8,
23    pub header:            Header,
24    pub body:              Body,
25    pub content_length:    i64,
26    pub transfer_encoding: Vec<String>,
27    pub host:              String,
28}
29
30impl std::fmt::Debug for ParsedRequest {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        f.debug_struct("ParsedRequest")
33            .field("method", &self.method)
34            .field("request_uri", &self.request_uri)
35            .finish_non_exhaustive()
36    }
37}
38
39/// Parse an HTTP/1.1 request from `r`.
40///
41/// Port of Go's `readRequest(b *bufio.Reader) (*Request, error)`.
42/// Reads the request line, validates the method and proto, reads headers,
43/// then resolves the body framing.
44pub fn read_request(
45    r: impl Read + Send + 'static,
46    max_header_bytes: usize,
47) -> Result<ParsedRequest, ParseError> {
48    let mut r: Box<dyn Read + Send> = Box::new(r);
49
50    // ── Request line ─────────────────────────────────────────────────────────
51    let line = read_line(&mut r)?;
52    let parts: Vec<&str> = line.splitn(3, ' ').collect();
53    if parts.len() != 3 {
54        return Err(ParseError::BadRequestLine);
55    }
56    let raw_method = parts[0];
57    let request_uri = parts[1];
58    let proto = parts[2];
59
60    if !method::is_valid(raw_method) {
61        return Err(ParseError::BadRequestLine);
62    }
63
64    let (proto_major, proto_minor) = parse_proto(proto)?;
65
66    // ── Headers ───────────────────────────────────────────────────────────────
67    let header = read_headers(&mut r, max_header_bytes)?;
68
69    // Host: prefer the Host header; fall back to the authority in the URI.
70    let host = header.get("Host").unwrap_or("").to_owned();
71
72    // ── Transfer-Encoding list ────────────────────────────────────────────────
73    let transfer_encoding: Vec<String> = header
74        .values("Transfer-Encoding")
75        .iter()
76        .flat_map(|v| v.split(',').map(|s| s.trim().to_ascii_lowercase()))
77        .collect();
78
79    // ── Content-Length ────────────────────────────────────────────────────────
80    let content_length = parse_content_length(&header)?;
81
82    // ── Body ──────────────────────────────────────────────────────────────────
83    let body = resolve_body(r, &header, MessageKind::Request)?;
84
85    Ok(ParsedRequest {
86        method: raw_method.to_owned(),
87        request_uri: request_uri.to_owned(),
88        proto: proto.to_owned(),
89        proto_major,
90        proto_minor,
91        header,
92        body,
93        content_length,
94        transfer_encoding,
95        host,
96    })
97}
98
99// ---------------------------------------------------------------------------
100// Helpers
101// ---------------------------------------------------------------------------
102
103/// Parse "HTTP/1.1" → (1, 1).  Port of Go's `ParseHTTPVersion`.
104fn parse_proto(proto: &str) -> Result<(u8, u8), ParseError> {
105    if !proto.starts_with("HTTP/") {
106        return Err(ParseError::BadRequestLine);
107    }
108    let ver = &proto[5..];
109    let dot = ver.find('.').ok_or(ParseError::BadRequestLine)?;
110    let major: u8 = ver[..dot].parse().map_err(|_| ParseError::BadRequestLine)?;
111    let minor: u8 = ver[dot + 1..].parse().map_err(|_| ParseError::BadRequestLine)?;
112    Ok((major, minor))
113}
114
115/// Parse Content-Length header. Returns -1 if absent; error if malformed.
116fn parse_content_length(h: &Header) -> Result<i64, ParseError> {
117    match h.get("Content-Length") {
118        None => Ok(-1),
119        Some(s) => s
120            .trim()
121            .parse::<i64>()
122            .map_err(|_| ParseError::InvalidContentLength),
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use std::io::{Cursor, Read};
130
131    fn parse(raw: &'static [u8]) -> ParsedRequest {
132        read_request(Cursor::new(raw), DEFAULT_MAX_HEADER_BYTES).unwrap()
133    }
134
135    #[test]
136    fn simple_get() {
137        let req = parse(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n");
138        assert_eq!(req.method, "GET");
139        assert_eq!(req.request_uri, "/");
140        assert_eq!(req.proto_major, 1);
141        assert_eq!(req.proto_minor, 1);
142        assert_eq!(req.host, "example.com");
143    }
144
145    #[test]
146    fn post_with_body() {
147        let raw = b"POST /submit HTTP/1.1\r\nHost: x\r\nContent-Length: 5\r\n\r\nHello extra";
148        let mut req = parse(raw);
149        let mut body_out = Vec::new();
150        req.body.read_to_end(&mut body_out).unwrap();
151        assert_eq!(body_out, b"Hello");
152    }
153
154    #[test]
155    fn bad_method() {
156        let result = read_request(
157            Cursor::new(b"G\xc3\x89T / HTTP/1.1\r\n\r\n"),
158            DEFAULT_MAX_HEADER_BYTES,
159        );
160        assert_eq!(result.unwrap_err(), ParseError::BadRequestLine);
161    }
162
163    #[test]
164    fn bad_proto() {
165        let result = read_request(
166            Cursor::new(b"GET / NOTHTTP\r\n\r\n"),
167            DEFAULT_MAX_HEADER_BYTES,
168        );
169        assert_eq!(result.unwrap_err(), ParseError::BadRequestLine);
170    }
171}