import { HIBP_CODES } from "../error-codes.js"; import { HibpError } from "../errors/hibp-error.js"; /** Strict-mode framing: uppercase hex, single colon, decimal count. */ const STRICT_LINE_RE = /^[0-9A-F]{35}:[0-9]+$/; /** * One parsed line from a range response: the 35-char hex suffix plus its * documented breach count. */ export interface RangeLine { readonly suffix: string; readonly count: number; } /** * Validate and split a raw line. In strict mode, malformed lines throw * `HIBP.MALFORMED_RESPONSE` with the offending zero-based line index. In * off-mode, lowercase hex is accepted (uppercased on the way out), and an * unparseable count defaults to `0`. */ export function parseRangeLine( raw: string, lineIndex: number, strict: boolean ): RangeLine { if (strict) { if (!STRICT_LINE_RE.test(raw)) { throw new HibpError( HIBP_CODES.MALFORMED_RESPONSE, `malformed range line at index ${lineIndex}`, { context: { lineIndex } } ); } const idx = raw.indexOf(":"); return { suffix: raw.slice(0, idx), count: Number.parseInt(raw.slice(idx + 1), 10), }; } const idx = raw.indexOf(":"); if (idx === -1) { throw new HibpError( HIBP_CODES.MALFORMED_RESPONSE, `malformed range line at index ${lineIndex}`, { context: { lineIndex } } ); } const suffix = raw.slice(0, idx).toUpperCase(); if (suffix.length !== 35 || !/^[0-9A-F]{35}$/.test(suffix)) { throw new HibpError( HIBP_CODES.MALFORMED_RESPONSE, `malformed range line at index ${lineIndex}`, { context: { lineIndex } } ); } const tail = raw.slice(idx + 1); const parsed = Number.parseInt(tail, 10); const count = Number.isFinite(parsed) && parsed >= 0 ? parsed : 0; return { suffix, count }; }