AkurAI Build
Menu

AkurAI-Build

public

Latest change 1c2159692a31765cd66ed709791ba11468054873 - Initial commit: bunfork v0.1.0 source tree by Olafur Bui

use std::{
    collections::{BTreeMap, HashSet},
    fs::{self, OpenOptions},
    io::Read,
    net::SocketAddr,
    path::{Path, PathBuf},
    sync::Arc,
};

use anyhow::{Context, Result, ensure};
use axum::{
    Json, Router,
    body::{Body, Bytes},
    extract::{DefaultBodyLimit, OriginalUri, Request, State},
    http::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode, Uri, header},
    middleware::{self, Next},
    response::{Html, IntoResponse, Response},
    routing::{get, post, put},
};
use bunfork::artifact::{self, CacheClass, Manifest, Resolution};
use minijinja::{AutoEscape, Environment, context};
use percent_encoding::percent_decode_str;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use subtle::ConstantTimeEq;
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
use tracing::{error, info, warn};
use zeroize::Zeroizing;

use crate::db::{
    Database, VectorInputError, VectorMatch, validate_vector_id, validate_vector_search,
    validate_vector_upsert,
};

const MAX_BODY_BYTES: usize = 1024 * 1024;
const MAX_PRELOADED_STATIC_BYTES: u64 = 512 * 1024 * 1024;

#[derive(Clone)]
pub struct ServeOptions {
    pub address: SocketAddr,
    pub pages: PathBuf,
    pub public: PathBuf,
    pub tenant: String,
    pub model: String,
    pub development: bool,
    pub api_token: Arc<Zeroizing<String>>,
    pub public_origin: Option<String>,
}

#[derive(Clone)]
struct AppState {
    database: Database,
    pages: Arc<PageRouter>,
    pages_dir: PathBuf,
    tenant: String,
    model: String,
    development: bool,
    api_token: Arc<Zeroizing<String>>,
    public_origin: Option<String>,
    public_assets: Arc<BTreeMap<String, StaticFile>>,
    search_gate: Arc<tokio::sync::Semaphore>,
}

#[derive(Clone)]
struct StaticState {
    manifest: Arc<Manifest>,
    files: Arc<BTreeMap<String, StaticFile>>,
}

#[derive(Clone)]
struct StaticFile {
    bytes: Bytes,
    etag: String,
}

#[derive(Debug, Clone)]
pub struct PageRouter {
    pages: Vec<Page>,
    templates: BTreeMap<String, Arc<str>>,
    not_found: Option<Arc<str>>,
    internal_error: Option<Arc<str>>,
}

#[derive(Debug, Clone)]
struct Page {
    template: Arc<str>,
    pattern: String,
    segments: Vec<Segment>,
}

#[derive(Debug, Clone, Eq, PartialEq)]
enum Segment {
    Static(String),
    Parameter(String),
    CatchAll(String),
    OptionalCatchAll(String),
}

#[derive(Debug)]
struct PageMatch {
    template: Arc<str>,
    params: BTreeMap<String, String>,
}

#[derive(Debug, Deserialize)]
struct VectorUpsert {
    id: String,
    content: String,
    embedding: Vec<f32>,
}

#[derive(Debug, Deserialize)]
struct VectorSearch {
    embedding: Vec<f32>,
    #[serde(default = "default_search_limit")]
    limit: usize,
}

#[derive(Debug, Serialize)]
struct SearchResponse {
    matches: Vec<VectorMatch>,
}

#[derive(Debug, Serialize)]
struct Message<'a> {
    message: &'a str,
}

struct ApiError {
    status: StatusCode,
    public: &'static str,
}

impl ApiError {
    fn unauthorized() -> Self {
        Self {
            status: StatusCode::UNAUTHORIZED,
            public: "unauthorized",
        }
    }

    fn bad_request(public: &'static str) -> Self {
        Self {
            status: StatusCode::BAD_REQUEST,
            public,
        }
    }

    fn too_many_requests() -> Self {
        Self {
            status: StatusCode::TOO_MANY_REQUESTS,
            public: "another vector search is already running",
        }
    }

    fn internal(error: anyhow::Error) -> Self {
        error!(error = ?error, "request failed");
        Self {
            status: StatusCode::INTERNAL_SERVER_ERROR,
            public: "internal server error",
        }
    }

    fn database(error: anyhow::Error) -> Self {
        if error.downcast_ref::<VectorInputError>().is_some() {
            Self::bad_request("invalid vector request")
        } else {
            Self::internal(error)
        }
    }
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        (
            self.status,
            Json(Message {
                message: self.public,
            }),
        )
            .into_response()
    }
}

pub async fn serve(database: Database, options: ServeOptions) -> Result<()> {
    ensure!(
        options.pages.is_dir(),
        "pages directory does not exist: {}",
        options.pages.display()
    );
    ensure!(
        options.public.is_dir(),
        "public directory does not exist: {}",
        options.public.display()
    );
    let public_assets = preload_public_files(&options.public)?;
    let public_origin = options
        .public_origin
        .as_deref()
        .map(validate_public_origin)
        .transpose()?;
    if !options.address.ip().is_loopback()
        && !public_origin
            .as_deref()
            .is_some_and(|origin| origin.starts_with("https://"))
    {
        warn!(
            address = %options.address,
            "public HTTP listener has no HTTPS public origin; keep it on a trusted network or add a TLS proxy"
        );
    }
    let page_router = PageRouter::scan(&options.pages)?;
    ensure!(
        !page_router.pages.is_empty(),
        "no .html pages found in {}",
        options.pages.display()
    );

    let state = AppState {
        database,
        pages: Arc::new(page_router),
        pages_dir: options.pages,
        tenant: options.tenant,
        model: options.model,
        development: options.development,
        api_token: options.api_token,
        public_origin,
        public_assets: Arc::new(public_assets),
        search_gate: Arc::new(tokio::sync::Semaphore::new(1)),
    };
    let app = native_status_routes()
        .route(
            "/api/vectors/{id}",
            put(upsert_vector).delete(delete_vector),
        )
        .route("/api/vectors/search", post(search_vectors))
        .route("/assets/{*path}", get(public_asset))
        .fallback(render_page)
        .with_state(state)
        .layer(DefaultBodyLimit::max(MAX_BODY_BYTES))
        .layer(CompressionLayer::new())
        .layer(TraceLayer::new_for_http())
        .layer(middleware::from_fn(reject_hidden_request_paths))
        .layer(middleware::from_fn(security_headers));

    let listener = tokio::net::TcpListener::bind(options.address)
        .await
        .with_context(|| format!("bind {}", options.address))?;
    info!(address = %listener.local_addr()?, "bunfork listening");
    axum::serve(listener, app)
        .with_graceful_shutdown(shutdown_signal())
        .await
        .context("serve HTTP")
}

pub async fn serve_static(
    address: SocketAddr,
    root: &Path,
    manifest_path: &Path,
    development: bool,
) -> Result<()> {
    let root = fs::canonicalize(root).context("resolve static artifact root")?;
    let manifest = artifact::read_manifest(manifest_path)?;
    artifact::verify(&root, &manifest)?;
    let files = preload_static_files(&root, &manifest)?;
    if development {
        warn!("static dev mode reuses the frozen artifact; rebuild and restart to see changes");
    }
    if !address.ip().is_loopback() {
        warn!(
            address = %address,
            "public HTTP listener has no built-in TLS; keep it on a trusted network or add a TLS proxy"
        );
    }
    let state = StaticState {
        manifest: Arc::new(manifest),
        files: Arc::new(files),
    };
    let app = static_status_routes()
        .fallback(static_request)
        .with_state(state)
        .layer(TraceLayer::new_for_http())
        .layer(middleware::from_fn(static_security_headers));
    let listener = tokio::net::TcpListener::bind(address)
        .await
        .with_context(|| format!("bind {address}"))?;
    info!(address = %listener.local_addr()?, mode = "static", "bunfork listening");
    axum::serve(listener, app)
        .with_graceful_shutdown(shutdown_signal())
        .await
        .context("serve static HTTP")
}

fn preload_static_files(root: &Path, manifest: &Manifest) -> Result<BTreeMap<String, StaticFile>> {
    let mut loaded = BTreeMap::new();
    let mut total = 0_u64;
    for (relative, record) in &manifest.files {
        total = total
            .checked_add(record.bytes)
            .context("static artifact size overflow")?;
        ensure!(
            total <= MAX_PRELOADED_STATIC_BYTES,
            "static artifact exceeds the 512 MiB in-memory serving limit"
        );
        let bytes = read_static_file(&root.join(relative), relative, record)?;
        loaded.insert(
            relative.clone(),
            StaticFile {
                bytes: Bytes::from(bytes),
                etag: format!("\"{}\"", record.sha256),
            },
        );
    }
    Ok(loaded)
}

fn preload_public_files(root: &Path) -> Result<BTreeMap<String, StaticFile>> {
    let inventory = artifact::inventory(root).context("inventory native public files")?;
    let mut loaded = BTreeMap::new();
    let mut total = 0_u64;
    for (relative, record) in inventory {
        total = total
            .checked_add(record.bytes)
            .context("native public asset size overflow")?;
        ensure!(
            total <= MAX_PRELOADED_STATIC_BYTES,
            "native public assets exceed the 512 MiB in-memory serving limit"
        );
        let bytes = read_static_file(&root.join(&relative), &relative, &record)?;
        loaded.insert(
            relative,
            StaticFile {
                bytes: Bytes::from(bytes),
                etag: format!("W/\"{}\"", record.sha256),
            },
        );
    }
    Ok(loaded)
}

fn read_static_file(path: &Path, label: &str, record: &artifact::FileRecord) -> Result<Vec<u8>> {
    let mut options = OpenOptions::new();
    options.read(true);
    #[cfg(unix)]
    {
        use std::os::unix::fs::OpenOptionsExt;
        options.custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC);
    }
    let mut file = options
        .open(path)
        .with_context(|| format!("open admitted static file {label}"))?;
    let metadata = file.metadata()?;
    ensure!(
        metadata.is_file(),
        "static file is no longer regular: {label}"
    );
    #[cfg(unix)]
    {
        use std::os::unix::fs::MetadataExt;
        ensure!(
            metadata.nlink() == 1,
            "static file became hard-linked: {label}"
        );
    }
    ensure!(
        metadata.len() == record.bytes,
        "static file size changed: {label}"
    );
    let mut bytes = Vec::with_capacity(usize::try_from(record.bytes)?);
    file.read_to_end(&mut bytes)?;
    let digest = format!("{:x}", Sha256::digest(&bytes));
    ensure!(
        digest == record.sha256,
        "static file digest changed: {label}"
    );
    let path_metadata = fs::symlink_metadata(path)?;
    #[cfg(unix)]
    {
        use std::os::unix::fs::MetadataExt;
        ensure!(
            path_metadata.dev() == metadata.dev() && path_metadata.ino() == metadata.ino(),
            "static file path changed while loading: {label}"
        );
    }
    Ok(bytes)
}

async fn static_request(
    State(state): State<StaticState>,
    method: Method,
    OriginalUri(uri): OriginalUri,
    headers: HeaderMap,
) -> Response {
    if method != Method::GET && method != Method::HEAD {
        let mut response = StatusCode::METHOD_NOT_ALLOWED.into_response();
        response
            .headers_mut()
            .insert(header::ALLOW, HeaderValue::from_static("GET, HEAD"));
        return response;
    }
    let resolution = match artifact::resolve(&state.manifest, uri.path()) {
        Ok(resolution) => resolution,
        Err(_) => return StatusCode::BAD_REQUEST.into_response(),
    };
    match resolution {
        Resolution::File {
            relative_path,
            status,
            cache,
        }
        | Resolution::Fallback {
            relative_path,
            status,
            cache,
        } => static_file_response(&state, &method, &headers, &relative_path, status, cache),
        Resolution::Redirect {
            mut location,
            status,
        } => {
            if let Some(query) = uri.query() {
                location.push('?');
                location.push_str(query);
            }
            let status = StatusCode::from_u16(status).unwrap_or(StatusCode::PERMANENT_REDIRECT);
            let mut response = status.into_response();
            match HeaderValue::from_str(&location) {
                Ok(location) => {
                    response.headers_mut().insert(header::LOCATION, location);
                    response
                }
                Err(_) => StatusCode::BAD_REQUEST.into_response(),
            }
        }
        Resolution::NotFound { status } => StatusCode::from_u16(status)
            .unwrap_or(StatusCode::NOT_FOUND)
            .into_response(),
    }
}

fn static_file_response(
    state: &StaticState,
    method: &Method,
    request_headers: &HeaderMap,
    relative: &str,
    status: u16,
    cache: CacheClass,
) -> Response {
    let (served_path, encoding) = negotiated_static_path(state, relative, request_headers);
    let Some(file) = state.files.get(&served_path) else {
        return StatusCode::INTERNAL_SERVER_ERROR.into_response();
    };
    let status = StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
    if request_headers
        .get(header::IF_NONE_MATCH)
        .and_then(|value| value.to_str().ok())
        .is_some_and(|value| value.split(',').any(|tag| tag.trim() == file.etag))
    {
        let mut response = StatusCode::NOT_MODIFIED.into_response();
        let headers = response.headers_mut();
        if let Ok(etag) = HeaderValue::from_str(&file.etag) {
            headers.insert(header::ETAG, etag);
        }
        headers.insert(header::VARY, HeaderValue::from_static("Accept-Encoding"));
        headers.insert(
            header::CACHE_CONTROL,
            HeaderValue::from_static(static_cache_control(relative, cache)),
        );
        if let Some(encoding) = encoding {
            headers.insert(header::CONTENT_ENCODING, HeaderValue::from_static(encoding));
        }
        return response;
    }
    let body = if method == Method::HEAD {
        Body::empty()
    } else {
        Body::from(file.bytes.clone())
    };
    let mut response = Response::new(body);
    *response.status_mut() = status;
    let headers = response.headers_mut();
    headers.insert(
        header::CONTENT_TYPE,
        HeaderValue::from_static(content_type(relative)),
    );
    if let Ok(length) = HeaderValue::from_str(&file.bytes.len().to_string()) {
        headers.insert(header::CONTENT_LENGTH, length);
    }
    if let Ok(etag) = HeaderValue::from_str(&file.etag) {
        headers.insert(header::ETAG, etag);
    }
    if let Some(encoding) = encoding {
        headers.insert(header::CONTENT_ENCODING, HeaderValue::from_static(encoding));
    }
    headers.insert(header::VARY, HeaderValue::from_static("Accept-Encoding"));
    headers.insert(
        header::CACHE_CONTROL,
        HeaderValue::from_static(static_cache_control(relative, cache)),
    );
    response
}

fn static_cache_control(relative: &str, cache: CacheClass) -> &'static str {
    if cache == CacheClass::Document {
        "no-cache"
    } else if has_immutable_namespace(relative) {
        "public, max-age=31536000, immutable"
    } else {
        "public, max-age=0, must-revalidate"
    }
}

fn negotiated_static_path(
    state: &StaticState,
    relative: &str,
    headers: &HeaderMap,
) -> (String, Option<&'static str>) {
    let accepted = headers
        .get(header::ACCEPT_ENCODING)
        .and_then(|value| value.to_str().ok())
        .unwrap_or_default();
    let br_path = format!("{relative}.br");
    let gzip_path = format!("{relative}.gz");
    let br_quality = encoding_quality(accepted, "br");
    let gzip_quality = encoding_quality(accepted, "gzip");
    let br_available = br_quality > 0 && state.files.contains_key(&br_path);
    let gzip_available = gzip_quality > 0 && state.files.contains_key(&gzip_path);
    if br_available && (!gzip_available || br_quality >= gzip_quality) {
        return (br_path, Some("br"));
    }
    if gzip_available {
        return (gzip_path, Some("gzip"));
    }
    (relative.to_owned(), None)
}

fn encoding_quality(header: &str, wanted: &str) -> u16 {
    let mut exact = None;
    let mut wildcard = None;
    for item in header.split(',') {
        let mut parts = item.trim().split(';');
        let name = parts.next().unwrap_or_default().trim();
        let mut quality = 1000;
        for parameter in parts {
            let Some((key, value)) = parameter.trim().split_once('=') else {
                continue;
            };
            if key.trim().eq_ignore_ascii_case("q") {
                quality = parse_quality(value).unwrap_or(0);
            }
        }
        if name.eq_ignore_ascii_case(wanted) {
            exact = Some(quality);
        } else if name == "*" {
            wildcard = Some(quality);
        }
    }
    exact.or(wildcard).unwrap_or(0)
}

fn parse_quality(value: &str) -> Option<u16> {
    let value = value.trim();
    if value == "0" {
        return Some(0);
    }
    if value == "1" {
        return Some(1000);
    }
    if let Some(fraction) = value.strip_prefix("0.")
        && fraction.len() <= 3
        && fraction.bytes().all(|byte| byte.is_ascii_digit())
    {
        let parsed = fraction.parse::<u16>().ok()?;
        return Some(parsed * 10_u16.pow(u32::try_from(3 - fraction.len()).ok()?));
    }
    if let Some(fraction) = value.strip_prefix("1.")
        && fraction.len() <= 3
        && fraction.bytes().all(|byte| byte == b'0')
    {
        return Some(1000);
    }
    None
}

fn has_immutable_namespace(path: &str) -> bool {
    if path.starts_with("_app/immutable/") {
        return true;
    }
    let Some(next) = path.strip_prefix("_next/static/") else {
        return false;
    };
    let namespace = next.split('/').next().unwrap_or_default();
    matches!(
        namespace,
        "chunks" | "css" | "image" | "immutable" | "media" | "pages" | "runtime"
    )
}

fn content_type(path: &str) -> &'static str {
    let path = path
        .strip_suffix(".br")
        .or_else(|| path.strip_suffix(".gz"))
        .unwrap_or(path);
    match Path::new(path)
        .extension()
        .and_then(|extension| extension.to_str())
        .unwrap_or_default()
        .to_ascii_lowercase()
        .as_str()
    {
        "html" | "htm" => "text/html; charset=utf-8",
        "css" => "text/css; charset=utf-8",
        "js" | "mjs" | "cjs" => "text/javascript; charset=utf-8",
        "json" | "map" => "application/json; charset=utf-8",
        "txt" => "text/plain; charset=utf-8",
        "xml" => "application/xml; charset=utf-8",
        "svg" => "image/svg+xml",
        "png" => "image/png",
        "jpg" | "jpeg" => "image/jpeg",
        "gif" => "image/gif",
        "webp" => "image/webp",
        "avif" => "image/avif",
        "ico" => "image/x-icon",
        "woff" => "font/woff",
        "woff2" => "font/woff2",
        "ttf" => "font/ttf",
        "otf" => "font/otf",
        "wasm" => "application/wasm",
        "pdf" => "application/pdf",
        _ => "application/octet-stream",
    }
}

async fn static_security_headers(request: Request, next: Next) -> Response {
    let mut response = next.run(request).await;
    let headers = response.headers_mut();
    headers.insert(
        header::X_CONTENT_TYPE_OPTIONS,
        HeaderValue::from_static("nosniff"),
    );
    headers.insert(header::X_FRAME_OPTIONS, HeaderValue::from_static("DENY"));
    headers.insert(
        header::REFERRER_POLICY,
        HeaderValue::from_static("no-referrer"),
    );
    headers.insert(
        header::CONTENT_SECURITY_POLICY,
        HeaderValue::from_static(
            "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'",
        ),
    );
    headers.insert(
        HeaderName::from_static("permissions-policy"),
        HeaderValue::from_static("camera=(), geolocation=(), microphone=()"),
    );
    response
}

impl PageRouter {
    pub fn scan(root: &Path) -> Result<Self> {
        ensure!(
            root.is_dir(),
            "pages directory does not exist: {}",
            root.display()
        );
        let mut files = Vec::new();
        scan_directory(root, root, &mut files)?;
        files.sort();
        let templates = load_templates(root)?;
        let not_found = load_special_page(root, "_404.html", &templates)?;
        let internal_error = load_special_page(root, "_500.html", &templates)?;

        let mut pages = Vec::new();
        let mut effective_patterns = HashSet::new();
        for source in files {
            let relative = source
                .strip_prefix(root)
                .context("page escaped pages directory")?;
            let segments = parse_route(relative)?;
            let pattern = display_pattern(&segments);
            let effective = effective_pattern(&segments);
            ensure!(
                effective_patterns.insert(effective),
                "duplicate page route {pattern}"
            );
            let template = read_page(&source)?;
            let mut environment = template_environment(&templates)?;
            environment
                .add_template_owned("page.html", template.clone())
                .with_context(|| format!("compile page template {}", source.display()))?;
            pages.push(Page {
                template: Arc::from(template),
                pattern,
                segments,
            });
        }
        pages.sort_by(compare_pages);
        Ok(Self {
            pages,
            templates,
            not_found,
            internal_error,
        })
    }

    pub fn route_count(&self) -> usize {
        self.pages.len()
    }

    fn match_path(&self, path: &str) -> Result<Option<PageMatch>> {
        ensure!(!path.contains('\0'), "request path contains a null byte");
        ensure!(
            !path.contains("//"),
            "request path contains an empty segment"
        );
        let raw_segments = path
            .trim_matches('/')
            .split('/')
            .filter(|segment| !segment.is_empty())
            .collect::<Vec<_>>();
        let mut decoded = Vec::with_capacity(raw_segments.len());
        for raw in raw_segments {
            let segment = percent_decode_str(raw)
                .decode_utf8()
                .context("request path is not valid UTF-8")?
                .into_owned();
            ensure!(segment != "." && segment != "..", "unsafe request path");
            ensure!(
                !segment.contains(['/', '\\', '\0']),
                "unsafe encoded request path segment"
            );
            decoded.push(segment);
        }

        for page in &self.pages {
            if let Some(params) = match_segments(&page.segments, &decoded) {
                return Ok(Some(PageMatch {
                    template: page.template.clone(),
                    params,
                }));
            }
        }
        Ok(None)
    }
}

async fn health() -> impl IntoResponse {
    Json(Message { message: "ok" })
}

async fn native_ready(State(state): State<AppState>) -> Response {
    readiness_response(state.database.check_ready())
}

fn readiness_response(result: Result<()>) -> Response {
    match result {
        Ok(()) => Json(Message { message: "ok" }).into_response(),
        Err(error) => {
            warn!(?error, "database readiness probe failed");
            (
                StatusCode::SERVICE_UNAVAILABLE,
                Json(Message {
                    message: "unavailable",
                }),
            )
                .into_response()
        }
    }
}

fn health_routes<S>() -> Router<S>
where
    S: Clone + Send + Sync + 'static,
{
    Router::new()
        .route("/api/health", get(health))
        .route("/_bunfork/health", get(health))
}

fn native_status_routes() -> Router<AppState> {
    health_routes()
        .route("/api/ready", get(native_ready))
        .route("/_bunfork/ready", get(native_ready))
}

fn static_status_routes<S>() -> Router<S>
where
    S: Clone + Send + Sync + 'static,
{
    health_routes()
        .route("/api/ready", get(health))
        .route("/_bunfork/ready", get(health))
}

async fn public_asset(
    State(state): State<AppState>,
    method: Method,
    OriginalUri(uri): OriginalUri,
    headers: HeaderMap,
) -> Response {
    let relative = match decode_public_asset_path(uri.path()) {
        Ok(relative) => relative,
        Err(_) => return StatusCode::BAD_REQUEST.into_response(),
    };
    let Some(file) = state.public_assets.get(&relative) else {
        return StatusCode::NOT_FOUND.into_response();
    };
    if headers
        .get(header::IF_NONE_MATCH)
        .and_then(|value| value.to_str().ok())
        .is_some_and(|value| value.split(',').any(|tag| tag.trim() == file.etag))
    {
        let mut response = StatusCode::NOT_MODIFIED.into_response();
        if let Ok(etag) = HeaderValue::from_str(&file.etag) {
            response.headers_mut().insert(header::ETAG, etag);
        }
        response.headers_mut().insert(
            header::CACHE_CONTROL,
            HeaderValue::from_static(static_cache_control(
                &format!("assets/{relative}"),
                CacheClass::Asset,
            )),
        );
        return response;
    }
    let body = if method == Method::HEAD {
        Body::empty()
    } else {
        Body::from(file.bytes.clone())
    };
    let mut response = Response::new(body);
    let response_headers = response.headers_mut();
    response_headers.insert(
        header::CONTENT_TYPE,
        HeaderValue::from_static(content_type(&relative)),
    );
    if let Ok(length) = HeaderValue::from_str(&file.bytes.len().to_string()) {
        response_headers.insert(header::CONTENT_LENGTH, length);
    }
    if let Ok(etag) = HeaderValue::from_str(&file.etag) {
        response_headers.insert(header::ETAG, etag);
    }
    response_headers.insert(
        header::CACHE_CONTROL,
        HeaderValue::from_static(static_cache_control(
            &format!("assets/{relative}"),
            CacheClass::Asset,
        )),
    );
    response
}

fn decode_public_asset_path(path: &str) -> Result<String> {
    let raw = path
        .strip_prefix("/assets/")
        .context("request is outside the public asset namespace")?;
    ensure!(!raw.is_empty() && !raw.contains("//"), "invalid asset path");
    let mut decoded = Vec::new();
    for segment in raw.split('/') {
        let segment = percent_decode_str(segment)
            .decode_utf8()
            .context("asset path is not valid UTF-8")?
            .into_owned();
        ensure!(
            !segment.is_empty()
                && segment != "."
                && segment != ".."
                && !segment.starts_with('.')
                && !segment.contains(['/', '\\', '\0']),
            "unsafe asset path"
        );
        decoded.push(segment);
    }
    Ok(decoded.join("/"))
}

async fn upsert_vector(
    State(state): State<AppState>,
    headers: HeaderMap,
    axum::extract::Path(id): axum::extract::Path<String>,
    Json(payload): Json<VectorUpsert>,
) -> Result<impl IntoResponse, ApiError> {
    authorize(&headers, &state)?;
    validate_same_origin(&headers, &state)?;
    if id != payload.id {
        return Err(ApiError::bad_request("path and body vector ids differ"));
    }
    validate_vector_upsert(
        &state.tenant,
        &state.model,
        &payload.id,
        &payload.content,
        &payload.embedding,
    )
    .map_err(ApiError::database)?;
    let database = state.database.clone();
    let tenant = state.tenant.clone();
    let model = state.model.clone();
    tokio::task::spawn_blocking(move || {
        database.upsert_vector(
            &tenant,
            &model,
            &payload.id,
            &payload.content,
            &payload.embedding,
        )
    })
    .await
    .map_err(|error| ApiError::internal(error.into()))?
    .map_err(ApiError::database)?;
    Ok((StatusCode::CREATED, Json(Message { message: "stored" })))
}

async fn search_vectors(
    State(state): State<AppState>,
    headers: HeaderMap,
    Json(payload): Json<VectorSearch>,
) -> Result<Json<SearchResponse>, ApiError> {
    authorize(&headers, &state)?;
    validate_same_origin(&headers, &state)?;
    validate_vector_search(
        &state.tenant,
        &state.model,
        &payload.embedding,
        payload.limit,
    )
    .map_err(ApiError::database)?;
    let _permit = state
        .search_gate
        .clone()
        .try_acquire_owned()
        .map_err(|_| ApiError::too_many_requests())?;
    let database = state.database.clone();
    let tenant = state.tenant.clone();
    let model = state.model.clone();
    let matches = tokio::task::spawn_blocking(move || {
        database.search_vectors(&tenant, &model, &payload.embedding, payload.limit)
    })
    .await
    .map_err(|error| ApiError::internal(error.into()))?
    .map_err(ApiError::database)?;
    Ok(Json(SearchResponse { matches }))
}

async fn delete_vector(
    State(state): State<AppState>,
    headers: HeaderMap,
    axum::extract::Path(id): axum::extract::Path<String>,
) -> Result<StatusCode, ApiError> {
    authorize(&headers, &state)?;
    validate_same_origin(&headers, &state)?;
    validate_vector_id(&state.tenant, &state.model, &id).map_err(ApiError::database)?;
    let database = state.database.clone();
    let tenant = state.tenant.clone();
    let model = state.model.clone();
    let deleted = tokio::task::spawn_blocking(move || database.delete_vector(&tenant, &model, &id))
        .await
        .map_err(|error| ApiError::internal(error.into()))?
        .map_err(ApiError::database)?;
    if deleted {
        Ok(StatusCode::NO_CONTENT)
    } else {
        Err(ApiError {
            status: StatusCode::NOT_FOUND,
            public: "vector not found",
        })
    }
}

async fn render_page(State(state): State<AppState>, OriginalUri(uri): OriginalUri) -> Response {
    let router = if state.development {
        match PageRouter::scan(&state.pages_dir) {
            Ok(router) => router,
            Err(error) => return ApiError::internal(error).into_response(),
        }
    } else {
        state.pages.as_ref().clone()
    };
    let query = match parse_query(&uri) {
        Ok(query) => query,
        Err(_) => return (StatusCode::BAD_REQUEST, Html("<h1>Bad request</h1>")).into_response(),
    };
    let matched = match router.match_path(uri.path()) {
        Ok(Some(matched)) => matched,
        Ok(None) => {
            if let Some(template) = &router.not_found {
                return render_native_template(
                    &router,
                    template,
                    BTreeMap::new(),
                    &query,
                    &uri,
                    &state,
                    StatusCode::NOT_FOUND,
                )
                .unwrap_or_else(|error| ApiError::internal(error).into_response());
            }
            return (StatusCode::NOT_FOUND, Html("<h1>Not found</h1>")).into_response();
        }
        Err(_) => return (StatusCode::BAD_REQUEST, Html("<h1>Bad request</h1>")).into_response(),
    };
    match render_native_template(
        &router,
        &matched.template,
        matched.params,
        &query,
        &uri,
        &state,
        StatusCode::OK,
    ) {
        Ok(response) => response,
        Err(error) => {
            error!(error = ?error, "page rendering failed");
            if let Some(template) = &router.internal_error {
                render_native_template(
                    &router,
                    template,
                    BTreeMap::new(),
                    &BTreeMap::new(),
                    &uri,
                    &state,
                    StatusCode::INTERNAL_SERVER_ERROR,
                )
                .unwrap_or_else(|error| ApiError::internal(error).into_response())
            } else {
                ApiError::internal(error).into_response()
            }
        }
    }
}

fn render_native_template(
    router: &PageRouter,
    source: &str,
    params: BTreeMap<String, String>,
    query: &BTreeMap<String, Vec<String>>,
    uri: &Uri,
    state: &AppState,
    status: StatusCode,
) -> Result<Response> {
    let mut environment = template_environment(&router.templates)?;
    environment.add_template_owned("page.html", source.to_owned())?;
    let template = environment.get_template("page.html")?;
    let html = template.render(context! {
        params => params,
        query => query,
        pathname => uri.path(),
        tenant => state.tenant,
        model => state.model,
    })?;
    Ok((status, Html(html)).into_response())
}

fn parse_query(uri: &Uri) -> Result<BTreeMap<String, Vec<String>>> {
    let Some(query) = uri.query() else {
        return Ok(BTreeMap::new());
    };
    ensure!(query.len() <= 8192, "query string exceeds 8 KiB");
    let mut values = BTreeMap::<String, Vec<String>>::new();
    for (index, pair) in query.split('&').enumerate() {
        ensure!(index < 64, "query string has too many fields");
        if pair.is_empty() {
            continue;
        }
        let (key, value) = pair.split_once('=').unwrap_or((pair, ""));
        let key = decode_query_component(key)?;
        let value = decode_query_component(value)?;
        ensure!(!key.is_empty(), "query field name cannot be empty");
        ensure!(key.len() <= 256, "query field name is too long");
        ensure!(value.len() <= 4096, "query field value is too long");
        values.entry(key).or_default().push(value);
    }
    Ok(values)
}

fn decode_query_component(value: &str) -> Result<String> {
    let bytes = value.as_bytes();
    for index in 0..bytes.len() {
        if bytes[index] == b'%' {
            ensure!(
                index + 2 < bytes.len()
                    && bytes[index + 1].is_ascii_hexdigit()
                    && bytes[index + 2].is_ascii_hexdigit(),
                "query contains malformed percent encoding"
            );
        }
    }
    percent_decode_str(&value.replace('+', " "))
        .decode_utf8()
        .context("query is not valid UTF-8")
        .map(|value| value.into_owned())
}

fn authorize(headers: &HeaderMap, state: &AppState) -> Result<(), ApiError> {
    let Some(value) = headers.get(header::AUTHORIZATION) else {
        return Err(ApiError::unauthorized());
    };
    let Ok(value) = value.to_str() else {
        return Err(ApiError::unauthorized());
    };
    let Some(provided) = value.strip_prefix("Bearer ") else {
        return Err(ApiError::unauthorized());
    };
    let expected = state.api_token.as_str();
    let valid = provided.len() == expected.len()
        && bool::from(provided.as_bytes().ct_eq(expected.as_bytes()));
    if valid {
        Ok(())
    } else {
        Err(ApiError::unauthorized())
    }
}

fn validate_same_origin(headers: &HeaderMap, state: &AppState) -> Result<(), ApiError> {
    let Some(origin) = headers.get(header::ORIGIN) else {
        return Ok(());
    };
    let origin = origin
        .to_str()
        .map_err(|_| ApiError::bad_request("invalid origin header"))?;
    if let Some(expected) = &state.public_origin {
        return if origin == expected {
            Ok(())
        } else {
            Err(ApiError::bad_request("cross-origin mutation rejected"))
        };
    }
    let Some(host) = headers.get(header::HOST) else {
        return Err(ApiError::bad_request("missing host header"));
    };
    let host = host
        .to_str()
        .map_err(|_| ApiError::bad_request("invalid host header"))?;
    if origin == format!("http://{host}") || origin == format!("https://{host}") {
        Ok(())
    } else {
        Err(ApiError::bad_request("cross-origin mutation rejected"))
    }
}

fn validate_public_origin(origin: &str) -> Result<String> {
    let uri: Uri = origin.parse().context("invalid public origin")?;
    let scheme = uri
        .scheme_str()
        .context("public origin requires a scheme")?;
    let authority = uri
        .authority()
        .context("public origin requires an authority")?;
    ensure!(
        matches!(scheme, "http" | "https")
            && matches!(uri.path(), "" | "/")
            && uri.query().is_none(),
        "public origin must contain only an http(s) scheme and authority"
    );
    Ok(format!("{scheme}://{authority}"))
}

async fn security_headers(request: Request, next: Next) -> Response {
    let mut response = next.run(request).await;
    let headers = response.headers_mut();
    headers.insert(
        header::X_CONTENT_TYPE_OPTIONS,
        HeaderValue::from_static("nosniff"),
    );
    headers.insert(header::X_FRAME_OPTIONS, HeaderValue::from_static("DENY"));
    headers.insert(
        header::REFERRER_POLICY,
        HeaderValue::from_static("no-referrer"),
    );
    headers.insert(
        header::CONTENT_SECURITY_POLICY,
        HeaderValue::from_static(
            "default-src 'self'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; form-action 'self'",
        ),
    );
    headers.insert(
        HeaderName::from_static("permissions-policy"),
        HeaderValue::from_static("camera=(), geolocation=(), microphone=()"),
    );
    response
}

async fn reject_hidden_request_paths(request: Request, next: Next) -> Response {
    if has_hidden_segment(request.uri().path()) {
        return StatusCode::NOT_FOUND.into_response();
    }
    next.run(request).await
}

fn has_hidden_segment(path: &str) -> bool {
    path.split('/').any(|segment| {
        percent_decode_str(segment)
            .decode_utf8()
            .is_ok_and(|segment| segment.starts_with('.'))
    })
}

fn scan_directory(root: &Path, current: &Path, pages: &mut Vec<PathBuf>) -> Result<()> {
    let mut entries = fs::read_dir(current)
        .with_context(|| format!("read pages directory {}", current.display()))?
        .collect::<std::io::Result<Vec<_>>>()?;
    entries.sort_by_key(|entry| entry.file_name());
    for entry in entries {
        let file_type = entry.file_type()?;
        ensure!(!file_type.is_symlink(), "page tree cannot contain symlinks");
        let path = entry.path();
        let name = entry.file_name();
        let name = name.to_string_lossy();
        ensure!(
            !name.starts_with('.'),
            "page tree cannot contain hidden entries"
        );
        if name.starts_with('_') {
            continue;
        }
        if file_type.is_dir() {
            scan_directory(root, &path, pages)?;
        } else {
            ensure!(file_type.is_file(), "page tree contains a special file");
            if path.extension().and_then(|extension| extension.to_str()) == Some("html") {
                ensure!(path.starts_with(root), "page escaped pages directory");
                pages.push(path);
            }
        }
    }
    Ok(())
}

fn parse_route(relative: &Path) -> Result<Vec<Segment>> {
    let mut components = relative
        .components()
        .map(|component| component.as_os_str().to_string_lossy().into_owned())
        .collect::<Vec<_>>();
    let file = components.pop().context("page path has no file name")?;
    let stem = file
        .strip_suffix(".html")
        .context("page must end in .html")?;
    if stem != "index" {
        components.push(stem.to_owned());
    }

    let mut segments = Vec::new();
    for component in components {
        if component.starts_with('(') && component.ends_with(')') {
            ensure!(component.len() > 2, "empty route group");
            continue;
        }
        segments.push(parse_segment(&component)?);
    }
    for (index, segment) in segments.iter().enumerate() {
        if matches!(segment, Segment::CatchAll(_) | Segment::OptionalCatchAll(_)) {
            ensure!(
                index + 1 == segments.len(),
                "catch-all route must be terminal"
            );
        }
    }
    let mut parameters = HashSet::new();
    for segment in &segments {
        let name = match segment {
            Segment::Parameter(name)
            | Segment::CatchAll(name)
            | Segment::OptionalCatchAll(name) => name,
            Segment::Static(_) => continue,
        };
        ensure!(parameters.insert(name), "route repeats parameter {name}");
    }
    Ok(segments)
}

fn read_page(path: &Path) -> Result<String> {
    let mut options = OpenOptions::new();
    options.read(true);
    #[cfg(unix)]
    {
        use std::os::unix::fs::OpenOptionsExt;
        options.custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC);
    }
    let mut file = options
        .open(path)
        .with_context(|| format!("open page without following symlinks {}", path.display()))?;
    let metadata = file.metadata()?;
    ensure!(metadata.is_file(), "page is not a regular file");
    #[cfg(unix)]
    {
        use std::os::unix::fs::MetadataExt;
        ensure!(metadata.nlink() == 1, "page cannot be hard-linked");
    }
    ensure!(metadata.len() <= 1024 * 1024, "page exceeds 1 MiB");
    let mut source = String::new();
    file.read_to_string(&mut source)
        .with_context(|| format!("read UTF-8 page {}", path.display()))?;
    Ok(source)
}

fn load_templates(root: &Path) -> Result<BTreeMap<String, Arc<str>>> {
    let Some(app_root) = root.parent() else {
        return Ok(BTreeMap::new());
    };
    let template_root = app_root.join("templates");
    match fs::symlink_metadata(&template_root) {
        Ok(metadata) => ensure!(
            metadata.is_dir() && !metadata.file_type().is_symlink(),
            "template root must be a real directory"
        ),
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(BTreeMap::new()),
        Err(error) => return Err(error.into()),
    }
    let mut paths = Vec::new();
    scan_directory(&template_root, &template_root, &mut paths)?;
    let mut templates = BTreeMap::new();
    for path in paths {
        let relative = path
            .strip_prefix(&template_root)
            .context("template escaped template root")?;
        let name = relative
            .components()
            .map(|component| {
                component
                    .as_os_str()
                    .to_str()
                    .context("template path must be UTF-8")
            })
            .collect::<Result<Vec<_>>>()?
            .join("/");
        ensure!(
            templates
                .insert(name.clone(), Arc::from(read_page(&path)?))
                .is_none(),
            "duplicate template {name}"
        );
    }
    Ok(templates)
}

fn load_special_page(
    root: &Path,
    name: &str,
    templates: &BTreeMap<String, Arc<str>>,
) -> Result<Option<Arc<str>>> {
    let path = root.join(name);
    match fs::symlink_metadata(&path) {
        Ok(_) => {
            let source = read_page(&path)?;
            let mut environment = template_environment(templates)?;
            environment.add_template_owned("page.html", source.clone())?;
            environment.get_template("page.html")?;
            Ok(Some(Arc::from(source)))
        }
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
        Err(error) => Err(error.into()),
    }
}

fn template_environment(templates: &BTreeMap<String, Arc<str>>) -> Result<Environment<'static>> {
    let mut environment = Environment::new();
    environment.set_auto_escape_callback(|name| {
        if name.ends_with(".html") {
            AutoEscape::Html
        } else {
            AutoEscape::None
        }
    });
    for (name, source) in templates {
        environment.add_template_owned(name.clone(), source.to_string())?;
    }
    Ok(environment)
}

fn parse_segment(value: &str) -> Result<Segment> {
    ensure!(!value.is_empty(), "route contains an empty segment");
    if let Some(name) = value
        .strip_prefix("[[...")
        .and_then(|name| name.strip_suffix("]]"))
    {
        validate_parameter(name)?;
        return Ok(Segment::OptionalCatchAll(name.to_owned()));
    }
    if let Some(name) = value
        .strip_prefix("[...")
        .and_then(|name| name.strip_suffix(']'))
    {
        validate_parameter(name)?;
        return Ok(Segment::CatchAll(name.to_owned()));
    }
    if let Some(name) = value
        .strip_prefix('[')
        .and_then(|name| name.strip_suffix(']'))
    {
        validate_parameter(name)?;
        return Ok(Segment::Parameter(name.to_owned()));
    }
    ensure!(
        !value.contains(['[', ']']) && value != "." && value != "..",
        "invalid static route segment: {value}"
    );
    Ok(Segment::Static(value.to_owned()))
}

fn validate_parameter(name: &str) -> Result<()> {
    ensure!(
        !name.is_empty()
            && name
                .bytes()
                .all(|byte| byte.is_ascii_alphanumeric() || byte == b'_'),
        "invalid route parameter: {name}"
    );
    Ok(())
}

fn match_segments(pattern: &[Segment], path: &[String]) -> Option<BTreeMap<String, String>> {
    let mut params = BTreeMap::new();
    let mut path_index = 0;
    for segment in pattern {
        match segment {
            Segment::Static(expected) => {
                if path.get(path_index)? != expected {
                    return None;
                }
                path_index += 1;
            }
            Segment::Parameter(name) => {
                params.insert(name.clone(), path.get(path_index)?.clone());
                path_index += 1;
            }
            Segment::CatchAll(name) => {
                if path_index >= path.len() {
                    return None;
                }
                params.insert(name.clone(), path[path_index..].join("/"));
                path_index = path.len();
            }
            Segment::OptionalCatchAll(name) => {
                params.insert(name.clone(), path[path_index..].join("/"));
                path_index = path.len();
            }
        }
    }
    (path_index == path.len()).then_some(params)
}

fn display_pattern(segments: &[Segment]) -> String {
    if segments.is_empty() {
        return "/".to_owned();
    }
    format!(
        "/{}",
        segments
            .iter()
            .map(|segment| match segment {
                Segment::Static(value) => value.clone(),
                Segment::Parameter(name) => format!("[{name}]"),
                Segment::CatchAll(name) => format!("[...{name}]"),
                Segment::OptionalCatchAll(name) => format!("[[...{name}]]"),
            })
            .collect::<Vec<_>>()
            .join("/")
    )
}

fn effective_pattern(segments: &[Segment]) -> String {
    segments
        .iter()
        .map(|segment| match segment {
            Segment::Static(value) => format!("s:{value}"),
            Segment::Parameter(_) => "p".to_owned(),
            Segment::CatchAll(_) => "c".to_owned(),
            Segment::OptionalCatchAll(_) => "o".to_owned(),
        })
        .collect::<Vec<_>>()
        .join("/")
}

fn compare_pages(left: &Page, right: &Page) -> std::cmp::Ordering {
    let rank = |segment: &Segment| match segment {
        Segment::Static(_) => 0_u8,
        Segment::Parameter(_) => 1,
        Segment::CatchAll(_) => 2,
        Segment::OptionalCatchAll(_) => 3,
    };
    left.segments
        .iter()
        .map(rank)
        .cmp(right.segments.iter().map(rank))
        .then_with(|| right.segments.len().cmp(&left.segments.len()))
        .then_with(|| left.pattern.cmp(&right.pattern))
}

pub fn validate_public_tree(root: &Path) -> Result<()> {
    artifact::inventory(root).context("validate native public files")?;
    Ok(())
}

fn default_search_limit() -> usize {
    10
}

async fn shutdown_signal() {
    let ctrl_c = async {
        if let Err(error) = tokio::signal::ctrl_c().await {
            error!(?error, "failed to install Ctrl-C handler");
        }
    };

    #[cfg(unix)]
    let terminate = async {
        match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) {
            Ok(mut signal) => {
                signal.recv().await;
            }
            Err(error) => error!(?error, "failed to install SIGTERM handler"),
        }
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        () = ctrl_c => {},
        () = terminate => {},
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use axum::{body::Body, http::Method};
    use tower::ServiceExt as _;

    #[tokio::test]
    async fn static_status_routes_are_stable_and_get_only() -> Result<()> {
        for path in [
            "/api/health",
            "/api/ready",
            "/_bunfork/health",
            "/_bunfork/ready",
        ] {
            let response = static_status_routes::<()>()
                .oneshot(Request::builder().uri(path).body(Body::empty())?)
                .await?;
            assert_eq!(response.status(), StatusCode::OK);
            assert_eq!(
                axum::body::to_bytes(response.into_body(), 1024).await?,
                r#"{"message":"ok"}"#
            );

            let response = static_status_routes::<()>()
                .oneshot(
                    Request::builder()
                        .method(Method::POST)
                        .uri(path)
                        .body(Body::empty())?,
                )
                .await?;
            assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED);
        }
        Ok(())
    }

    #[tokio::test]
    async fn native_readiness_maps_database_failure_to_unavailable() -> Result<()> {
        let response = readiness_response(Err(anyhow::anyhow!("database unavailable")));
        assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
        assert_eq!(
            axum::body::to_bytes(response.into_body(), 1024).await?,
            r#"{"message":"unavailable"}"#
        );

        let response = readiness_response(Ok(()));
        assert_eq!(response.status(), StatusCode::OK);
        Ok(())
    }

    #[test]
    fn static_routes_precede_dynamic_and_catch_all() -> Result<()> {
        let directory = tempfile::tempdir()?;
        let pages = directory.path();
        fs::create_dir_all(pages.join("docs"))?;
        fs::write(pages.join("index.html"), "home")?;
        fs::write(pages.join("[slug].html"), "dynamic")?;
        fs::write(pages.join("about.html"), "about")?;
        fs::write(pages.join("docs/[...path].html"), "docs")?;
        let router = PageRouter::scan(pages)?;

        let about = router.match_path("/about")?.context("about route")?;
        assert_eq!(about.template.as_ref(), "about");
        let dynamic = router.match_path("/hello")?.context("dynamic route")?;
        assert_eq!(
            dynamic.params.get("slug").map(String::as_str),
            Some("hello")
        );
        let docs = router.match_path("/docs/a/b")?.context("docs route")?;
        assert_eq!(docs.params.get("path").map(String::as_str), Some("a/b"));
        Ok(())
    }

    #[test]
    fn hidden_public_files_and_pages_are_rejected() -> Result<()> {
        let directory = tempfile::tempdir()?;
        let public = directory.path().join("public");
        let pages = directory.path().join("pages");
        fs::create_dir_all(&public)?;
        fs::create_dir_all(&pages)?;
        fs::write(public.join(".bunfork.key"), "secret")?;
        fs::write(pages.join(".secret.html"), "secret")?;
        assert!(validate_public_tree(&public).is_err());
        assert!(PageRouter::scan(&pages).is_err());
        assert!(has_hidden_segment("/assets/.bunfork.key"));
        assert!(has_hidden_segment("/assets/%2ebunfork.token"));
        Ok(())
    }

    #[test]
    fn public_origin_is_canonicalized() -> Result<()> {
        assert_eq!(
            validate_public_origin("https://example.com/")?,
            "https://example.com"
        );
        assert!(validate_public_origin("https://example.com/path").is_err());
        assert!(validate_public_origin("javascript:alert(1)").is_err());
        Ok(())
    }

    #[test]
    fn immutable_caching_requires_a_framework_semantic_namespace() {
        assert!(has_immutable_namespace("_app/immutable/start.C544yXVy.js"));
        assert!(has_immutable_namespace("_app/immutable/chunks/Bjy-W4x2.js"));
        assert!(has_immutable_namespace(
            "_next/static/chunks/472-abcdef123456.js"
        ));
        assert!(!has_immutable_namespace(
            "_next/static/service-worker/sw.js"
        ));
        assert!(!has_immutable_namespace(
            "_next/static/build-id/_buildManifest.js"
        ));
        assert!(!has_immutable_namespace("_nuxt/entry.D7oq8EHe.js"));
        assert!(!has_immutable_namespace("assets/index-GUtueyEN.js"));
        assert!(!has_immutable_namespace("assets/app-MarketingHero.js"));
    }

    #[test]
    fn encoding_quality_honors_zero_wildcards_and_preference() {
        assert_eq!(encoding_quality("br;q=0.0, gzip;q=0.8", "br"), 0);
        assert_eq!(encoding_quality("br;q=0.2, gzip;q=0.8", "gzip"), 800);
        assert_eq!(encoding_quality("*;q=0.4, br;q=0", "br"), 0);
        assert_eq!(encoding_quality("*;q=0.4, br;q=0", "gzip"), 400);
        assert_eq!(encoding_quality("gzip;q=bogus", "gzip"), 0);
    }

    #[test]
    fn native_public_assets_are_preloaded_and_paths_are_strict() -> Result<()> {
        let directory = tempfile::tempdir()?;
        fs::write(directory.path().join("app.css"), "original")?;
        let assets = preload_public_files(directory.path())?;
        fs::write(directory.path().join("app.css"), "changed")?;
        let asset = assets.get("app.css").context("preloaded asset")?;
        assert_eq!(asset.bytes, "original");
        assert_eq!(
            asset.etag,
            format!("W/\"{:x}\"", Sha256::digest(b"original"))
        );
        assert_eq!(
            decode_public_asset_path("/assets/icons/logo.svg")?,
            "icons/logo.svg"
        );
        assert!(decode_public_asset_path("/assets/%2e%2e/secret").is_err());
        assert!(decode_public_asset_path("/assets/icons%2flogo.svg").is_err());
        assert!(decode_public_asset_path("/assets/.token").is_err());
        Ok(())
    }

    #[test]
    fn routes_queries_and_templates_reject_ambiguous_input() -> Result<()> {
        let directory = tempfile::tempdir()?;
        let pages = directory.path();
        fs::create_dir_all(pages.join("[id]"))?;
        fs::write(pages.join("[id]/[id].html"), "duplicate")?;
        assert!(PageRouter::scan(pages).is_err());

        fs::remove_dir_all(pages.join("[id]"))?;
        fs::write(pages.join("index.html"), "{% if %}")?;
        assert!(PageRouter::scan(pages).is_err());

        fs::write(pages.join("index.html"), "valid")?;
        let router = PageRouter::scan(pages)?;
        assert!(router.match_path("/%2e%2e/secret").is_err());
        assert!(router.match_path("/a%2fb").is_err());

        let uri: Uri = "/?tag=a&tag=b&q=hello+world".parse()?;
        let query = parse_query(&uri)?;
        assert_eq!(
            query.get("tag"),
            Some(&vec!["a".to_owned(), "b".to_owned()])
        );
        assert_eq!(query.get("q"), Some(&vec!["hello world".to_owned()]));
        assert!(parse_query(&"/?bad=%zz".parse()?).is_err());
        Ok(())
    }
}