diff --git a/ApiCore-Backend/src/automation/number-format.spec.ts b/ApiCore-Backend/src/automation/number-format.spec.ts index 23665f3..f122531 100644 --- a/ApiCore-Backend/src/automation/number-format.spec.ts +++ b/ApiCore-Backend/src/automation/number-format.spec.ts @@ -28,4 +28,11 @@ describe('formatGeneratedNumber', () => { expect(result).toBe('FIXED-NUMBER'); }); + + it('clamps a pathologically large {SEQ:N} width instead of allocating it', () => { + const result = formatGeneratedNumber('L-{SEQ:999999999}', 7, date); + + expect(result).toBe(`L-${'0'.repeat(19)}7`); + expect(result.length).toBe(22); + }); }); diff --git a/ApiCore-Backend/src/automation/number-format.ts b/ApiCore-Backend/src/automation/number-format.ts index a916819..a041b10 100644 --- a/ApiCore-Backend/src/automation/number-format.ts +++ b/ApiCore-Backend/src/automation/number-format.ts @@ -1,3 +1,10 @@ +// Far beyond any realistic business use (sequence numbers are a handful of +// digits) but small enough that padStart can never be tricked into +// allocating a pathologically large string - {SEQ:999999999} in a +// carelessly typed format would otherwise try to build a ~1GB string on +// every single batch/bottling creation from then on. +const MAX_SEQUENCE_PAD_WIDTH = 20; + // Placeholders: {YYYY} 4-digit year, {YY} 2-digit year, {MM} month (01-12), // {DD} day (01-31), {SEQ} running sequence unpadded, {SEQ:N} sequence // zero-padded to N digits. Example matching the lastenheft's own sample @@ -18,7 +25,10 @@ export function formatGeneratedNumber( .replace(/\{MM\}/g, mm) .replace(/\{DD\}/g, dd) .replace(/\{SEQ:(\d+)\}/g, (_match, width: string) => - String(sequence).padStart(Number(width), '0'), + String(sequence).padStart( + Math.min(Number(width), MAX_SEQUENCE_PAD_WIDTH), + '0', + ), ) .replace(/\{SEQ\}/g, String(sequence)); } diff --git a/ApiCore-Frontend/src/lib/automation/number-format.ts b/ApiCore-Frontend/src/lib/automation/number-format.ts index 351da39..332c2e2 100644 --- a/ApiCore-Frontend/src/lib/automation/number-format.ts +++ b/ApiCore-Frontend/src/lib/automation/number-format.ts @@ -4,6 +4,11 @@ // Placeholders: {YYYY} 4-digit year, {YY} 2-digit year, {MM} month (01-12), // {DD} day (01-31), {SEQ} running sequence unpadded, {SEQ:N} sequence // zero-padded to N digits. +// Same cap as the backend: without it, typing {SEQ:999999999} while +// editing the format would try to build a multi-hundred-MB string on +// every keystroke and freeze the settings page. +const MAX_SEQUENCE_PAD_WIDTH = 20; + export function formatGeneratedNumber(format: string, sequence: number, date: Date): string { const yyyy = String(date.getFullYear()); const yy = yyyy.slice(-2); @@ -15,6 +20,8 @@ export function formatGeneratedNumber(format: string, sequence: number, date: Da .replace(/\{YY\}/g, yy) .replace(/\{MM\}/g, mm) .replace(/\{DD\}/g, dd) - .replace(/\{SEQ:(\d+)\}/g, (_match, width: string) => String(sequence).padStart(Number(width), "0")) + .replace(/\{SEQ:(\d+)\}/g, (_match, width: string) => + String(sequence).padStart(Math.min(Number(width), MAX_SEQUENCE_PAD_WIDTH), "0"), + ) .replace(/\{SEQ\}/g, String(sequence)); }