use crate::handler::{HeaderSelector, HeadersAndCompression};
use crate::http::headers::{
Line, ALLOW, CACHE_CONTROL, COEP, CONTENT_LENGTH, CONTENT_TYPE, COOP, CORP, CSP, HSTS,
SERVICE_WORKER_ALLOWED, X_CONTENT_TYPE_OPTIONS, X_FRAME_OPTIONS, X_XSS_PROTECTION,
};
use std::sync::LazyLock;
pub static DEFAULT_HEADERS: LazyLock<Vec<Line>> = LazyLock::new(|| {
let headers = vec![
(ALLOW, b"GET, HEAD".as_slice()).into(),
(X_CONTENT_TYPE_OPTIONS, b"nosniff".as_slice()).into(),
(X_FRAME_OPTIONS, b"DENY".as_slice()).into(),
(X_XSS_PROTECTION, b"1; mode=block".as_slice()).into(),
(CORP, b"same-site".as_slice()).into(),
(COEP, b"crendentialless".as_slice()).into(),
(COOP, b"same-origin".as_slice()).into(),
(CSP,
b"\
default-src 'self';\
connect-src 'self' https:;\
script-src 'wasm-unsafe-eval';\
script-src-elem 'self' 'unsafe-inline';\
script-src-attr 'none';\
worker-src 'self' blob:;\
style-src 'self' 'unsafe-inline';\
img-src 'self' data: blob:;\
font-src 'self' data:;\
frame-src 'none';\
object-src 'none';\
base-uri 'none';\
frame-ancestors 'none';\
form-action 'none'\
".as_slice()).into(),
(HSTS, b"max-age=63072000; includeSubDomains; preload".as_slice()).into(),
];
headers
});
pub static ERROR_HEADERS: LazyLock<Vec<Line>> = LazyLock::new(|| {
let headers = vec![
(ALLOW, b"GET, HEAD".as_slice()).into(),
(CONTENT_LENGTH, b"0".as_slice()).into(),
];
headers
});
const CACHE_CONTROL_NO_CACHE: &[u8] = b"public,no-cache,max-age=0,stale-if-error=3600";
const CACHE_CONTROL_REVALIDATE: &[u8] = b"public,max-age=3600,stale-if-error=3600";
const CACHE_CONTROL_DEFAULT: &[u8] =
b"public,max-age=72000,stale-while-revalidate=28800,stale-if-error=3600";
const CACHE_CONTROL_IMMUTABLE: &[u8] =
b"public,max-age=86400,immutable,stale-while-revalidate=864000,stale-if-error=3600";
pub(crate) fn default_headers() -> impl Iterator<Item = &'static Line> {
DEFAULT_HEADERS.iter()
}
pub(crate) fn default_error_headers() -> &'static [Line] {
ERROR_HEADERS.as_slice()
}
pub(crate) fn headers_for_type(filename: &str, extension: &str) -> Option<HeadersAndCompression> {
match extension {
"html" | "htm" => Some(headers_and_compression(
Some(b"text/html"),
Some(CACHE_CONTROL_REVALIDATE),
true,
)),
"css" => Some(headers_and_compression(
Some(b"text/css"),
Some(CACHE_CONTROL_REVALIDATE),
true,
)),
"js" | "mjs" | "map" => Some(
if filename.starts_with("service-worker.") || filename.starts_with("sw.") {
let mut headers_and_compression = headers_and_compression(
Some(b"application/javascript"),
Some(CACHE_CONTROL_NO_CACHE),
true,
);
headers_and_compression
.headers
.push(Line::with_array_ref_value(SERVICE_WORKER_ALLOWED, b"/"));
headers_and_compression
} else {
headers_and_compression(
Some(b"application/javascript"),
Some(CACHE_CONTROL_REVALIDATE),
true,
)
},
),
"json" => Some(
if filename.starts_with("manifest.") || filename.ends_with(".manifest") {
headers_and_compression(
Some(b"application/manifest+json"),
Some(CACHE_CONTROL_DEFAULT),
true,
)
} else if filename.ends_with(".ld") {
headers_and_compression(
Some(b"application/ld+json"),
Some(CACHE_CONTROL_DEFAULT),
true,
)
} else if filename.ends_with(".schema") {
headers_and_compression(
Some(b"application/schema+json"),
Some(CACHE_CONTROL_DEFAULT),
true,
)
} else {
headers_and_compression(
Some(b"application/json"),
Some(CACHE_CONTROL_REVALIDATE),
true,
)
},
),
"xml" => Some(
if filename.starts_with("atom.") || filename.ends_with(".atom") {
headers_and_compression(
Some(b"application/atom+xml"),
Some(CACHE_CONTROL_DEFAULT),
true,
)
} else if filename.ends_with(".dtd") {
headers_and_compression(
Some(b"application/xnl-dtd"),
Some(CACHE_CONTROL_DEFAULT),
true,
)
} else {
headers_and_compression(
Some(b"application/xml"),
Some(CACHE_CONTROL_REVALIDATE),
true,
)
},
),
"ldjson" => Some(headers_and_compression(
Some(b"application/ld+json"),
Some(CACHE_CONTROL_DEFAULT),
true,
)),
"txt" => Some(headers_and_compression(
Some(b"text/plain"),
Some(CACHE_CONTROL_REVALIDATE),
true,
)),
"csv" => Some(headers_and_compression(
Some(b"text/csv"),
Some(CACHE_CONTROL_REVALIDATE),
true,
)),
"md" => Some(headers_and_compression(
Some(b"text/markdown"),
Some(CACHE_CONTROL_REVALIDATE),
true,
)),
"wasm" => Some(headers_and_compression(
Some(b"application/wasm"),
Some(CACHE_CONTROL_REVALIDATE),
true,
)),
"woff2" => Some(headers_and_compression(
Some(b"font/woff2"),
Some(CACHE_CONTROL_REVALIDATE),
false,
)),
"ico" => Some(headers_and_compression(
Some(b"image/x-icon"),
Some(CACHE_CONTROL_IMMUTABLE),
true,
)),
"webp" => Some(headers_and_compression(
Some(b"image/webp"),
Some(CACHE_CONTROL_IMMUTABLE),
false,
)),
"avif" => Some(headers_and_compression(
Some(b"image/avif"),
Some(CACHE_CONTROL_IMMUTABLE),
false,
)),
"gif" => Some(headers_and_compression(
Some(b"image/gif"),
Some(CACHE_CONTROL_IMMUTABLE),
false,
)),
"heif" => Some(headers_and_compression(
Some(b"image/heif"),
Some(CACHE_CONTROL_IMMUTABLE),
false,
)),
"heic" => Some(headers_and_compression(
Some(b"image/heic"),
Some(CACHE_CONTROL_IMMUTABLE),
false,
)),
"png" => Some(headers_and_compression(
Some(b"image/png"),
Some(CACHE_CONTROL_IMMUTABLE),
false,
)),
"jpg" => Some(headers_and_compression(
Some(b"image/jpeg"),
Some(CACHE_CONTROL_IMMUTABLE),
false,
)),
"jpeg" => Some(headers_and_compression(
Some(b"image/jpeg"),
Some(CACHE_CONTROL_IMMUTABLE),
false,
)),
"aac" => Some(headers_and_compression(
Some(b"audio/aac"),
Some(CACHE_CONTROL_REVALIDATE),
false,
)),
"mp3" => Some(headers_and_compression(
Some(b"audio/mp3"),
Some(CACHE_CONTROL_REVALIDATE),
false,
)),
"flac" => Some(headers_and_compression(
Some(b"audio/flac"),
Some(CACHE_CONTROL_REVALIDATE),
false,
)),
"webm" => Some(headers_and_compression(
Some(b"audio/webm"),
Some(CACHE_CONTROL_REVALIDATE),
false,
)),
"mp4" => Some(headers_and_compression(
Some(b"video/mp4"),
Some(CACHE_CONTROL_REVALIDATE),
false,
)),
"svg" => Some(headers_and_compression(
Some(b"image/svg+xml"),
Some(CACHE_CONTROL_IMMUTABLE),
true,
)),
"pdf" => Some(headers_and_compression(
Some(b"application/pdf"),
Some(CACHE_CONTROL_REVALIDATE),
true,
)),
"gpx" => Some(headers_and_compression(
Some(b"application/gpx+xml"),
Some(CACHE_CONTROL_DEFAULT),
true,
)),
"kml" => Some(headers_and_compression(
Some(b"application/vnd.google-earth.kml+xml"),
Some(CACHE_CONTROL_DEFAULT),
true,
)),
"geojson" => Some(headers_and_compression(
Some(b"application/geo+json"),
Some(CACHE_CONTROL_DEFAULT),
true,
)),
"glb" => Some(headers_and_compression(
Some(b"model/gltf-binary"),
Some(CACHE_CONTROL_DEFAULT),
true,
)),
"zip" => Some(headers_and_compression(
Some(b"application/zip"),
Some(CACHE_CONTROL_REVALIDATE),
false,
)),
"bin" => Some(headers_and_compression(
Some(b"application/octet-stream"),
Some(CACHE_CONTROL_REVALIDATE),
true,
)),
"307" => Some(headers_and_compression(
None,
Some(CACHE_CONTROL_REVALIDATE),
false,
)),
"308" => Some(headers_and_compression(None, None, false)),
_ => None,
}
}
fn headers_and_compression(
content_type: Option<&'static [u8]>,
cache_control: Option<&'static [u8]>,
compressible: bool,
) -> HeadersAndCompression {
let default_headers = default_headers();
let mut new_headers = vec![];
if let Some(content_type) = content_type {
new_headers.push(Line::with_slice_value(CONTENT_TYPE, content_type));
}
if let Some(cache_control) = cache_control {
new_headers.push(Line::with_slice_value(CACHE_CONTROL, cache_control));
}
HeadersAndCompression {
headers: if new_headers.is_empty() {
default_headers.cloned().collect()
} else {
default_headers.cloned().chain(new_headers).collect()
},
compressible,
redirection: content_type.is_none(),
}
}
pub(crate) struct DefaultHeaderSelector;
impl HeaderSelector for DefaultHeaderSelector {
fn headers_for_extension(
&self,
filename: &str,
extension: &str,
) -> Option<HeadersAndCompression> {
headers_for_type(filename, extension)
}
fn error_headers(&self) -> &'static [Line] {
default_error_headers()
}
}