use bytes::Bytes;
use hyper::header::{self, HeaderMap, HeaderName, HeaderValue};
use std::sync::Arc;
const STATIC_SECURITY_HEADERS: [(HeaderName, &str); 6] = [
(header::X_CONTENT_TYPE_OPTIONS, "nosniff"),
(header::X_FRAME_OPTIONS, "SAMEORIGIN"),
(header::REFERRER_POLICY, "strict-origin-when-cross-origin"),
(
header::STRICT_TRANSPORT_SECURITY,
"max-age=63072000; includeSubDomains",
),
(
HeaderName::from_static("permissions-policy"),
"camera=(), microphone=(), geolocation=()",
),
(HeaderName::from_static("x-dns-prefetch-control"), "off"),
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Encoding {
Identity,
Gzip,
Brotli,
Zstd,
}
impl Encoding {
pub const ALL: [Self; 4] = [Self::Identity, Self::Gzip, Self::Brotli, Self::Zstd];
pub fn from_accept_encoding(accept: &str) -> Self {
let mut best = Self::Identity;
for part in accept.split(',') {
let mut segments = part.split(';');
let token = segments.next().unwrap_or("").trim();
let rejected = segments.any(|s| {
s.trim()
.strip_prefix("q=")
.and_then(|v| v.trim().parse::<f32>().ok())
.is_some_and(|q| q == 0.0)
});
if rejected {
continue;
}
match token {
"br" => return Self::Brotli, "zstd" => best = Self::Zstd,
"gzip" if !matches!(best, Self::Zstd) => best = Self::Gzip,
_ => {}
}
}
best
}
}
#[derive(Debug, Clone)]
pub struct ResponseBuffer {
pub body: Bytes,
pub content_type: Arc<str>,
pub content_encoding: Option<&'static str>,
pub etag: Arc<str>,
pub last_modified: Arc<str>,
pub cache_control: Arc<str>,
pub content_length: Arc<str>,
pub vary_encoding: bool,
pub headers: HeaderMap,
}
impl ResponseBuffer {
pub fn new(
body: Bytes,
content_type: Arc<str>,
content_encoding: Option<&'static str>,
etag: Arc<str>,
last_modified: Arc<str>,
cache_control: Arc<str>,
vary_encoding: bool,
) -> Self {
let content_length: Arc<str> = Arc::from(body.len().to_string().as_str());
let headers = build_headers(
&content_type,
content_encoding,
&etag,
&last_modified,
&cache_control,
&content_length,
vary_encoding,
);
Self {
body,
content_type,
content_encoding,
etag,
last_modified,
cache_control,
content_length,
vary_encoding,
headers,
}
}
}
fn build_headers(
content_type: &str,
content_encoding: Option<&'static str>,
etag: &str,
last_modified: &str,
cache_control: &str,
content_length: &str,
vary_encoding: bool,
) -> HeaderMap {
let mut h = HeaderMap::with_capacity(13);
let val = |s: &str| HeaderValue::from_str(s).expect("server-controlled header value");
h.insert(header::CONTENT_TYPE, val(content_type));
h.insert(header::ETAG, val(etag));
h.insert(header::LAST_MODIFIED, val(last_modified));
h.insert(header::CACHE_CONTROL, val(cache_control));
h.insert(header::CONTENT_LENGTH, val(content_length));
if let Some(encoding) = content_encoding {
h.insert(header::CONTENT_ENCODING, HeaderValue::from_static(encoding));
}
if vary_encoding {
h.insert(header::VARY, HeaderValue::from_static("Accept-Encoding"));
}
for (name, value) in STATIC_SECURITY_HEADERS {
h.insert(name, HeaderValue::from_static(value));
}
h
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encoding_priority() {
assert_eq!(
Encoding::from_accept_encoding("gzip, br, zstd"),
Encoding::Brotli
);
assert_eq!(Encoding::from_accept_encoding("br"), Encoding::Brotli);
assert_eq!(Encoding::from_accept_encoding("gzip, zstd"), Encoding::Zstd);
assert_eq!(Encoding::from_accept_encoding("zstd"), Encoding::Zstd);
assert_eq!(Encoding::from_accept_encoding("gzip"), Encoding::Gzip);
assert_eq!(
Encoding::from_accept_encoding("deflate"),
Encoding::Identity
);
assert_eq!(Encoding::from_accept_encoding(""), Encoding::Identity);
}
#[test]
fn test_encoding_no_substring_false_positives() {
assert_eq!(
Encoding::from_accept_encoding("vibrant"),
Encoding::Identity
);
assert_eq!(Encoding::from_accept_encoding("broken"), Encoding::Identity);
}
#[test]
fn test_encoding_with_quality_values() {
assert_eq!(
Encoding::from_accept_encoding("gzip;q=1.0, br;q=0.8"),
Encoding::Brotli
);
assert_eq!(
Encoding::from_accept_encoding("gzip;q=0.5, zstd;q=1.0"),
Encoding::Zstd
);
}
#[test]
fn test_encoding_respects_q_zero() {
assert_eq!(
Encoding::from_accept_encoding("br;q=0, gzip"),
Encoding::Gzip
);
assert_eq!(
Encoding::from_accept_encoding("br;q=0, zstd;q=0, gzip"),
Encoding::Gzip
);
assert_eq!(
Encoding::from_accept_encoding("br;q=0, zstd;q=0, gzip;q=0"),
Encoding::Identity
);
assert_eq!(
Encoding::from_accept_encoding("gzip;q=0"),
Encoding::Identity
);
}
}