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 {}