import type { TransformRequest, TransformResult } from './transformTypes'; export const TRANSFORM_LIMITS = { maxXmlBytes: 5 * 1024 * 1024, maxXsltBytes: 2 * 1024 * 1024, maxOutputBytes: 10 * 1024 * 1024, timeoutMs: 20_000, } as const; function utf8Bytes(value: string): number { return new TextEncoder().encode(value).byteLength; } export function assertTransformInputWithinLimits( request: TransformRequest ): void { const xmlBytes = utf8Bytes(request.xmlText); if (xmlBytes > TRANSFORM_LIMITS.maxXmlBytes) { throw new Error( `XML input exceeds the ${TRANSFORM_LIMITS.maxXmlBytes}-byte execution limit.` ); } const xsltBytes = utf8Bytes(request.xsltText); if (xsltBytes > TRANSFORM_LIMITS.maxXsltBytes) { throw new Error( `XSLT stylesheet exceeds the ${TRANSFORM_LIMITS.maxXsltBytes}-byte execution limit.` ); } } export function assertTransformOutputWithinLimits( result: TransformResult ): void { if (utf8Bytes(result.output) > TRANSFORM_LIMITS.maxOutputBytes) { throw new Error( `Transformation output exceeds the ${TRANSFORM_LIMITS.maxOutputBytes}-byte execution limit.` ); } } export async function withTransformationTimeout( operation: () => Promise, timeoutMs: number = TRANSFORM_LIMITS.timeoutMs ): Promise { let timer: number | undefined; try { return await Promise.race([ Promise.resolve().then(operation), new Promise((_resolve, reject) => { timer = window.setTimeout( () => reject( new Error( `Transformation exceeded the ${timeoutMs}-millisecond execution limit.` ) ), timeoutMs ); }), ]); } finally { if (timer !== undefined) window.clearTimeout(timer); } }