Skip to main content

http_net/
limits.rs

1//! Budgets that bound what a peer can make a connection buffer or accept.
2
3/// Upper bound for the start-line and header-section budgets. Keeps every
4/// buffer offset inside `u32` and every buffer allocation sane.
5const HARD_CAP: usize = 16 * 1024 * 1024;
6
7/// Bytes of buffer space, beyond the head budget, used to stream body data.
8const BODY_WINDOW: usize = 16 * 1024;
9
10/// The budgets a connection enforces on incoming messages.
11///
12/// Every limit is checked **before** the bytes it governs are buffered or
13/// allocated: a declared `Content-Length` above [`max_body`](Limits::max_body)
14/// is refused on the header line, a chunk size is checked against the
15/// remaining body budget before its data is read, and the connection's input
16/// buffer can never grow past `2 * (max_start_line + max_header_bytes)` plus a
17/// fixed 16 KiB body window (64 KiB with the defaults): room for a pinned
18/// head plus one partial chunk line or trailer section.
19///
20/// The defaults are the budgets of LexerSketch Studio's localhost server:
21///
22/// | Budget | Default |
23/// |---|---|
24/// | request line / status line / chunk-size line | 8 KiB |
25/// | header section (all field lines) | 16 KiB |
26/// | header fields (and, separately, trailer fields) | 100 |
27/// | body | 1 MiB |
28///
29/// # Examples
30///
31/// ```
32/// use http_net::Limits;
33///
34/// let limits = Limits::new().with_max_body(64 * 1024).with_max_headers(32);
35/// assert_eq!(limits.max_body(), 64 * 1024);
36/// assert_eq!(limits.max_headers(), 32);
37/// assert_eq!(limits.max_start_line(), 8 * 1024);
38/// ```
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
40pub struct Limits {
41    start_line: usize,
42    header_bytes: usize,
43    headers: usize,
44    body: u64,
45}
46
47impl Default for Limits {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53impl Limits {
54    /// The default budgets (see the table on [`Limits`]).
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// assert_eq!(http_net::Limits::new(), http_net::Limits::default());
60    /// ```
61    #[must_use]
62    pub const fn new() -> Self {
63        Self {
64            start_line: 8 * 1024,
65            header_bytes: 16 * 1024,
66            headers: 100,
67            body: 1024 * 1024,
68        }
69    }
70
71    /// Sets the longest accepted request line, status line, or chunk-size
72    /// line, in bytes including the CRLF. Clamped to `16..=16 MiB`.
73    ///
74    /// # Examples
75    ///
76    /// ```
77    /// let l = http_net::Limits::new().with_max_start_line(1);
78    /// assert_eq!(l.max_start_line(), 16);
79    /// ```
80    #[must_use]
81    pub const fn with_max_start_line(mut self, bytes: usize) -> Self {
82        self.start_line = clamp(bytes, 16, HARD_CAP);
83        self
84    }
85
86    /// Sets the largest accepted header section (every field line plus the
87    /// blank line that ends it), in bytes. Clamped to `2..=16 MiB`.
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// let l = http_net::Limits::new().with_max_header_bytes(4096);
93    /// assert_eq!(l.max_header_bytes(), 4096);
94    /// ```
95    #[must_use]
96    pub const fn with_max_header_bytes(mut self, bytes: usize) -> Self {
97        self.header_bytes = clamp(bytes, 2, HARD_CAP);
98        self
99    }
100
101    /// Sets the most header fields accepted in one message (trailer fields
102    /// are counted against the same number separately). Clamped to
103    /// `0..=65536`.
104    ///
105    /// # Examples
106    ///
107    /// ```
108    /// let l = http_net::Limits::new().with_max_headers(8);
109    /// assert_eq!(l.max_headers(), 8);
110    /// ```
111    #[must_use]
112    pub const fn with_max_headers(mut self, count: usize) -> Self {
113        self.headers = clamp(count, 0, 65_536);
114        self
115    }
116
117    /// Sets the largest accepted body, in bytes after chunked decoding.
118    ///
119    /// # Examples
120    ///
121    /// ```
122    /// let l = http_net::Limits::new().with_max_body(0);
123    /// assert_eq!(l.max_body(), 0);
124    /// ```
125    #[must_use]
126    pub const fn with_max_body(mut self, bytes: u64) -> Self {
127        self.body = bytes;
128        self
129    }
130
131    /// The longest accepted start line or chunk-size line, CRLF included.
132    ///
133    /// # Examples
134    ///
135    /// ```
136    /// assert_eq!(http_net::Limits::new().max_start_line(), 8192);
137    /// ```
138    #[must_use]
139    pub const fn max_start_line(&self) -> usize {
140        self.start_line
141    }
142
143    /// The largest accepted header section, in bytes.
144    ///
145    /// # Examples
146    ///
147    /// ```
148    /// assert_eq!(http_net::Limits::new().max_header_bytes(), 16384);
149    /// ```
150    #[must_use]
151    pub const fn max_header_bytes(&self) -> usize {
152        self.header_bytes
153    }
154
155    /// The most header fields accepted in one message.
156    ///
157    /// # Examples
158    ///
159    /// ```
160    /// assert_eq!(http_net::Limits::new().max_headers(), 100);
161    /// ```
162    #[must_use]
163    pub const fn max_headers(&self) -> usize {
164        self.headers
165    }
166
167    /// The largest accepted body, in bytes.
168    ///
169    /// # Examples
170    ///
171    /// ```
172    /// assert_eq!(http_net::Limits::new().max_body(), 1 << 20);
173    /// ```
174    #[must_use]
175    pub const fn max_body(&self) -> u64 {
176        self.body
177    }
178
179    /// The input buffer's hard capacity. The current message's head stays
180    /// pinned while its body streams, and the decoder may need one whole
181    /// chunk-size line or trailer section on top of it, so twice the head
182    /// budget plus the body window always fits whatever the parser is
183    /// waiting to complete.
184    pub(crate) const fn buffer_cap(&self) -> usize {
185        2 * (self.start_line + self.header_bytes) + BODY_WINDOW
186    }
187}
188
189const fn clamp(v: usize, lo: usize, hi: usize) -> usize {
190    if v < lo {
191        lo
192    } else if v > hi {
193        hi
194    } else {
195        v
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn test_limits_clamp_to_hard_bounds() {
205        let l = Limits::new()
206            .with_max_start_line(usize::MAX)
207            .with_max_header_bytes(0)
208            .with_max_headers(usize::MAX);
209        assert_eq!(l.max_start_line(), HARD_CAP);
210        assert_eq!(l.max_header_bytes(), 2);
211        assert_eq!(l.max_headers(), 65_536);
212        assert!(l.buffer_cap() <= u32::MAX as usize);
213    }
214}