Skip to main content

simoom_http1/
lib.rs

1//! Sans-io, strict HTTP/1.1 parser and serializer (RFC 9112) for simoom.
2//!
3//! - [`parse_request`] / [`RequestParser`]: zero-copy request heads (borrowed slices, caller-owned
4//!   header storage, no allocation), resumable over partial input, strict per RFC 9112.
5//! - [`parse_response`]: the same for responses (client side and test tools).
6//! - [`BodyDecoder`]: Content-Length, chunked (strict grammar, capped extensions, trailers) and
7//!   close-delimited bodies, incremental and zero-copy.
8//! - [`write`]: cached `Date`, pre-serialized heads, a head writer, the chunked encoder.
9//! - [`h2c`]: the HTTP/2 preface and the `Upgrade: h2c` request.
10//! - `ws` (feature `websocket`): the RFC 6455 handshake and frame codec.
11//!
12//! Pipelining: a parsed head reports its length, the body decoder reports what it consumed, and
13//! the next request starts right after; nothing is buffered inside the codec except while a
14//! trailer section is incomplete.
15#![forbid(unsafe_code)]
16
17mod b64;
18mod body;
19pub mod h2c;
20mod parse;
21pub mod write;
22#[cfg(feature = "websocket")]
23mod sha1;
24#[cfg(feature = "websocket")]
25pub mod ws;
26
27pub use b64::{base64_decode, base64_encode, base64url_decode};
28pub use body::{BodyDecoder, BodyEvent};
29pub use parse::{has_token, parse_request, parse_response, parse_trailers, RequestParser};
30
31/// The HTTP version of a message.
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum Version {
34    /// HTTP/1.0
35    Http10,
36    /// HTTP/1.1
37    Http11,
38}
39
40/// A header field: borrowed name and value (OWS trimmed).
41#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
42pub struct Header<'b> {
43    /// Field name, as sent.
44    pub name: &'b [u8],
45    /// Field value without leading and trailing whitespace.
46    pub value: &'b [u8],
47}
48
49/// An empty header, to initialise header storage: `[EMPTY_HEADER; 100]`.
50pub const EMPTY_HEADER: Header<'static> = Header { name: b"", value: b"" };
51
52/// How the message body is delimited (RFC 9112 §6.3).
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub enum Framing {
55    /// No body.
56    None,
57    /// Exactly this many bytes (never zero: a zero length is [`Framing::None`]).
58    Length(u64),
59    /// `Transfer-Encoding: chunked`.
60    Chunked,
61    /// Until the connection closes (responses only).
62    Close,
63}
64
65/// The form of a request target (RFC 9112 §3.2).
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67pub enum TargetForm {
68    /// `/path?query`
69    Origin,
70    /// `scheme://authority/path?query`
71    Absolute,
72    /// `host:port`, CONNECT only.
73    Authority,
74    /// `*`, OPTIONS only.
75    Asterisk,
76}
77
78/// Result of parsing input that may be incomplete.
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub enum Status<T> {
81    /// A whole message head.
82    Complete(T),
83    /// More bytes are needed.
84    Partial,
85}
86
87/// A parsed request head. Everything borrows from the receive buffer (`'b`) and the header
88/// storage (`'h`).
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90pub struct Request<'b, 'h> {
91    /// The method token.
92    pub method: &'b [u8],
93    /// The request target, as sent.
94    pub target: &'b [u8],
95    /// The target's form.
96    pub form: TargetForm,
97    /// The version.
98    pub version: Version,
99    /// All header fields in order.
100    pub headers: &'h [Header<'b>],
101    /// How the body is delimited.
102    pub framing: Framing,
103    /// Whether the connection may be reused after this exchange (version and `Connection`).
104    pub keep_alive: bool,
105    /// `Expect: 100-continue` on an HTTP/1.1 request (RFC 9110 §10.1.1).
106    pub expect_continue: bool,
107    /// The `Upgrade` value when `Connection` names `upgrade` (HTTP/1.1 only).
108    pub upgrade: Option<&'b [u8]>,
109    /// The `Host` value.
110    pub host: Option<&'b [u8]>,
111    /// Bytes taken by the head, including leading empty lines and the final CRLF.
112    pub head_len: usize,
113}
114
115impl<'b> Request<'b, '_> {
116    /// The value of the first header named `name` (case-insensitive).
117    pub fn header(&self, name: &[u8]) -> Option<&'b [u8]> {
118        self.headers.iter().find(|h| h.name.eq_ignore_ascii_case(name)).map(|h| h.value)
119    }
120
121    /// True when a `100 Continue` should be sent before reading the body.
122    pub fn wants_continue(&self) -> bool {
123        self.expect_continue && self.framing != Framing::None
124    }
125}
126
127/// A parsed response head.
128#[derive(Clone, Copy, Debug, PartialEq, Eq)]
129pub struct Response<'b, 'h> {
130    /// The version.
131    pub version: Version,
132    /// The status code, 100 to 599.
133    pub status: u16,
134    /// The reason phrase (possibly empty).
135    pub reason: &'b [u8],
136    /// All header fields in order.
137    pub headers: &'h [Header<'b>],
138    /// How the body is delimited, given the request method.
139    pub framing: Framing,
140    /// Whether the connection may be reused.
141    pub keep_alive: bool,
142    /// The connection now carries another protocol: `101 Switching Protocols`, or a 2xx reply
143    /// to CONNECT (a tunnel).
144    pub upgraded: bool,
145    /// Bytes taken by the head.
146    pub head_len: usize,
147}
148
149impl<'b> Response<'b, '_> {
150    /// The value of the first header named `name` (case-insensitive).
151    pub fn header(&self, name: &[u8]) -> Option<&'b [u8]> {
152        self.headers.iter().find(|h| h.name.eq_ignore_ascii_case(name)).map(|h| h.value)
153    }
154}
155
156/// Parser limits (plan §7.1). Exceeding one gives a specific [`Error`].
157#[derive(Clone, Copy, Debug, PartialEq, Eq)]
158pub struct Limits {
159    /// Request line (or status line) in bytes, CRLF included. Default 8 KiB.
160    pub max_request_line: usize,
161    /// Whole head in bytes. Default 16 KiB.
162    pub max_head: usize,
163    /// Header fields. Default 100.
164    pub max_headers: usize,
165    /// One header line in bytes, CRLF included. Default 8 KiB.
166    pub max_header_line: usize,
167    /// Body in bytes; a larger Content-Length is refused before any body byte is read.
168    /// Default 10 MiB.
169    pub max_body: u64,
170    /// A chunk-size line (size, extensions and CRLF) in bytes. Default 1 KiB.
171    pub max_chunk_line: usize,
172    /// The trailer section in bytes. Default 8 KiB.
173    pub max_trailers: usize,
174}
175
176impl Default for Limits {
177    fn default() -> Self {
178        Limits {
179            max_request_line: 8 * 1024,
180            max_head: 16 * 1024,
181            max_headers: 100,
182            max_header_line: 8 * 1024,
183            max_body: 10 * 1024 * 1024,
184            max_chunk_line: 1024,
185            max_trailers: 8 * 1024,
186        }
187    }
188}
189
190/// Why a message was refused. Every error means the connection must close after the error
191/// response (if any) is sent.
192#[derive(Clone, Copy, Debug, PartialEq, Eq)]
193pub enum Error {
194    /// The method is empty or has a byte outside `tchar`, or is not followed by one SP.
195    InvalidMethod,
196    /// The request target has a forbidden byte, bad percent-encoding, or the wrong form for the
197    /// method.
198    InvalidTarget,
199    /// The version is not `HTTP/` digit `.` digit, or the start line has extra bytes.
200    InvalidVersion,
201    /// A well-formed version other than 1.0 and 1.1.
202    VersionNotSupported,
203    /// The request line passed [`Limits::max_request_line`].
204    RequestLineTooLong,
205    /// The head passed [`Limits::max_head`].
206    HeadTooLarge,
207    /// More than [`Limits::max_headers`] fields, or more than the header storage holds.
208    TooManyHeaders,
209    /// A header line passed [`Limits::max_header_line`].
210    HeaderLineTooLong,
211    /// A field name is empty or has a byte outside `tchar`.
212    InvalidHeaderName,
213    /// A field value has a control byte other than HTAB.
214    InvalidHeaderValue,
215    /// Whitespace between a field name and the colon (RFC 9112 §5.1).
216    WhitespaceBeforeColon,
217    /// A line starting with whitespace: obs-fold, or whitespace after the start line.
218    ObsFold,
219    /// CR not followed by LF.
220    BareCr,
221    /// LF not preceded by CR.
222    BareLf,
223    /// An HTTP/1.1 request without `Host`.
224    MissingHost,
225    /// More than one `Host`.
226    DuplicateHost,
227    /// A `Host` value that is not `uri-host [":" port]`.
228    InvalidHost,
229    /// Both `Transfer-Encoding` and `Content-Length`.
230    TeWithContentLength,
231    /// More than one `Content-Length` value, equal or not.
232    DuplicateContentLength,
233    /// A `Content-Length` that is not 1*DIGIT within 64 bits.
234    InvalidContentLength,
235    /// A malformed `Transfer-Encoding` list (empty elements, non-token codings, parameters).
236    InvalidTransferEncoding,
237    /// `Transfer-Encoding` whose final coding is not `chunked`, or `chunked` twice.
238    ChunkedNotFinal,
239    /// Codings before the final `chunked` (they are not implemented).
240    UnsupportedTransferCoding,
241    /// `Transfer-Encoding` in an HTTP/1.0 request.
242    TransferEncodingHttp10,
243    /// The body passed [`Limits::max_body`].
244    BodyTooLarge,
245    /// An `Expect` value other than `100-continue`.
246    ExpectationFailed,
247    /// A duplicate `Upgrade` field.
248    DuplicateUpgrade,
249    /// A chunk size that is not 1*HEXDIG.
250    InvalidChunkSize,
251    /// A chunk size beyond 64 bits.
252    ChunkSizeOverflow,
253    /// A chunk extension that breaks the RFC 9112 §7.1.1 grammar.
254    InvalidChunkExtension,
255    /// A chunk-size line longer than [`Limits::max_chunk_line`].
256    ChunkLineTooLong,
257    /// Chunk data not followed by CRLF.
258    MissingChunkCrlf,
259    /// The trailer section passed [`Limits::max_trailers`].
260    TrailersTooLarge,
261    /// A trailer field that may not be a trailer (`Content-Length`, `Transfer-Encoding`,
262    /// `Host`).
263    ForbiddenTrailer,
264    /// The connection closed inside a body.
265    UnexpectedEof,
266    /// A status code that is not three digits from 100 to 599.
267    InvalidStatus,
268    /// A reason phrase with a control byte.
269    InvalidReason,
270    /// A writer was asked to emit `Content-Length` or `Transfer-Encoding` as a plain field;
271    /// use the framing methods instead.
272    FramingHeader,
273}
274
275impl Error {
276    /// The response status for this error, when it happens on a request.
277    pub fn status(self) -> u16 {
278        use Error::*;
279        match self {
280            RequestLineTooLong => 414,
281            HeadTooLarge | TooManyHeaders | HeaderLineTooLong => 431,
282            VersionNotSupported => 505,
283            UnsupportedTransferCoding => 501,
284            BodyTooLarge => 413,
285            ExpectationFailed => 417,
286            _ => 400,
287        }
288    }
289}
290
291impl std::fmt::Display for Error {
292    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293        std::fmt::Debug::fmt(self, f)
294    }
295}
296
297impl std::error::Error for Error {}