diff --git a/src/__tests__/convert-sanitized.test.ts b/src/__tests__/convert-sanitized.test.ts
new file mode 100644
index 0000000..58002fd
--- /dev/null
+++ b/src/__tests__/convert-sanitized.test.ts
@@ -0,0 +1,98 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import express from "express";
+import request from "supertest";
+
+vi.mock("node:dns/promises", () => ({
+ default: { lookup: vi.fn() },
+ lookup: vi.fn(),
+}));
+
+let app: express.Express;
+
+beforeEach(async () => {
+ vi.clearAllMocks();
+ vi.resetModules();
+
+ const { renderPdf, renderUrlPdf } = await import("../services/browser.js");
+ vi.mocked(renderPdf).mockResolvedValue(Buffer.from("%PDF-1.4 mock"));
+ vi.mocked(renderUrlPdf).mockResolvedValue(Buffer.from("%PDF-1.4 mock url"));
+
+ const dns = await import("node:dns/promises");
+ vi.mocked(dns.default.lookup).mockResolvedValue({ address: "93.184.216.34", family: 4 } as any);
+
+ const { convertRouter } = await import("../routes/convert.js");
+ const { demoRouter } = await import("../routes/demo.js");
+ app = express();
+ app.use(express.json({ limit: "500kb" }));
+ app.use("/v1/convert", convertRouter);
+ app.use("/v1/demo", demoRouter);
+});
+
+describe("convert routes use sanitized PDF options", () => {
+ it("POST /v1/convert/html passes sanitized format (a4 → A4)", async () => {
+ const { renderPdf } = await import("../services/browser.js");
+
+ await request(app)
+ .post("/v1/convert/html")
+ .set("content-type", "application/json")
+ .send({ html: "
Test
", format: "a4" });
+
+ expect(vi.mocked(renderPdf)).toHaveBeenCalledOnce();
+ const opts = vi.mocked(renderPdf).mock.calls[0][1];
+ expect(opts.format).toBe("A4");
+ });
+
+ it("POST /v1/convert/markdown passes sanitized format (letter → Letter)", async () => {
+ const { renderPdf } = await import("../services/browser.js");
+
+ await request(app)
+ .post("/v1/convert/markdown")
+ .set("content-type", "application/json")
+ .send({ markdown: "# Test", format: "letter" });
+
+ expect(vi.mocked(renderPdf)).toHaveBeenCalledOnce();
+ const opts = vi.mocked(renderPdf).mock.calls[0][1];
+ expect(opts.format).toBe("Letter");
+ });
+
+ it("POST /v1/convert/url passes sanitized format (a3 → A3)", async () => {
+ const { renderUrlPdf } = await import("../services/browser.js");
+
+ await request(app)
+ .post("/v1/convert/url")
+ .set("content-type", "application/json")
+ .send({ url: "https://example.com", format: "a3" });
+
+ expect(vi.mocked(renderUrlPdf)).toHaveBeenCalledOnce();
+ const opts = vi.mocked(renderUrlPdf).mock.calls[0][1];
+ expect(opts.format).toBe("A3");
+ });
+});
+
+describe("demo routes use sanitized PDF options", () => {
+ it("POST /v1/demo/html passes sanitized format (a4 → A4)", async () => {
+ const { renderPdf } = await import("../services/browser.js");
+
+ await request(app)
+ .post("/v1/demo/html")
+ .set("content-type", "application/json")
+ .send({ html: "Test
", format: "a4" });
+
+ expect(vi.mocked(renderPdf)).toHaveBeenCalledOnce();
+ const opts = vi.mocked(renderPdf).mock.calls[0][1];
+ expect(opts.format).toBe("A4");
+ });
+
+ it("POST /v1/demo/markdown passes sanitized format (a4 → A4)", async () => {
+ const { renderPdf } = await import("../services/browser.js");
+
+ await request(app)
+ .post("/v1/demo/markdown")
+ .set("content-type", "application/json")
+ .send({ markdown: "# Test", format: "a4" });
+
+ expect(vi.mocked(renderPdf)).toHaveBeenCalledOnce();
+ const opts = vi.mocked(renderPdf).mock.calls[0][1];
+ expect(opts.format).toBe("A4");
+ });
+});
diff --git a/src/routes/convert.ts b/src/routes/convert.ts
index 8e46885..684c873 100644
--- a/src/routes/convert.ts
+++ b/src/routes/convert.ts
@@ -114,18 +114,7 @@ convertRouter.post("/html", async (req: Request & { acquirePdfSlot?: () => Promi
: wrapHtml(body.html, body.css);
const pdf = await renderPdf(fullHtml, {
- format: body.format,
- landscape: body.landscape,
- margin: body.margin,
- printBackground: body.printBackground,
- headerTemplate: body.headerTemplate,
- footerTemplate: body.footerTemplate,
- displayHeaderFooter: body.displayHeaderFooter,
- scale: body.scale,
- pageRanges: body.pageRanges,
- preferCSSPageSize: body.preferCSSPageSize,
- width: body.width,
- height: body.height,
+ ...validation.sanitized,
});
const filename = sanitizeFilename(body.filename || "document.pdf");
@@ -226,18 +215,7 @@ convertRouter.post("/markdown", async (req: Request & { acquirePdfSlot?: () => P
const html = markdownToHtml(body.markdown, body.css);
const pdf = await renderPdf(html, {
- format: body.format,
- landscape: body.landscape,
- margin: body.margin,
- printBackground: body.printBackground,
- headerTemplate: body.headerTemplate,
- footerTemplate: body.footerTemplate,
- displayHeaderFooter: body.displayHeaderFooter,
- scale: body.scale,
- pageRanges: body.pageRanges,
- preferCSSPageSize: body.preferCSSPageSize,
- width: body.width,
- height: body.height,
+ ...validation.sanitized,
});
const filename = sanitizeFilename(body.filename || "document.pdf");
@@ -368,19 +346,7 @@ convertRouter.post("/url", async (req: Request & { acquirePdfSlot?: () => Promis
}
const pdf = await renderUrlPdf(body.url, {
- format: body.format,
- landscape: body.landscape,
- margin: body.margin,
- printBackground: body.printBackground,
- headerTemplate: body.headerTemplate,
- footerTemplate: body.footerTemplate,
- displayHeaderFooter: body.displayHeaderFooter,
- scale: body.scale,
- pageRanges: body.pageRanges,
- preferCSSPageSize: body.preferCSSPageSize,
- width: body.width,
- height: body.height,
- waitUntil: body.waitUntil,
+ ...validation.sanitized,
hostResolverRules: `MAP ${parsed.hostname} ${resolvedAddress}`,
});
diff --git a/src/routes/demo.ts b/src/routes/demo.ts
index 8e5a28f..ab1862f 100644
--- a/src/routes/demo.ts
+++ b/src/routes/demo.ts
@@ -127,11 +127,15 @@ router.post("/html", async (req: Request & { acquirePdfSlot?: () => Promise Promise<
const htmlContent = markdownToHtml(body.markdown);
const fullHtml = injectWatermark(wrapHtml(htmlContent, body.css));
+ const defaultOpts = {
+ format: "A4",
+ landscape: false,
+ printBackground: true,
+ margin: { top: "0", right: "0", bottom: "0", left: "0" },
+ };
const pdf = await renderPdf(fullHtml, {
- format: body.format || "A4",
- landscape: body.landscape || false,
- printBackground: body.printBackground !== false,
- margin: body.margin || { top: "0", right: "0", bottom: "0", left: "0" },
+ ...defaultOpts,
+ ...validation.sanitized,
});
const filename = sanitizeFilename(body.filename || "demo.pdf");