const VALID_FORMATS = ["Letter", "Legal", "Tabloid", "Ledger", "A0", "A1", "A2", "A3", "A4", "A5", "A6"]; const FORMAT_MAP = new Map(VALID_FORMATS.map(f => [f.toLowerCase(), f])); const PAGE_RANGES_RE = /^\d+(-\d*)?(\s*,\s*\d+(-\d*)?)*$/; const MARGIN_KEYS = new Set(["top", "right", "bottom", "left"]); const VALID_WAIT_UNTIL = ["load", "domcontentloaded", "networkidle0", "networkidle2"]; const MAX_TEMPLATE_SIZE = 102400; // 100KB in characters type PdfInput = Record; type ValidResult = { valid: true; sanitized: Record }; type InvalidResult = { valid: false; error: string }; export function validatePdfOptions(opts: PdfInput): ValidResult | InvalidResult { if (!opts || typeof opts !== "object") return { valid: true, sanitized: {} }; const sanitized: Record = {}; // scale if (opts.scale !== undefined) { if (typeof opts.scale !== "number" || opts.scale < 0.1 || opts.scale > 2.0) { return { valid: false, error: "scale must be a number between 0.1 and 2.0" }; } sanitized.scale = opts.scale; } // format if (opts.format !== undefined) { if (typeof opts.format !== "string") { return { valid: false, error: "format must be a string" }; } const canonical = FORMAT_MAP.get(opts.format.toLowerCase()); if (!canonical) { return { valid: false, error: `format must be one of: ${VALID_FORMATS.join(", ")}` }; } sanitized.format = canonical; } // booleans for (const field of ["landscape", "printBackground", "displayHeaderFooter", "preferCSSPageSize"]) { if (opts[field] !== undefined) { if (typeof opts[field] !== "boolean") { return { valid: false, error: `${field} must be a boolean` }; } sanitized[field] = opts[field]; } } // width/height for (const field of ["width", "height"]) { if (opts[field] !== undefined) { if (typeof opts[field] !== "string") { return { valid: false, error: `${field} must be a string (CSS dimension)` }; } sanitized[field] = opts[field]; } } // margin if (opts.margin !== undefined) { if (typeof opts.margin !== "object" || opts.margin === null || Array.isArray(opts.margin)) { return { valid: false, error: "margin must be an object with top/right/bottom/left string fields" }; } for (const key of Object.keys(opts.margin)) { if (!MARGIN_KEYS.has(key)) { return { valid: false, error: `margin contains unknown key: ${key}` }; } if (typeof opts.margin[key] !== "string") { return { valid: false, error: `margin.${key} must be a string` }; } } sanitized.margin = { ...opts.margin }; } // pageRanges if (opts.pageRanges !== undefined) { if (typeof opts.pageRanges !== "string") { return { valid: false, error: "pageRanges must be a string" }; } if (!PAGE_RANGES_RE.test(opts.pageRanges.trim())) { return { valid: false, error: "pageRanges must match pattern like '1-5', '1,3,5', or '2-'" }; } sanitized.pageRanges = opts.pageRanges; } // waitUntil if (opts.waitUntil !== undefined) { if (typeof opts.waitUntil !== "string") { return { valid: false, error: "waitUntil must be a string" }; } if (!VALID_WAIT_UNTIL.includes(opts.waitUntil)) { return { valid: false, error: `waitUntil must be one of: ${VALID_WAIT_UNTIL.join(", ")}` }; } sanitized.waitUntil = opts.waitUntil; } // headerTemplate if (opts.headerTemplate !== undefined) { if (typeof opts.headerTemplate !== "string") { return { valid: false, error: "headerTemplate must be a string" }; } if (opts.headerTemplate.length > MAX_TEMPLATE_SIZE) { return { valid: false, error: "headerTemplate must not exceed 100KB" }; } sanitized.headerTemplate = opts.headerTemplate; } // footerTemplate if (opts.footerTemplate !== undefined) { if (typeof opts.footerTemplate !== "string") { return { valid: false, error: "footerTemplate must be a string" }; } if (opts.footerTemplate.length > MAX_TEMPLATE_SIZE) { return { valid: false, error: "footerTemplate must not exceed 100KB" }; } sanitized.footerTemplate = opts.footerTemplate; } return { valid: true, sanitized }; }