AkurAI Build
Menu

Public repository / master

AkurAI-Notes / crates/notes-api/src/handlers/static_files.rs

Latest change 9846363ea7fea56c5079985945447933709e6726 - fix: restore Notes app SSR routes by Claude Code

use crate::router_setup::{internal_error, not_found, NotesState};
use akurai_http::{Request, Response};
use std::sync::{Arc, Mutex};

pub fn static_file_route(
    state: Arc<Mutex<NotesState>>,
) -> Box<dyn Fn(&Request) -> Response + Send + Sync> {
    Box::new(move |req: &Request| {
        let path = &req.path;
        let state = match state.lock() {
            Ok(s) => s,
            Err(_) => return internal_error("lock poisoned"),
        };
        let frontend = &state.frontend_dir;

        let file_path = if path == "/" {
            frontend.join("index.html")
        } else {
            let clean = path.trim_start_matches('/');
            frontend.join(clean)
        };

        // SSR owns application pages. Do not serve raw template files as a
        // fallback, because unrendered {% ... %} / {{ ... }} tags are user-visible.
        if !file_path.exists() || !file_path.is_file() {
            return not_found("file not found");
        }

        // Path traversal check for real files only
        let canonical = std::fs::canonicalize(&file_path).ok();
        let frontend_canonical = std::fs::canonicalize(frontend)
            .unwrap_or_else(|_| std::path::PathBuf::from("/nonexistent"));
        let is_safe = canonical
            .as_ref()
            .is_some_and(|c| c.starts_with(&frontend_canonical));

        if !is_safe {
            return not_found("invalid path");
        }

        let ext = file_path.extension().and_then(|e| e.to_str()).unwrap_or("");
        let mime = match ext {
            "html" => "text/html; charset=utf-8",
            "css" => "text/css; charset=utf-8",
            "js" => "application/javascript; charset=utf-8",
            "json" => "application/json",
            "png" => "image/png",
            "jpg" | "jpeg" => "image/jpeg",
            "svg" => "image/svg+xml",
            "ico" => "image/x-icon",
            "woff2" => "font/woff2",
            _ => "application/octet-stream",
        };

        match std::fs::read(&file_path) {
            Ok(bytes) => Response::ok()
                .with_header("Content-Type", mime)
                .with_body(mime, bytes),
            Err(_) => not_found("file not found"),
        }
    })
}