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"),
}
})
}