use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::BTreeMap;
use holger_errcode as ec;
pub const PAGE: &str = include_str!("../assets/front.html");
pub const PICTURE: &[u8] = include_bytes!("../assets/holger.webp");
pub const PICTURE_PATH: &str = "/holger.webp";
pub const VETRA_MARK: &[u8] = include_bytes!("../assets/vetra.svg");
pub const VETRA_PATH: &str = "/vetra.svg";
pub const IGNALINA_MARK: &[u8] = include_bytes!("../assets/ignalina.png");
pub const IGNALINA_PATH: &str = "/ignalina.png";
pub const FRONT_PATH: &str = "/-/front";
pub const RELEASES_PATH: &str = "/-/releases";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Release {
pub repository: String,
pub artifact: String,
pub version: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Repository {
pub name: String,
pub format: String,
pub packages: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct Catalogue {
pub repositories: Vec<Repository>,
pub releases: Vec<Release>,
}
pub fn artifact_name(namespace: Option<&str>, name: &str) -> String {
match namespace {
Some(ns) if !ns.is_empty() => format!("{ns}/{name}"),
_ => name.to_string(),
}
}
pub fn latest_by<F>(rows: impl IntoIterator<Item = Release>, newer: F) -> Vec<Release>
where
F: Fn(&str, &str) -> Ordering,
{
let mut best: BTreeMap<(String, String), String> = BTreeMap::new();
for r in rows {
let key = (r.repository, r.artifact);
match best.get(&key) {
Some(have) if newer(&r.version, have) != Ordering::Greater => {}
_ => {
best.insert(key, r.version);
}
}
}
best.into_iter()
.map(|((repository, artifact), version)| Release { repository, artifact, version })
.collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Door {
#[default]
Link,
Here,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Login {
pub label: String,
pub start: String,
#[serde(default)]
pub door: Door,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub finish: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub next: Option<String>,
}
pub const CONSOLE_LABEL: &str = "Open the console";
pub const PASSKEY_LABEL: &str = "Log in with passkey";
pub fn console_login(path: &str) -> Option<Login> {
if !same_site_rooted_path(path) {
return None;
}
Some(Login {
label: CONSOLE_LABEL.to_string(),
start: path.to_string(),
door: Door::Link,
finish: None,
next: None,
})
}
pub fn passkey_login_here(start: &str, finish: &str, next: &str) -> Option<Login> {
if !same_site_rooted_path(start)
|| !same_site_rooted_path(finish)
|| !same_site_rooted_path(next)
{
return None;
}
Some(Login {
label: PASSKEY_LABEL.to_string(),
start: start.to_string(),
door: Door::Here,
finish: Some(finish.to_string()),
next: Some(next.to_string()),
})
}
fn same_site_rooted_path(path: &str) -> bool {
!path.is_empty()
&& path.starts_with('/')
&& !path.starts_with("//")
&& !path.starts_with("/\\")
&& !path.chars().any(|c| (c as u32) <= 0x20 || c as u32 == 0x7f)
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Front {
#[serde(skip_serializing_if = "Option::is_none")]
pub base_url: Option<String>,
pub login: Option<Login>,
}
impl Front {
pub fn anonymous(base_url: Option<String>) -> Self {
Front { base_url, login: None }
}
pub fn no_login_refusal() -> String {
format!(
"{}: this server offers no browser login. Its doors are mTLS, OIDC and bearer — \
for cargo, pip, docker and the console, not for a tab.",
ec::FRONT_NO_BROWSER_LOGIN.code
)
}
}
pub fn page_refusals() -> [(&'static ec::ErrCode, String); 3] {
[
(&ec::FRONT_NO_BROWSER_LOGIN, Front::no_login_refusal()),
(
&ec::FRONT_READS_GATED,
format!(
"{}: this server gates reads behind a credential, so its front page cannot name \
itself. Configure `require_auth_for_reads: false`, or read the console instead.",
ec::FRONT_READS_GATED.code
),
),
(
&ec::FRONT_DOOR_ABSENT,
format!(
"{}: this build of holger-server has no `/-/front` door. The page is newer than \
the server behind it.",
ec::FRONT_DOOR_ABSENT.code
),
),
]
}
#[derive(Debug, Clone, PartialEq)]
pub struct Reply {
pub status: u16,
pub content_type: &'static str,
pub cache_control: &'static str,
pub body: Vec<u8>,
}
impl Reply {
fn html(body: &str) -> Reply {
Reply {
status: 200,
content_type: "text/html; charset=utf-8",
cache_control: "no-store",
body: body.as_bytes().to_vec(),
}
}
fn json(body: String) -> Reply {
Reply {
status: 200,
content_type: "application/json",
cache_control: "no-store",
body: body.into_bytes(),
}
}
fn webp(bytes: &'static [u8]) -> Reply {
Reply {
status: 200,
content_type: "image/webp",
cache_control: "public, max-age=31536000, immutable",
body: bytes.to_vec(),
}
}
fn mark(bytes: &'static [u8], content_type: &'static str) -> Reply {
Reply {
status: 200,
content_type,
cache_control: "public, max-age=31536000, immutable",
body: bytes.to_vec(),
}
}
fn refused(status: u16, code: &ec::ErrCode, detail: &str) -> Reply {
Reply {
status,
content_type: "application/json",
cache_control: "no-store",
body: serde_json::json!({ "code": code.code, "error": detail }).to_string().into_bytes(),
}
}
}
pub struct Doors<'a> {
pub front: &'a Front,
pub catalogue: &'a Catalogue,
}
pub fn route(method: &str, path: &str, doors: &Doors<'_>) -> Option<Reply> {
let owned = matches!(
path,
"/" | PICTURE_PATH | VETRA_PATH | IGNALINA_PATH | FRONT_PATH | RELEASES_PATH
);
if !owned {
return None;
}
if method != "GET" && method != "HEAD" {
return Some(Reply::refused(
405,
&ec::FRONT_METHOD_NOT_ALLOWED,
"the front door answers GET and HEAD only",
));
}
let mut reply = match path {
"/" => Reply::html(PAGE),
PICTURE_PATH => Reply::webp(PICTURE),
VETRA_PATH => Reply::mark(VETRA_MARK, "image/svg+xml"),
IGNALINA_PATH => Reply::mark(IGNALINA_MARK, "image/png"),
FRONT_PATH => Reply::json(
serde_json::to_string(doors.front).unwrap_or_else(|_| "{}".to_string()),
),
RELEASES_PATH => Reply::json(
serde_json::to_string(doors.catalogue)
.unwrap_or_else(|_| r#"{"repositories":[],"releases":[]}"#.to_string()),
),
_ => unreachable!("`owned` above is the same list"),
};
if method == "HEAD" {
reply.body.clear();
}
Some(reply)
}
pub fn reads_gated() -> Reply {
Reply::refused(
403,
&ec::FRONT_READS_GATED,
"this server gates reads behind a credential, so its front page cannot name itself",
)
}
#[cfg(test)]
mod tests {
use super::*;
fn lexicographic(a: &str, b: &str) -> Ordering {
a.cmp(b)
}
fn numeric(a: &str, b: &str) -> Ordering {
let parts = |v: &str| v.split('.').map(|p| p.parse::<u64>().unwrap_or(0)).collect::<Vec<_>>();
parts(a).cmp(&parts(b))
}
fn rel(repo: &str, art: &str, ver: &str) -> Release {
Release { repository: repo.into(), artifact: art.into(), version: ver.into() }
}
#[test]
fn a_rooted_path_becomes_the_console_button() {
let login = console_login("/console").expect("a rooted path is a door");
assert_eq!(login.start, "/console");
assert_eq!(login.label, CONSOLE_LABEL);
}
#[test]
fn the_rust_rule_and_the_pages_rule_refuse_the_same_values() {
assert!(
PAGE.contains("if (!start.startsWith('/') || start.startsWith('//') || start.startsWith('/\\\\')) return false;"),
"the page's same-site guard has moved; console_login may no longer mirror it"
);
assert!(
PAGE.contains(r"if (/[\x00-\x20\x7f]/.test(start)) return false;"),
"the page's control-character class has changed; same_site_rooted_path is now a second opinion"
);
for refused in [
"", "https://console.example.com", "console", "//evil.example.com", "/\\evil.example.com", "/console\u{0}", "/console\u{9}", "/console\u{a}", "/con sole", "/console\u{7f}", ] {
assert!(
console_login(refused).is_none(),
"{refused:?} must not become a button: the page would drop it in silence"
);
}
}
#[test]
fn no_console_configured_is_still_the_named_refusal() {
let f = Front { base_url: None, login: None };
let body = serde_json::to_string(&f).unwrap();
assert!(body.contains("\"login\":null"), "{body}");
assert!(Front::no_login_refusal().starts_with(ec::FRONT_NO_BROWSER_LOGIN.code));
}
#[test]
fn the_comparator_decides_and_this_crate_does_not() {
let rows = vec![rel("crates", "serde", "1.9.0"), rel("crates", "serde", "1.10.0")];
assert_eq!(latest_by(rows.clone(), numeric)[0].version, "1.10.0");
assert_eq!(latest_by(rows, lexicographic)[0].version, "1.9.0");
}
#[test]
fn one_row_per_repository_and_artifact() {
let out = latest_by(
vec![
rel("crates", "serde", "1.0.1"),
rel("crates", "serde", "1.0.9"),
rel("mirror", "serde", "1.0.4"),
rel("crates", "tokio", "1.2.0"),
],
numeric,
);
assert_eq!(out.len(), 3);
assert_eq!(
out.iter().map(|r| (r.repository.as_str(), r.artifact.as_str(), r.version.as_str())).collect::<Vec<_>>(),
vec![("crates", "serde", "1.0.9"), ("crates", "tokio", "1.2.0"), ("mirror", "serde", "1.0.4")]
);
}
#[test]
fn the_order_does_not_depend_on_the_input_order() {
let a = vec![rel("b", "y", "1"), rel("a", "z", "1"), rel("a", "y", "1")];
let mut b = a.clone();
b.reverse();
assert_eq!(latest_by(a, numeric), latest_by(b, numeric));
}
#[test]
fn a_namespace_joins_the_name_with_one_slash_and_an_empty_one_does_not() {
assert_eq!(artifact_name(Some("org.apache.arrow"), "arrow-vector"), "org.apache.arrow/arrow-vector");
assert_eq!(artifact_name(Some("@scope"), "pkg"), "@scope/pkg");
assert_eq!(artifact_name(None, "serde"), "serde");
assert_eq!(artifact_name(Some(""), "serde"), "serde", "an empty namespace put a slash on the front");
}
fn doors() -> (Front, Catalogue) {
(
Front::anonymous(Some("https://holger.rs".into())),
Catalogue {
repositories: vec![Repository {
name: "bundles".into(),
format: "generic".into(),
packages: 1,
}],
releases: vec![rel("bundles", "site-a", "3")],
},
)
}
#[test]
fn the_page_is_served_at_the_root_and_is_not_cached() {
let (f, r) = doors();
let d = Doors { front: &f, catalogue: &r };
let reply = route("GET", "/", &d).expect("the root is this door's");
assert_eq!(reply.status, 200);
assert_eq!(reply.content_type, "text/html; charset=utf-8");
assert_eq!(reply.cache_control, "no-store", "the page must not be cached");
assert_eq!(reply.body, PAGE.as_bytes());
}
#[test]
fn the_picture_is_cached_forever_and_the_json_never() {
let (f, r) = doors();
let d = Doors { front: &f, catalogue: &r };
let pic = route("GET", PICTURE_PATH, &d).unwrap();
assert_eq!(pic.content_type, "image/webp");
assert!(pic.cache_control.contains("immutable"), "{}", pic.cache_control);
for p in [FRONT_PATH, RELEASES_PATH] {
assert_eq!(route("GET", p, &d).unwrap().cache_control, "no-store", "{p} was cacheable");
}
}
#[test]
fn the_page_asks_for_the_picture_this_door_serves() {
assert!(
PAGE.contains(&format!("url(\"{PICTURE_PATH}\")")),
"the page's --backdrop-image does not name {PICTURE_PATH}"
);
assert!(PAGE.contains(RELEASES_PATH), "the page does not fetch {RELEASES_PATH}");
assert!(PAGE.contains(FRONT_PATH), "the page does not fetch {FRONT_PATH}");
}
#[test]
fn the_page_carries_no_raw_control_bytes() {
for (i, b) in PAGE.bytes().enumerate() {
let allowed = matches!(b, b'\t' | b'\n' | b'\r');
assert!(
allowed || !(b.is_ascii_control() || b == 0x7f),
"assets/front.html carries a raw control byte {b:#04x} at offset {i}; \
a browser replaces U+0000 with U+FFFD in script data and throws the whole \
inline script away. Write it as a JS escape."
);
}
}
#[test]
fn the_compiled_in_picture_is_a_webp() {
assert!(PICTURE.len() > 12, "the picture is empty — run `holger-ops logo`");
assert_eq!(&PICTURE[0..4], b"RIFF", "not a RIFF container");
assert_eq!(&PICTURE[8..12], b"WEBP", "not a WebP");
}
#[test]
fn a_repository_path_is_never_this_doors() {
let (f, r) = doors();
let d = Doors { front: &f, catalogue: &r };
for p in ["/crates-mirror", "/v2/alpine/manifests/latest", "/-/search", "/healthz", "/bundles/x", ""] {
assert!(route("GET", p, &d).is_none(), "{p} was claimed by the front door");
}
}
#[test]
fn a_write_is_refused_by_name_with_its_code() {
let (f, r) = doors();
let d = Doors { front: &f, catalogue: &r };
for m in ["POST", "PUT", "DELETE", "PATCH"] {
let reply = route(m, "/", &d).unwrap();
assert_eq!(reply.status, 405, "{m}");
let body = String::from_utf8(reply.body).unwrap();
assert!(body.contains(ec::FRONT_METHOD_NOT_ALLOWED.code), "{m}: {body}");
}
}
#[test]
fn head_answers_the_same_thing_without_the_bytes() {
let (f, r) = doors();
let d = Doors { front: &f, catalogue: &r };
for p in ["/", PICTURE_PATH, FRONT_PATH, RELEASES_PATH] {
let get = route("GET", p, &d).unwrap();
let head = route("HEAD", p, &d).unwrap();
assert_eq!(head.status, get.status);
assert_eq!(head.content_type, get.content_type);
assert!(head.body.is_empty(), "HEAD {p} carried a body");
}
}
#[test]
fn the_wire_carries_exactly_three_columns() {
let (f, r) = doors();
let d = Doors { front: &f, catalogue: &r };
let body = String::from_utf8(route("GET", RELEASES_PATH, &d).unwrap().body).unwrap();
let doc: serde_json::Value = serde_json::from_str(&body).unwrap();
let rows: Vec<serde_json::Map<String, serde_json::Value>> =
serde_json::from_value(doc["releases"].clone()).unwrap();
assert_eq!(rows.len(), 1);
let mut keys: Vec<&str> = rows[0].keys().map(|k| k.as_str()).collect();
keys.sort_unstable();
assert_eq!(keys, vec!["artifact", "repository", "version"], "the row grew or lost a column");
}
#[test]
fn a_server_with_no_login_says_so_rather_than_saying_nothing() {
let f = Front::anonymous(None);
let body = serde_json::to_string(&f).unwrap();
assert!(body.contains("\"login\":null"), "{body}");
assert!(!body.contains("base_url"), "an absent base URL should not be sent at all: {body}");
}
#[test]
fn the_no_login_refusal_is_the_same_sentence_in_the_page_and_in_the_code() {
let r = Front::no_login_refusal();
assert!(r.starts_with(ec::FRONT_NO_BROWSER_LOGIN.code), "{r}");
assert!(PAGE.contains(&r), "the page's sentence has drifted from Front::no_login_refusal():\n{r}");
}
#[test]
fn every_code_the_page_prints_is_in_the_registry() {
for code in [
ec::FRONT_METHOD_NOT_ALLOWED,
ec::FRONT_NO_BROWSER_LOGIN,
ec::FRONT_READS_GATED,
ec::FRONT_DOOR_ABSENT,
] {
assert_eq!(code.subsystem, "front");
assert!(holger_errcode::ALL.iter().any(|c| c.code == code.code), "{} is not in ALL", code.code);
}
}
#[test]
fn the_pages_refusals_are_the_ones_this_crate_words() {
for (code, sentence) in page_refusals() {
assert!(sentence.starts_with(code.code), "{sentence}");
assert!(
PAGE.contains(&sentence),
"the page has drifted from the wording of {}:\n expected: {sentence}",
code.code
);
}
}
#[test]
#[test]
fn the_page_says_nothing_about_gunnar() {
let lower = PAGE.to_lowercase();
for word in ["gunnar", "badger", "git server"] {
assert!(!lower.contains(word), "the page still says `{word}`");
}
}
#[test]
fn the_credit_names_both_makers_and_this_door_serves_their_marks() {
assert!(PAGE.contains("Vetra AB"), "the credit does not name Vetra AB");
assert!(PAGE.contains("Ignalina ApS"), "the credit does not name Ignalina ApS");
assert!(!PAGE.contains("Rickard"), "the old people-and-repo credit is still here");
assert!(!PAGE.contains("codeberg.org/nordisk/holger"), "the old repo link is still here");
for path in [VETRA_PATH, IGNALINA_PATH] {
assert!(PAGE.contains(path), "the page does not reference {path}");
let (f, rel) = doors();
let d = Doors { front: &f, catalogue: &rel };
let r = route("GET", path, &d).unwrap_or_else(|| panic!("{path} is not served"));
assert_eq!(r.status, 200, "{path} answered {}", r.status);
assert!(!r.body.is_empty(), "{path} served an empty body");
}
}
#[test]
fn the_page_names_no_price_and_no_currency() {
for token in ["€", "$", "£", "kr", "SEK", "EUR", "USD", "/month", "per month", "free tier", "pricing"] {
assert!(!PAGE.contains(token), "the page carries `{token}`");
}
}
#[test]
fn the_page_is_holgers() {
assert!(PAGE.contains("<h1>Holger</h1>"), "the page does not name the product");
assert!(PAGE.contains("Immutable artifact repository"), "the tagline is not the readme's");
assert!(PAGE.contains("Latest releases"), "the list is not named");
let mut at = 0usize;
for col in ["Repository", "Artifact", "Version"] {
let head = format!(">{col}</th>");
let found = PAGE.find(&head).unwrap_or_else(|| panic!("the `{col}` column heading is missing"));
assert!(found > at, "the columns are out of order at `{col}`");
at = found;
}
let script = PAGE.split("<script>").nth(1).unwrap_or("");
assert!(
script.contains("[['repository', ''], ['artifact', ''], ['version', 'version']]"),
"the script no longer fills the columns in the order the headings promise"
);
}
#[test]
fn the_picture_is_on_the_left() {
let body = PAGE.split("<body>").nth(1).expect("the page has a body");
let art = body.find("class=\"art\"").expect("the page has the picture half");
let side = body.find("class=\"side\"").expect("the page has the column");
assert!(art < side, "the picture is not the first half of the page");
assert!(!PAGE.contains("row-reverse"), "something reversed the row and put the picture on the right");
assert!(PAGE.contains(".art {\n flex: 0 0 50%;"), "the picture no longer owns a half");
}
#[test]
fn the_page_does_not_rank_anything() {
let script = PAGE.split("<script>").nth(1).unwrap_or("");
for banned in [".sort(", "localeCompare", "parseFloat", "parseInt"] {
assert!(!script.contains(banned), "the page's script carries `{banned}` — it is deciding something");
}
}
#[test]
fn the_page_fetches_nothing_from_outside() {
for scheme in ["http://", "//cdn", "googleapis", "cdnjs", "jsdelivr", "unpkg"] {
assert!(!PAGE.contains(scheme), "the page reaches out to `{scheme}`");
}
assert_eq!(PAGE.matches("https://").count(), 1, "the page names more than one external URL");
}
#[test]
fn the_ceremony_door_is_labelled_and_wired_to_this_page() {
let login = passkey_login_here(
"/auth/passkey/login/start",
"/auth/passkey/login/finish",
"/overview",
)
.expect("three rooted paths are a ceremony");
assert_eq!(login.label, PASSKEY_LABEL);
assert_eq!(login.label, "Log in with passkey");
assert_eq!(login.door, Door::Here);
assert_eq!(login.finish.as_deref(), Some("/auth/passkey/login/finish"));
assert_eq!(login.next.as_deref(), Some("/overview"));
let body = serde_json::to_string(&Front {
base_url: None,
login: Some(login),
})
.unwrap();
assert!(body.contains(r#""door":"here""#), "{body}");
}
#[test]
fn a_link_door_still_says_link_and_carries_no_ceremony() {
let login = console_login("/login?next=/overview").unwrap();
assert_eq!(login.door, Door::Link);
assert_eq!(login.label, CONSOLE_LABEL);
assert!(login.finish.is_none() && login.next.is_none());
let body = serde_json::to_string(&login).unwrap();
assert!(body.contains(r#""door":"link""#), "{body}");
assert!(!body.contains("finish"), "a link door sent a ceremony field: {body}");
}
#[test]
fn a_document_from_before_this_field_is_still_a_link() {
let old = r#"{"label":"Open the console","start":"/login"}"#;
let login: Login = serde_json::from_str(old).unwrap();
assert_eq!(login.door, Door::Link);
}
#[test]
fn one_off_site_path_refuses_the_whole_ceremony() {
let good = ("/auth/passkey/login/start", "/auth/passkey/login/finish", "/overview");
assert!(passkey_login_here(good.0, good.1, good.2).is_some());
for bad in ["https://evil.example.com/finish", "//evil.example.com", "/\\evil", "", "relative"] {
assert!(passkey_login_here(bad, good.1, good.2).is_none(), "start {bad:?}");
assert!(passkey_login_here(good.0, bad, good.2).is_none(), "finish {bad:?}");
assert!(passkey_login_here(good.0, good.1, bad).is_none(), "next {bad:?}");
}
}
#[test]
fn the_page_carries_the_ceremony_and_not_a_second_page() {
let script = PAGE.split("<script>").nth(1).unwrap_or("");
assert!(
script.contains("navigator.credentials.get"),
"the page does not call the authenticator — the button is still a link"
);
assert!(script.contains("allowCredentials"), "the page does not decode the challenge list");
assert!(script.contains("clientDataJSON"), "the page does not send the assertion");
assert!(
!PAGE.contains("Open the console"),
"the page hard-codes a label; the server names the button"
);
}
#[test]
fn an_empty_store_still_names_its_repositories() {
let f = Front::anonymous(None);
let c = Catalogue {
repositories: vec![
Repository { name: "crates-mirror".into(), format: "cargo".into(), packages: 0 },
],
releases: vec![],
};
let d = Doors { front: &f, catalogue: &c };
let body = String::from_utf8(route("GET", RELEASES_PATH, &d).unwrap().body).unwrap();
let doc: serde_json::Value = serde_json::from_str(&body).unwrap();
assert_eq!(doc["repositories"].as_array().unwrap().len(), 1);
assert_eq!(doc["repositories"][0]["name"], "crates-mirror");
assert_eq!(doc["repositories"][0]["format"], "cargo");
assert_eq!(doc["repositories"][0]["packages"], 0);
assert!(doc["releases"].as_array().unwrap().is_empty());
}
#[test]
fn the_page_reads_both_halves_of_the_catalogue() {
let script = PAGE.split("<script>").nth(1).unwrap_or("");
assert!(script.contains("doc.repositories"), "the page ignores the roster");
assert!(script.contains("doc.releases"), "the page ignores the rows");
assert!(PAGE.contains("Public repositories"), "the roster has no heading");
}
#[test]
fn nothing_on_the_page_turns_text_into_markup() {
assert!(!PAGE.contains("innerHTML"), "the page writes markup from data");
assert!(!PAGE.contains("document.write"), "the page uses document.write");
}
}