syntaxai/tdd.md · main · src / d21_handlers_source.ts

d21_handlers_source.ts 39 lines · 1813 bytes raw
// c21 — handler: serves the raw markdown source of an editable doc
// page from the main domain. Replaces the previous "view source on
// git.tdd.md" link so the docs site doesn't depend on the Forgejo
// subdomain for "view source". Reuses c32_edit_resolve so the same
// allowlist (sama / guides / blog + safe slug regex) protects both
// the editor and the raw view from path traversal.

import { resolveEdit } from "./b32_edit_resolve.ts";
import { renderNotFound, htmlResponse } from "./b51_render_layout.ts";

// The route literal is `/content/:section/:filename` and the handler
// requires the filename to end in `.md`. We don't use `:slug.md`
// because Bun's path parser treats that as a single param literally
// named "slug.md", which makes the URL un-typeable.
export const rawSourceHandler = async (
  req: Request & { params: { section: string; filename: string } },
): Promise<Response> => {
  const fullPath = `/content/${req.params.section}/${req.params.filename}`;
  const notFound = async (): Promise<Response> => {
    const html = await renderNotFound(fullPath);
    return htmlResponse(html, 404);
  };
  if (!req.params.filename.endsWith(".md")) return await notFound();
  const slug = req.params.filename.slice(0, -3);
  const resolved = resolveEdit(req.params.section, slug);
  if (!resolved) return await notFound();
  const file = Bun.file(`./${resolved.filePath}`);
  if (!(await file.exists())) return await notFound();
  // text/plain so browsers render the markdown source inline rather
  // than offering a download. UTF-8 is fixed because the content/ dir
  // is UTF-8 throughout (verified by sama-verify).
  return new Response(await file.text(), {
    headers: {
      "Content-Type": "text/plain; charset=utf-8",
      "Cache-Control": "public, max-age=60",
    },
  });
};