use alloc::format;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use std::collections::HashMap;
use std::io::Write as _;
use std::sync::{OnceLock, RwLock};
use ::axum::Router;
use ::axum::body::Body;
use ::axum::extract::Path;
use ::axum::response::{IntoResponse, Response};
use ::axum::routing::get;
use ::http::header::{
ACCEPT_ENCODING, CACHE_CONTROL, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE, HeaderMap,
LOCATION, VARY,
};
use ::http::{HeaderValue, StatusCode};
pub const CATALOG_PREFIX: &str = crate::links::CATALOG_ROUTE;
const CATALOG_TYPE: HeaderValue = HeaderValue::from_static("application/octet-stream");
const IMMUTABLE: HeaderValue = HeaderValue::from_static("public, max-age=31536000, immutable");
const NO_CACHE: HeaderValue = HeaderValue::from_static("no-cache");
const VARY_ENCODING: HeaderValue = HeaderValue::from_static("accept-encoding");
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Encoding {
Brotli,
Gzip,
Identity,
}
impl Encoding {
fn header(self) -> Option<HeaderValue> {
match self {
Encoding::Brotli => Some(HeaderValue::from_static("br")),
Encoding::Gzip => Some(HeaderValue::from_static("gzip")),
Encoding::Identity => None,
}
}
}
fn best_encoding(headers: &HeaderMap) -> Encoding {
let Some(accept) = headers.get(ACCEPT_ENCODING).and_then(|v| v.to_str().ok()) else {
return Encoding::Identity;
};
let offers = |name: &str| {
accept.split(',').any(|item| {
let item = item.trim();
let token = item.split(';').next().unwrap_or(item).trim();
token.eq_ignore_ascii_case(name) && !item.contains("q=0,") && !item.ends_with("q=0")
})
};
if offers("br") {
Encoding::Brotli
} else if offers("gzip") {
Encoding::Gzip
} else {
Encoding::Identity
}
}
#[derive(Default)]
struct Variants {
brotli: OnceLock<Option<Arc<[u8]>>>,
gzip: OnceLock<Option<Arc<[u8]>>>,
}
fn variants() -> &'static RwLock<HashMap<&'static str, Arc<Variants>>> {
static VARIANTS: OnceLock<RwLock<HashMap<&'static str, Arc<Variants>>>> = OnceLock::new();
VARIANTS.get_or_init(|| RwLock::new(HashMap::new()))
}
fn variants_of(file: &'static str) -> Option<Arc<Variants>> {
if let Ok(map) = variants().read()
&& let Some(found) = map.get(file)
{
return Some(Arc::clone(found));
}
let mut map = variants().write().ok()?;
Some(Arc::clone(
map.entry(file)
.or_insert_with(|| Arc::new(Variants::default())),
))
}
fn brotli(bytes: &[u8]) -> Option<Arc<[u8]>> {
let mut out = Vec::new();
let mut writer = brotli::CompressorWriter::new(&mut out, 4096, 11, 22);
writer.write_all(bytes).ok()?;
drop(writer);
Some(Arc::from(out))
}
fn gzip(bytes: &[u8]) -> Option<Arc<[u8]>> {
let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::best());
encoder.write_all(bytes).ok()?;
encoder.finish().ok().map(Arc::from)
}
fn body_for(
file: &'static str,
bytes: &'static [u8],
encoding: Encoding,
) -> (Body, Encoding, usize) {
let compressed = match encoding {
Encoding::Identity => None,
other => variants_of(file).and_then(|v| {
let slot = match other {
Encoding::Brotli => &v.brotli,
_ => &v.gzip,
};
slot.get_or_init(|| match other {
Encoding::Brotli => brotli(bytes),
_ => gzip(bytes),
})
.clone()
}),
};
match compressed {
Some(data) => {
let len = data.len();
(Body::from(data.to_vec()), encoding, len)
}
None => (Body::from(bytes), Encoding::Identity, bytes.len()),
}
}
async fn serve(Path(name): Path<String>, headers: HeaderMap) -> Response {
if let Some((file, bytes)) = catalog_file(&name) {
let encoding = best_encoding(&headers);
let (body, encoding, length) = body_for(file, bytes, encoding);
let mut response = Response::new(body);
let out = response.headers_mut();
out.insert(CONTENT_TYPE, CATALOG_TYPE);
out.insert(CACHE_CONTROL, IMMUTABLE);
out.insert(VARY, VARY_ENCODING);
if let Ok(value) = HeaderValue::from_str(&length.to_string()) {
out.insert(CONTENT_LENGTH, value);
}
if let Some(value) = encoding.header() {
out.insert(CONTENT_ENCODING, value);
}
return response;
}
if let Some(file) = super::catalog_name(&name) {
let target = format!("{CATALOG_PREFIX}{file}");
let mut response = Response::new(Body::empty());
*response.status_mut() = StatusCode::TEMPORARY_REDIRECT;
let out = response.headers_mut();
if let Ok(value) = HeaderValue::from_str(&target) {
out.insert(LOCATION, value);
}
out.insert(CACHE_CONTROL, NO_CACHE);
return response;
}
(StatusCode::NOT_FOUND, "no such catalog").into_response()
}
fn catalog_file(name: &str) -> Option<(&'static str, &'static [u8])> {
super::catalog_file(name)
}
pub fn catalog_routes<S>() -> Router<S>
where
S: Clone + Send + Sync + 'static,
{
Router::new().route(&format!("{CATALOG_PREFIX}{{name}}"), get(serve))
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::indexing_slicing, reason = "a test")]
mod tests {
use super::{Encoding, best_encoding};
use ::http::HeaderMap;
use ::http::header::ACCEPT_ENCODING;
fn accepting(value: &str) -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert(ACCEPT_ENCODING, value.parse().expect("a header value"));
headers
}
#[test]
fn brotli_wins_when_it_is_offered_at_all() {
assert_eq!(
best_encoding(&accepting("gzip, deflate, br")),
Encoding::Brotli
);
assert_eq!(best_encoding(&accepting("gzip, deflate")), Encoding::Gzip);
assert_eq!(best_encoding(&HeaderMap::new()), Encoding::Identity);
}
#[test]
fn an_encoding_refused_with_q_0_is_not_used() {
assert_eq!(best_encoding(&accepting("br;q=0, gzip")), Encoding::Gzip);
}
}