62 lines
1.9 KiB
TypeScript
62 lines
1.9 KiB
TypeScript
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 };
|
|
}
|