hotaru_http 0.8.3

HTTP/1.1 implementation for the Hotaru web framework
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
use crate::message::http_value::{HttpContentType, HttpMethod};

/// Centralized HTTP safety configuration with explicit state tracking
///
/// Tracks whether each parameter has been explicitly set or should use its default value.
/// Provides granular control over HTTP validation with secure defaults.
///
/// # Security Architecture
///
/// ## Request Timeout Handling
/// Request timeouts are NOT implemented at the HttpSafety level. This is an architectural
/// decision for efficiency:
/// - Timeout is enforced once at the APP/connection layer when accepting connections
/// - Per-request timeouts would be redundant and waste resources
/// - Connection-level timeout protects against slowloris attacks effectively
/// - This design choice means: one timeout check per connection, not per HTTP operation
///
/// If per-request timeout is needed for specific use cases, it should be implemented
/// as middleware at the application layer, not in the core framework.
///
/// ## Core Security: Size Limits
/// The primary security mechanism is enforcing size limits with secure defaults:
/// - max_body_size: 10MB (prevents memory exhaustion attacks)
/// - max_header_size: 1MB (prevents header bomb attacks)
/// - max_line_length: 64KB (prevents single-line DoS)
/// - max_headers: 100 (prevents header count DoS)
///
/// Method and content-type filtering are intentionally permissive by default, as these
/// are application-level concerns, not framework security concerns.
#[derive(Debug, Clone)]
pub struct HttpSafety {
    /// Maximum request body size (None = use default)
    max_body_size: Option<usize>,

    /// Allowed HTTP methods (None = allow all methods)
    allowed_methods: Option<Vec<HttpMethod>>,

    /// Allowed content types (None = allow all content types)
    allowed_content_types: Option<Vec<HttpContentType>>,

    /// Maximum header section size (None = use default)
    max_header_size: Option<usize>,

    /// Maximum header line length (None = use default)
    max_line_length: Option<usize>,

    /// Maximum number of headers (None = use default)
    max_headers: Option<usize>,
}

// Default constants for safety parameters
const DEFAULT_MAX_BODY_SIZE: usize = 10 * 1024 * 1024; // 10 MB
const DEFAULT_MAX_HEADER_SIZE: usize = 1024 * 1024; // 1 MB
const DEFAULT_MAX_LINE_LENGTH: usize = 1024 * 64; // 64 KB
const DEFAULT_MAX_HEADERS: usize = 100; // 100 headers

impl HttpSafety {
    // --------------------------------------------------
    // Constructor and Defaults
    // --------------------------------------------------

    /// Creates a new `HttpSafety` instance with all parameters unset
    ///
    /// # Examples
    /// ```
    /// # use crate::safety::HttpSafety;
    /// let safety = HttpSafety::new();
    /// assert!(safety.max_body_size().is_none());
    /// ```
    pub fn new() -> Self {
        Self {
            max_body_size: None,
            allowed_methods: None,
            allowed_content_types: None,
            max_header_size: None,
            max_line_length: None,
            max_headers: None,
        }
    }

    /// Returns the effective body size limit (set value or default)
    fn effective_max_body_size(&self) -> usize {
        self.max_body_size.unwrap_or(DEFAULT_MAX_BODY_SIZE)
    }

    /// Returns the effective header size limit (set value or default)
    fn effective_max_header_size(&self) -> usize {
        self.max_header_size.unwrap_or(DEFAULT_MAX_HEADER_SIZE)
    }

    /// Returns the effective line length limit (set value or default)
    fn effective_max_line_length(&self) -> usize {
        self.max_line_length.unwrap_or(DEFAULT_MAX_LINE_LENGTH)
    }

    /// Returns the effective headers count limit (set value or default)
    fn effective_max_headers(&self) -> usize {
        self.max_headers.unwrap_or(DEFAULT_MAX_HEADERS)
    }

    // --------------------------------------------------
    // Body Size Configuration
    // --------------------------------------------------

    /// Gets the explicitly set body size limit (None if unset)
    pub fn max_body_size(&self) -> Option<usize> {
        self.max_body_size
    }

    /// Sets the body size limit explicitly
    pub fn set_max_body_size(&mut self, size: Option<usize>) {
        self.max_body_size = size;
    }

    /// Gets the effective body size limit (always returns a value)
    pub fn effective_body_size(&self) -> usize {
        self.effective_max_body_size()
    }

    /// Checks if a body size is within effective limits
    pub fn check_body_size(&self, size: usize) -> bool {
        size <= self.effective_max_body_size()
    }

    // --------------------------------------------------
    // Method Allow List Configuration
    // --------------------------------------------------

    /// Gets the allowed methods list (None if unset = allow all)
    pub fn allowed_methods(&self) -> Option<&[HttpMethod]> {
        self.allowed_methods.as_deref()
    }

    /// Sets the allowed methods list
    pub fn set_allowed_methods(&mut self, methods: Option<Vec<HttpMethod>>) {
        self.allowed_methods = methods;
    }

    /// Adds a method to the allow list
    pub fn add_method(&mut self, method: HttpMethod) {
        let methods = self.allowed_methods.get_or_insert_with(Vec::new);
        if !methods.contains(&method) {
            methods.push(method);
        }
    }

    /// Checks if a method is allowed
    ///
    /// # Security Note: Allow-all default is intentional
    /// The `None => true` case (allow all methods) is a deliberate design choice, NOT a security
    /// vulnerability. Real security comes from size limits (max_body_size, max_header_size),
    /// which are enforced by default (10MB body, 1MB headers).
    ///
    /// Method filtering is application-level business logic, not a framework security concern.
    /// Attackers don't need specific HTTP methods to cause damage - they focus on:
    /// - Sending huge request bodies 鈫?Protected by max_body_size
    /// - Sending huge headers 鈫?Protected by max_header_size
    /// - Slowloris attacks 鈫?Handled by connection timeout at APP layer
    ///
    /// Applications should explicitly set allowed_methods only when business logic requires
    /// restricting operations (e.g., read-only API allowing only GET), not for security.
    pub fn check_method(&self, method: &HttpMethod) -> bool {
        match &self.allowed_methods {
            Some(methods) => methods.contains(method),
            None => true, // No restrictions - allow all methods (see security note above)
        }
    }

    // --------------------------------------------------
    // Content Type Allow List Configuration
    // --------------------------------------------------

    /// Gets the allowed content types list (None if unset = allow all)
    pub fn allowed_content_types(&self) -> Option<&[HttpContentType]> {
        self.allowed_content_types.as_deref()
    }

    /// Sets the allowed content types list
    pub fn set_allowed_content_types(&mut self, types: Option<Vec<HttpContentType>>) {
        self.allowed_content_types = types;
    }

    /// Adds a content type to the allow list
    pub fn add_content_type(&mut self, content_type: HttpContentType) {
        let types = self.allowed_content_types.get_or_insert_with(Vec::new);
        if !types.contains(&content_type) {
            types.push(content_type);
        }
    }

    /// Checks if a content type is allowed
    ///
    /// # Security Note: Content-type filtering is application logic, not security
    /// Similar to method filtering, allowing all content types by default is safe because:
    /// 1. Content-Type header doesn't control actual payload content
    /// 2. Attackers can send any header value regardless of restrictions
    /// 3. Real validation happens during parsing (JSON, form data, etc.)
    /// 4. Size limits (max_body_size) prevent payload-based attacks
    ///
    /// Applications should use allowed_content_types for API contract enforcement
    /// (e.g., "this endpoint only accepts JSON"), not as a security mechanism.
    pub fn check_content_type(&self, content_type: &HttpContentType) -> bool {
        match &self.allowed_content_types {
            Some(types) => types.contains(content_type),
            None => true, // No restrictions - allow all content types (see security note above)
        }
    }

    // --------------------------------------------------
    // Header Size Configuration
    // --------------------------------------------------

    /// Gets the header size limit (None if unset)
    pub fn max_header_size(&self) -> Option<usize> {
        self.max_header_size
    }

    /// Sets the header size limit explicitly
    pub fn set_max_header_size(&mut self, size: Option<usize>) {
        self.max_header_size = size;
    }

    /// Gets the effective header size limit (always returns a value)
    pub fn effective_header_size(&self) -> usize {
        self.effective_max_header_size()
    }

    /// Checks if header size is within effective limits
    pub fn check_header_size(&self, size: usize) -> bool {
        size <= self.effective_max_header_size()
    }

    // --------------------------------------------------
    // Line Length Configuration
    // --------------------------------------------------

    /// Gets the line length limit (None if unset)
    pub fn max_line_length(&self) -> Option<usize> {
        self.max_line_length
    }

    /// Sets the line length limit explicitly
    pub fn set_max_line_length(&mut self, size: Option<usize>) {
        self.max_line_length = size;
    }

    /// Gets the effective line length limit (always returns a value)
    pub fn effective_line_length(&self) -> usize {
        self.effective_max_line_length()
    }

    /// Checks if line length is within effective limits
    pub fn check_line_length(&self, size: usize) -> bool {
        size <= self.effective_max_line_length()
    }

    // --------------------------------------------------
    // Header Count Configuration
    // --------------------------------------------------

    /// Gets the headers count limit (None if unset)
    pub fn max_headers(&self) -> Option<usize> {
        self.max_headers
    }

    /// Sets the headers count limit explicitly
    pub fn set_max_headers(&mut self, size: Option<usize>) {
        self.max_headers = size;
    }

    /// Gets the effective headers count limit (always returns a value)
    pub fn effective_headers_count(&self) -> usize {
        self.effective_max_headers()
    }

    /// Checks if headers count is within effective limits
    pub fn check_headers_count(&self, count: usize) -> bool {
        count <= self.effective_max_headers()
    }

    // --------------------------------------------------
    // Configuration Merging
    // --------------------------------------------------

    /// Updates explicitly set parameters from another configuration
    ///
    /// Only modifies parameters that are explicitly set in the source.
    /// Preserves unset parameters in the current configuration.
    ///
    /// # Examples
    /// ```
    /// # use crate::safety::HttpSafety;
    /// let mut base = HttpSafety::new();
    /// base.set_max_body_size(Some(1024));
    ///
    /// let mut target = HttpSafety::new();
    /// target.update(&base);
    ///
    /// assert_eq!(target.max_body_size(), Some(1024));
    /// ```
    pub fn update(&mut self, source: &HttpSafety) {
        // Update only explicitly set parameters
        if source.max_body_size.is_some() {
            self.max_body_size = source.max_body_size;
        }
        if source.allowed_methods.is_some() {
            self.allowed_methods = source.allowed_methods.clone();
        }
        if source.allowed_content_types.is_some() {
            self.allowed_content_types = source.allowed_content_types.clone();
        }
        if source.max_header_size.is_some() {
            self.max_header_size = source.max_header_size;
        }
        if source.max_line_length.is_some() {
            self.max_line_length = source.max_line_length;
        }
        if source.max_headers.is_some() {
            self.max_headers = source.max_headers;
        }
    }

    /// Merges another configuration using "most restrictive wins" policy
    ///
    /// # Merge Logic
    /// - **Size Limits**: Takes the minimum value (more restrictive)
    /// - **Allow Lists**: Takes the intersection of allowed values
    /// - **Unset Parameters**: Treated as using default values during merge
    ///
    /// # Examples
    /// ```
    /// # use crate::safety::HttpSafety;
    /// # use crate::http_value::HttpMethod;
    /// let mut global = HttpSafety::new();
    /// global.set_max_body_size(Some(2048));
    /// global.set_allowed_methods(Some(vec![HttpMethod::Get, HttpMethod::Post]));
    ///
    /// let mut route = HttpSafety::new();
    /// route.set_max_body_size(Some(1024));
    /// route.set_allowed_methods(Some(vec![HttpMethod::Post]));
    ///
    /// global.merge(&route);
    ///
    /// assert_eq!(global.max_body_size(), Some(1024));
    /// assert_eq!(
    ///     global.allowed_methods(),
    ///     Some(vec![HttpMethod::Post].as_slice())
    /// );
    /// ```
    pub fn merge(&mut self, other: &HttpSafety) {
        // Merge size limits: take the more restrictive (minimum) value
        self.max_body_size = Some(
            self.effective_max_body_size()
                .min(other.effective_max_body_size()),
        );

        self.max_header_size = Some(
            self.effective_max_header_size()
                .min(other.effective_max_header_size()),
        );

        self.max_line_length = Some(
            self.effective_max_line_length()
                .min(other.effective_max_line_length()),
        );

        self.max_headers = Some(
            self.effective_max_headers()
                .min(other.effective_max_headers()),
        );

        // Merge method allow lists
        self.allowed_methods = match (&self.allowed_methods, &other.allowed_methods) {
            (Some(a), Some(b)) => Some(a.iter().filter(|m| b.contains(m)).cloned().collect()),
            (Some(_), None) => self.allowed_methods.clone(),
            (None, Some(_)) => other.allowed_methods.clone(),
            (None, None) => None,
        };

        // Merge content type allow lists
        self.allowed_content_types =
            match (&self.allowed_content_types, &other.allowed_content_types) {
                (Some(a), Some(b)) => Some(a.iter().filter(|ct| b.contains(ct)).cloned().collect()),
                (Some(_), None) => self.allowed_content_types.clone(),
                (None, Some(_)) => other.allowed_content_types.clone(),
                (None, None) => None,
            };
    }

    // --------------------------------------------------
    // Builder Pattern Methods
    // --------------------------------------------------

    /// Builder method to set body size
    pub fn with_max_body_size(mut self, size: usize) -> Self {
        self.set_max_body_size(Some(size));
        self
    }

    /// Builder method to add a single allowed method
    pub fn with_allowed_method(mut self, method: HttpMethod) -> Self {
        self.add_method(method);
        self
    }

    /// Builder method to set method allow list
    pub fn with_allowed_methods(mut self, methods: Vec<HttpMethod>) -> Self {
        self.set_allowed_methods(Some(methods));
        self
    }

    /// Builder method to add a single allowed content type
    pub fn with_allowed_content_type(mut self, content_type: HttpContentType) -> Self {
        self.add_content_type(content_type);
        self
    }

    /// Builder method to set content type allow list
    pub fn with_allowed_content_types(mut self, types: Vec<HttpContentType>) -> Self {
        self.set_allowed_content_types(Some(types));
        self
    }

    /// Builder method to set header size
    pub fn with_max_header_size(mut self, size: usize) -> Self {
        self.set_max_header_size(Some(size));
        self
    }

    /// Builder method to set line length
    pub fn with_max_line_length(mut self, size: usize) -> Self {
        self.set_max_line_length(Some(size));
        self
    }

    /// Builder method to set headers count
    pub fn with_max_headers(mut self, size: usize) -> Self {
        self.set_max_headers(Some(size));
        self
    }
}

impl Default for HttpSafety {
    fn default() -> Self {
        Self::new()
    }
}

impl Default for &HttpSafety {
    fn default() -> Self {
        static DEFAULT_SAFETY: HttpSafety = HttpSafety {
            max_body_size: None,
            allowed_methods: None,
            allowed_content_types: None,
            max_header_size: None,
            max_line_length: None,
            max_headers: None,
        };
        &DEFAULT_SAFETY
    }
}