Skip to main content

netget/server/http3/
wire.rs

1//! Shared semantic HTTP fields and explicit memory bounds.
2use anyhow::{ensure, Context, Result};
3use http::{HeaderMap, HeaderName, HeaderValue};
4use serde_json::{Map, Value};
5pub const MAX_BODY: usize = 8 * 1024 * 1024;
6pub const MAX_HEADERS: usize = 32 * 1024;
7pub const MAX_INBOUND_BYTES: usize = MAX_BODY + 2 * MAX_HEADERS;
8fn header_size(headers: &HeaderMap) -> usize {
9    headers.iter().fold(0usize, |size, (key, value)| {
10        size.saturating_add(key.as_str().len() + value.as_bytes().len() + 32)
11    })
12}
13pub fn check_headers(headers: &HeaderMap) -> Result<()> {
14    check_headers_for(headers, false)
15}
16pub fn check_request_headers(headers: &HeaderMap) -> Result<()> {
17    check_headers_for(headers, true)
18}
19fn check_headers_for(headers: &HeaderMap, request: bool) -> Result<()> {
20    ensure!(
21        header_size(headers) <= MAX_HEADERS,
22        "HTTP3 field section exceeds 32 KiB"
23    );
24    for (k, value) in headers {
25        ensure!(
26            !matches!(
27                k.as_str(),
28                "connection" | "keep-alive" | "proxy-connection" | "transfer-encoding" | "upgrade"
29            ),
30            "HTTP3 forbids connection-specific headers"
31        );
32        if k == "te" {
33            ensure!(
34                request && value.to_str()?.trim().eq_ignore_ascii_case("trailers"),
35                "HTTP3 TE is allowed only in request headers with value trailers"
36            );
37        }
38    }
39    Ok(())
40}
41/// Outgoing field sections include pseudo-fields, even when the peer advertises
42/// a larger limit. h3 enforces the full decoded bound on incoming sections.
43pub fn check_field_section(headers: &HeaderMap, pseudo: &[(&str, &str)]) -> Result<()> {
44    check_headers_for(headers, pseudo.iter().any(|(key, _)| *key == ":method"))?;
45    let size = pseudo
46        .iter()
47        .fold(header_size(headers), |size, (key, value)| {
48            size.saturating_add(key.len() + value.len() + 32)
49        });
50    ensure!(size <= MAX_HEADERS, "HTTP3 field section exceeds 32 KiB");
51    Ok(())
52}
53pub fn parse_headers(value: &Value) -> Result<HeaderMap> {
54    parse_headers_for(value, false)
55}
56pub fn parse_request_headers(value: &Value) -> Result<HeaderMap> {
57    parse_headers_for(value, true)
58}
59fn parse_headers_for(value: &Value, request: bool) -> Result<HeaderMap> {
60    let mut headers = HeaderMap::new();
61    if value.is_null() {
62        return Ok(headers);
63    }
64    let object = value
65        .as_object()
66        .context("HTTP3 headers must be an object")?;
67    let mut size = 0usize;
68    for (key, value) in object {
69        let name = HeaderName::from_bytes(key.as_bytes())?;
70        let values: Vec<&str> = if let Some(s) = value.as_str() {
71            vec![s]
72        } else {
73            value
74                .as_array()
75                .context("Header must be a string or string array")?
76                .iter()
77                .map(|v| v.as_str().context("Header array must contain strings"))
78                .collect::<Result<_>>()?
79        };
80        for v in values {
81            size = size.saturating_add(key.len() + v.len() + 32);
82            ensure!(size <= MAX_HEADERS, "HTTP3 field section exceeds 32 KiB");
83            headers.append(name.clone(), HeaderValue::from_str(v)?);
84        }
85    }
86    check_headers_for(&headers, request)?;
87    Ok(headers)
88}
89pub fn header_json(headers: &HeaderMap) -> Result<Map<String, Value>> {
90    header_json_for(headers, false)
91}
92pub fn request_header_json(headers: &HeaderMap) -> Result<Map<String, Value>> {
93    header_json_for(headers, true)
94}
95fn header_json_for(headers: &HeaderMap, request: bool) -> Result<Map<String, Value>> {
96    check_headers_for(headers, request)?;
97    let mut object = Map::new();
98    for key in headers.keys() {
99        let values: Vec<_> = headers
100            .get_all(key)
101            .iter()
102            .map(|v| v.to_str().map(str::to_owned))
103            .collect::<std::result::Result<_, _>>()?;
104        object.insert(
105            key.to_string(),
106            if values.len() == 1 {
107                Value::String(values[0].clone())
108            } else {
109                serde_json::json!(values)
110            },
111        );
112    }
113    Ok(object)
114}
115pub fn validate_length(headers: &HeaderMap, actual: usize) -> Result<()> {
116    for value in headers.get_all("content-length") {
117        ensure!(
118            value.to_str()?.parse::<usize>()? == actual,
119            "HTTP3 content-length does not match body"
120        );
121    }
122    Ok(())
123}
124
125/// HTTP/3 uses H3_NO_ERROR (0x100), unlike raw QUIC/DoQ's application zero.
126/// Close first so the shared transport guards' subsequent Drop is harmless.
127pub struct EndpointOwner(pub crate::utils::quic::EndpointGuard);
128impl std::ops::Deref for EndpointOwner {
129    type Target = quinn::Endpoint;
130    fn deref(&self) -> &Self::Target {
131        &self.0 .0
132    }
133}
134impl Drop for EndpointOwner {
135    fn drop(&mut self) {
136        self.0 .0.close(0x100u32.into(), b"HTTP3 endpoint stopped");
137    }
138}
139pub struct ConnectionOwner(pub crate::utils::quic::ConnectionGuard);
140impl std::ops::Deref for ConnectionOwner {
141    type Target = quinn::Connection;
142    fn deref(&self) -> &Self::Target {
143        &self.0 .0
144    }
145}
146impl Drop for ConnectionOwner {
147    fn drop(&mut self) {
148        self.0
149             .0
150            .close(0x100u32.into(), b"HTTP3 connection stopped");
151    }
152}