diff --git a/src/__tests__/demo.test.ts b/src/__tests__/demo.test.ts
index e613bfe..295f90c 100644
--- a/src/__tests__/demo.test.ts
+++ b/src/__tests__/demo.test.ts
@@ -83,6 +83,42 @@ describe("POST /v1/demo/html", () => {
expect(calledHtml).toContain("DEMO");
expect(calledHtml).toContain("docfast.dev");
});
+
+ it("returns 400 for invalid scale", async () => {
+ const res = await request(app)
+ .post("/v1/demo/html")
+ .set("content-type", "application/json")
+ .send({ html: "
Hello
", scale: 99 });
+ expect(res.status).toBe(400);
+ expect(res.body.error).toMatch(/scale/);
+ });
+
+ it("returns 400 for invalid format", async () => {
+ const res = await request(app)
+ .post("/v1/demo/html")
+ .set("content-type", "application/json")
+ .send({ html: "Hello
", format: "INVALID" });
+ expect(res.status).toBe(400);
+ expect(res.body.error).toMatch(/format/);
+ });
+
+ it("returns 400 for non-boolean landscape", async () => {
+ const res = await request(app)
+ .post("/v1/demo/html")
+ .set("content-type", "application/json")
+ .send({ html: "Hello
", landscape: "yes" });
+ expect(res.status).toBe(400);
+ expect(res.body.error).toMatch(/landscape/);
+ });
+
+ it("returns 400 for invalid margin", async () => {
+ const res = await request(app)
+ .post("/v1/demo/html")
+ .set("content-type", "application/json")
+ .send({ html: "Hello
", margin: "10px" });
+ expect(res.status).toBe(400);
+ expect(res.body.error).toMatch(/margin/);
+ });
});
describe("POST /v1/demo/markdown", () => {
@@ -145,4 +181,22 @@ describe("POST /v1/demo/markdown", () => {
expect(calledHtml).toContain("DEMO");
expect(calledHtml).toContain("docfast.dev");
});
+
+ it("returns 400 for invalid scale", async () => {
+ const res = await request(app)
+ .post("/v1/demo/markdown")
+ .set("content-type", "application/json")
+ .send({ markdown: "# Hello", scale: 99 });
+ expect(res.status).toBe(400);
+ expect(res.body.error).toMatch(/scale/);
+ });
+
+ it("returns 400 for invalid format", async () => {
+ const res = await request(app)
+ .post("/v1/demo/markdown")
+ .set("content-type", "application/json")
+ .send({ markdown: "# Hello", format: "INVALID" });
+ expect(res.status).toBe(400);
+ expect(res.body.error).toMatch(/format/);
+ });
});
diff --git a/src/routes/demo.ts b/src/routes/demo.ts
index fb8a094..8e5a28f 100644
--- a/src/routes/demo.ts
+++ b/src/routes/demo.ts
@@ -3,6 +3,8 @@ import rateLimit from "express-rate-limit";
import { renderPdf } from "../services/browser.js";
import { markdownToHtml, wrapHtml } from "../services/markdown.js";
import logger from "../services/logger.js";
+import { sanitizeFilename } from "../utils/sanitize.js";
+import { validatePdfOptions } from "../utils/pdf-options.js";
const router = Router();
@@ -42,10 +44,6 @@ interface DemoBody {
filename?: string;
}
-function sanitizeFilename(name: string): string {
- return name.replace(/[\x00-\x1f"\\\r\n]/g, "").trim() || "document.pdf";
-}
-
/**
* @openapi
* /v1/demo/html:
@@ -114,6 +112,12 @@ router.post("/html", async (req: Request & { acquirePdfSlot?: () => Promise Promise<
return;
}
+ const validation = validatePdfOptions(body);
+ if (!validation.valid) {
+ res.status(400).json({ error: validation.error });
+ return;
+ }
+
if (req.acquirePdfSlot) {
await req.acquirePdfSlot();
slotAcquired = true;