use anyhow::{ensure, Context, Result};
use http::{HeaderMap, HeaderName, HeaderValue};
use serde_json::{Map, Value};
pub const MAX_BODY: usize = 8 * 1024 * 1024;
pub const MAX_HEADERS: usize = 32 * 1024;
pub const MAX_INBOUND_BYTES: usize = MAX_BODY + 2 * MAX_HEADERS;
fn header_size(headers: &HeaderMap) -> usize {
headers.iter().fold(0usize, |size, (key, value)| {
size.saturating_add(key.as_str().len() + value.as_bytes().len() + 32)
})
}
pub fn check_headers(headers: &HeaderMap) -> Result<()> {
check_headers_for(headers, false)
}
pub fn check_request_headers(headers: &HeaderMap) -> Result<()> {
check_headers_for(headers, true)
}
fn check_headers_for(headers: &HeaderMap, request: bool) -> Result<()> {
ensure!(
header_size(headers) <= MAX_HEADERS,
"HTTP3 field section exceeds 32 KiB"
);
for (k, value) in headers {
ensure!(
!matches!(
k.as_str(),
"connection" | "keep-alive" | "proxy-connection" | "transfer-encoding" | "upgrade"
),
"HTTP3 forbids connection-specific headers"
);
if k == "te" {
ensure!(
request && value.to_str()?.trim().eq_ignore_ascii_case("trailers"),
"HTTP3 TE is allowed only in request headers with value trailers"
);
}
}
Ok(())
}
pub fn check_field_section(headers: &HeaderMap, pseudo: &[(&str, &str)]) -> Result<()> {
check_headers_for(headers, pseudo.iter().any(|(key, _)| *key == ":method"))?;
let size = pseudo
.iter()
.fold(header_size(headers), |size, (key, value)| {
size.saturating_add(key.len() + value.len() + 32)
});
ensure!(size <= MAX_HEADERS, "HTTP3 field section exceeds 32 KiB");
Ok(())
}
pub fn parse_headers(value: &Value) -> Result<HeaderMap> {
parse_headers_for(value, false)
}
pub fn parse_request_headers(value: &Value) -> Result<HeaderMap> {
parse_headers_for(value, true)
}
fn parse_headers_for(value: &Value, request: bool) -> Result<HeaderMap> {
let mut headers = HeaderMap::new();
if value.is_null() {
return Ok(headers);
}
let object = value
.as_object()
.context("HTTP3 headers must be an object")?;
let mut size = 0usize;
for (key, value) in object {
let name = HeaderName::from_bytes(key.as_bytes())?;
let values: Vec<&str> = if let Some(s) = value.as_str() {
vec![s]
} else {
value
.as_array()
.context("Header must be a string or string array")?
.iter()
.map(|v| v.as_str().context("Header array must contain strings"))
.collect::<Result<_>>()?
};
for v in values {
size = size.saturating_add(key.len() + v.len() + 32);
ensure!(size <= MAX_HEADERS, "HTTP3 field section exceeds 32 KiB");
headers.append(name.clone(), HeaderValue::from_str(v)?);
}
}
check_headers_for(&headers, request)?;
Ok(headers)
}
pub fn header_json(headers: &HeaderMap) -> Result<Map<String, Value>> {
header_json_for(headers, false)
}
pub fn request_header_json(headers: &HeaderMap) -> Result<Map<String, Value>> {
header_json_for(headers, true)
}
fn header_json_for(headers: &HeaderMap, request: bool) -> Result<Map<String, Value>> {
check_headers_for(headers, request)?;
let mut object = Map::new();
for key in headers.keys() {
let values: Vec<_> = headers
.get_all(key)
.iter()
.map(|v| v.to_str().map(str::to_owned))
.collect::<std::result::Result<_, _>>()?;
object.insert(
key.to_string(),
if values.len() == 1 {
Value::String(values[0].clone())
} else {
serde_json::json!(values)
},
);
}
Ok(object)
}
pub fn validate_length(headers: &HeaderMap, actual: usize) -> Result<()> {
for value in headers.get_all("content-length") {
ensure!(
value.to_str()?.parse::<usize>()? == actual,
"HTTP3 content-length does not match body"
);
}
Ok(())
}
pub struct EndpointOwner(pub crate::utils::quic::EndpointGuard);
impl std::ops::Deref for EndpointOwner {
type Target = quinn::Endpoint;
fn deref(&self) -> &Self::Target {
&self.0 .0
}
}
impl Drop for EndpointOwner {
fn drop(&mut self) {
self.0 .0.close(0x100u32.into(), b"HTTP3 endpoint stopped");
}
}
pub struct ConnectionOwner(pub crate::utils::quic::ConnectionGuard);
impl std::ops::Deref for ConnectionOwner {
type Target = quinn::Connection;
fn deref(&self) -> &Self::Target {
&self.0 .0
}
}
impl Drop for ConnectionOwner {
fn drop(&mut self) {
self.0
.0
.close(0x100u32.into(), b"HTTP3 connection stopped");
}
}