use axum::http::Uri; use axum::http::{HeaderMap, Method, StatusCode, header}; use axum::response::{IntoResponse, Response}; const DEV_HINT: &str = r#" filebrowser-ng

filebrowser-ng

The frontend has not been built yet.

In development, run just dev-web in a second terminal and open http://localhost:8080 (Trunk serves the app and proxies /api to this backend).

If you hit this page directly from the backend, make sure Trunk's output directory is visible — the server looks in target/web or $FBNG_DIST.

For the production single binary: just build.

"#; /// Serve the frontend: static assets, with a fallback to index.html so that /// client-side routes (e.g. `/s/`) work. pub(super) async fn fallback(method: Method, uri: Uri, headers: HeaderMap) -> Response { let path = uri.path(); if path.starts_with("/api/") { return (StatusCode::NOT_FOUND, "unknown endpoint").into_response(); } // CalDAV and CardDAV clients given only the server's address ask it for // the principal (RFC 6764, 6). if path == "/" { match method.as_str() { "PROPFIND" => { return ( StatusCode::TEMPORARY_REDIRECT, [(header::LOCATION, format!("{}/", api_types::PIM))], ) .into_response(); } "OPTIONS" => { return ( StatusCode::OK, [ (header::ALLOW, "OPTIONS, GET, HEAD, PROPFIND"), ( header::HeaderName::from_static("dav"), super::pim::COMPLIANCE, ), ], ) .into_response(); } _ => {} } } if method != Method::GET && method != Method::HEAD { return StatusCode::METHOD_NOT_ALLOWED.into_response(); } let rel = path.trim_start_matches('/'); let asset = if rel.is_empty() { crate::assets::get_asset("index.html") } else { crate::assets::get_asset(rel) }; match asset { Some((bytes, mime, cache, etag)) => { // The embedded assets carry a content hash, so a repeat visit can // be answered without sending the body again. if let Some(tag) = &etag && headers .get(header::IF_NONE_MATCH) .and_then(|v| v.to_str().ok()) .is_some_and(|v| v.split(',').any(|t| t.trim() == tag || t.trim() == "*")) { return ( StatusCode::NOT_MODIFIED, [(header::CACHE_CONTROL, cache), (header::ETAG, tag.clone())], ) .into_response(); } let mut res = ( [(header::CONTENT_TYPE, mime), (header::CACHE_CONTROL, cache)], bytes, ) .into_response(); if let Some(tag) = etag && let Ok(v) = tag.parse() { res.headers_mut().insert(header::ETAG, v); } res } None => match crate::assets::get_asset("index.html") { Some((bytes, mime, _, _)) => ( [ (header::CONTENT_TYPE, mime), (header::CACHE_CONTROL, "no-cache".to_string()), ], bytes, ) .into_response(), None => DEV_HINT.into_response(), }, } }