use std::io;
use std::path::{Path, PathBuf};
use super::safety::{content_type_for_path, ensure_no_parent_dir_components};
use crate::serve::shim::SHIM_BYTES;
fn inject_shim_inline(body: Vec<u8>) -> Vec<u8> {
let open = b"<script";
if !body.windows(open.len()).any(|w| w == open) {
return body;
}
let mut out =
Vec::with_capacity(body.len() + SHIM_BYTES.len() + open.len() + b"></script>".len());
let mut injected = false;
let mut i = 0usize;
while i < body.len() {
if !injected && body[i..].starts_with(&open[..]) {
out.extend_from_slice(b"<script>");
out.extend_from_slice(SHIM_BYTES);
out.extend_from_slice(b"</script>");
let tag_end = body[i..]
.iter()
.position(|&b| b == b'>')
.map(|p| i + p + 1)
.unwrap_or(body.len());
out.extend_from_slice(&body[i..tag_end]);
let mut j = tag_end;
while j < body.len()
&& (body[j] == b'\n' || body[j] == b' ' || body[j] == b'\t' || body[j] == b'\r')
{
out.push(body[j]);
j += 1;
}
i = j;
injected = true;
} else {
out.push(body[i]);
i += 1;
}
}
out
}
fn has_script_tag(body: &[u8]) -> bool {
body.windows(b"<script".len()).any(|w| w == b"<script")
}
pub fn serve_file(root: &Path, requested: &Path) -> Result<super::ServedFile, super::ServeError> {
let canonical_root = std::fs::canonicalize(root).map_err(super::ServeError::Io)?;
let target = if requested.is_absolute() {
requested.to_path_buf()
} else {
canonical_root.join(requested)
};
ensure_no_parent_dir_components(&target)?;
let meta = std::fs::metadata(&target).map_err(|e| {
if e.kind() == io::ErrorKind::NotFound {
super::ServeError::NotFound
} else {
super::ServeError::Io(e)
}
})?;
if !meta.is_file() {
return Err(super::ServeError::NotAFile);
}
let body = std::fs::read(&target).map_err(super::ServeError::Io)?;
let content_type = content_type_for_path(&target);
let body = if content_type == super::ContentType::Html && has_script_tag(&body) {
inject_shim_inline(body)
} else {
body
};
Ok(super::ServedFile { body, content_type })
}