use axum::http::{header, HeaderMap, HeaderValue, StatusCode};
use axum::response::{IntoResponse, Response};
pub const IMMUTABLE: &str = "public, max-age=31536000, immutable";
pub const REVALIDATE: &str = "public, no-cache";
pub const PRIVATE_NO_STORE: &str = "private, no-store";
const DIGEST_LEN: usize = 8;
pub fn digest(bytes: &[u8]) -> String {
blake3::hash(bytes).to_hex()[..DIGEST_LEN].to_string()
}
pub(crate) fn fingerprint(key: &str, digest: &str) -> String {
match key.rsplit_once('.') {
Some((stem, ext)) => format!("{stem}.{digest}.{ext}"),
None => format!("{key}.{digest}"),
}
}
pub(crate) fn strip_fingerprint(path: &str) -> (String, bool) {
let Some((rest, ext)) = path.rsplit_once('.') else {
return (path.to_string(), false);
};
match rest.rsplit_once('.') {
Some((stem, maybe_digest))
if maybe_digest.len() == DIGEST_LEN
&& maybe_digest.bytes().all(|b| b.is_ascii_hexdigit()) =>
{
(format!("{stem}.{ext}"), true)
}
_ => (path.to_string(), false),
}
}
pub fn matches_etag(headers: &HeaderMap, etag: &str) -> bool {
let Some(raw) = headers
.get(header::IF_NONE_MATCH)
.and_then(|v| v.to_str().ok())
else {
return false;
};
raw.split(',').any(|candidate| {
let candidate = candidate.trim().trim_start_matches("W/").trim_matches('"');
candidate == etag || candidate == "*"
})
}
pub fn conditional(
headers: &HeaderMap,
content_type: &str,
cache_control: &str,
body: Vec<u8>,
) -> Response {
let tag = digest(&body);
let quoted = format!("W/\"{tag}\"");
let etag = match HeaderValue::from_str("ed) {
Ok(v) => v,
Err(_) => return ([(header::CACHE_CONTROL, cache_control)], body).into_response(),
};
if matches_etag(headers, &tag) {
return (
StatusCode::NOT_MODIFIED,
[
(header::ETAG, etag),
(
header::CACHE_CONTROL,
HeaderValue::from_str(cache_control).unwrap(),
),
],
)
.into_response();
}
(
[
(
header::CONTENT_TYPE,
HeaderValue::from_str(content_type).unwrap(),
),
(header::ETAG, etag),
(
header::CACHE_CONTROL,
HeaderValue::from_str(cache_control).unwrap(),
),
],
body,
)
.into_response()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_digest_round_trips_through_a_url() {
let d = digest(b"body");
let url = fingerprint("laterite.css", &d);
assert_eq!(url, format!("laterite.{d}.css"));
assert_eq!(strip_fingerprint(&url), ("laterite.css".to_string(), true));
}
#[test]
fn a_nested_key_keeps_its_directory() {
let url = fingerprint("fields/ref-picker.js", "a1b2c3d4");
assert_eq!(url, "fields/ref-picker.a1b2c3d4.js");
assert_eq!(
strip_fingerprint(&url),
("fields/ref-picker.js".to_string(), true)
);
}
#[test]
fn an_unfingerprinted_path_is_reported_as_such() {
assert_eq!(
strip_fingerprint("laterite.css"),
("laterite.css".to_string(), false)
);
assert_eq!(
strip_fingerprint("mark.svg"),
("mark.svg".to_string(), false)
);
}
#[test]
fn a_hex_word_in_a_real_name_is_not_mistaken_for_a_digest() {
assert_eq!(
strip_fingerprint("vendor.deadbeefcafe.js"),
("vendor.deadbeefcafe.js".to_string(), false)
);
}
#[test]
fn if_none_match_recognises_the_tag_among_several() {
let mut headers = HeaderMap::new();
headers.insert(
header::IF_NONE_MATCH,
HeaderValue::from_static("\"aaaaaaaa\", W/\"a1b2c3d4\""),
);
assert!(matches_etag(&headers, "a1b2c3d4"));
assert!(!matches_etag(&headers, "ffffffff"));
}
#[test]
fn an_absent_header_never_matches() {
assert!(!matches_etag(&HeaderMap::new(), "a1b2c3d4"));
}
#[test]
fn an_unchanged_body_comes_back_bodyless() {
let body = b"h1 { color: red }".to_vec();
let tag = digest(&body);
let mut headers = HeaderMap::new();
headers.insert(
header::IF_NONE_MATCH,
HeaderValue::from_str(&format!("W/\"{tag}\"")).unwrap(),
);
let resp = conditional(&headers, "text/css", REVALIDATE, body);
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
}
#[test]
fn a_changed_body_comes_back_whole() {
let mut headers = HeaderMap::new();
headers.insert(
header::IF_NONE_MATCH,
HeaderValue::from_static("W/\"stale\""),
);
let resp = conditional(&headers, "text/css", REVALIDATE, b"new".to_vec());
assert_eq!(resp.status(), StatusCode::OK);
assert!(resp.headers().get(header::ETAG).is_some());
}
}