use super::Site;
use crate::door::http::{Request, Response};
use crate::door::packed::{IMMUTABLE, hashed, takes_gzip};
use serde_json::json;
use std::path::{Component, Path};
pub(super) fn file(site: &Site, request: &Request) -> Response {
let (files, path) = (&site.files, request.path.as_str());
let relative = Path::new(path.trim_start_matches('/'));
let plain = relative
.components()
.all(|component| matches!(component, Component::Normal(_)))
&& !path.contains(['%', '\\']);
let missing = || {
Response::json(
404,
&json!({"code": "file_unknown", "message": "Studio has no such file"}),
)
};
if !plain {
return missing();
}
let wanted = files.join(relative);
let (target, kind) = wanted
.extension()
.and_then(|extension| extension.to_str())
.map_or_else(
|| (files.join("index.html"), "html".to_owned()),
|extension| (wanted.clone(), extension.to_owned()),
);
let content_type = match kind.as_str() {
"html" => "text/html; charset=utf-8",
"js" | "mjs" => "text/javascript; charset=utf-8",
"css" => "text/css; charset=utf-8",
"json" | "map" => "application/json",
"svg" => "image/svg+xml",
"png" => "image/png",
"woff2" => "font/woff2",
"wasm" => "application/wasm",
_ => "application/octet-stream",
};
let compressible = matches!(
kind.as_str(),
"html" | "js" | "mjs" | "css" | "json" | "map" | "svg" | "wasm"
);
let gzip = compressible && takes_gzip(request.header("accept-encoding"));
let immutable = target == wanted && hashed(path);
let packed = &site.packed;
let body = if gzip {
packed
.gzipped(&target, immutable)
.map(|bytes| bytes.to_vec())
} else {
std::fs::read(&target)
};
let Ok(body) = body else {
return missing();
};
let mut response = Response::new(200, content_type, body);
if compressible {
response = response.with("Vary", "Accept-Encoding");
}
if gzip {
response = response.with("Content-Encoding", "gzip");
}
if immutable {
response = response.cached(IMMUTABLE);
}
response
}