Process and API-only Playwright harness: binary and image setup, server/CLI runners, fake Docker Engine socket, read-only database inspection, secret redaction in reports, seeded fuzzing, leak detection and harness self-checks.
169 lines
5.2 KiB
TypeScript
169 lines
5.2 KiB
TypeScript
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
// Copyright (C) 2026 Bryan Joshua Pedini
|
|
|
|
/**
|
|
* Go string quoting/unquoting compatible with strconv.Quote / strconv.Unquote,
|
|
* as used by `token list` (DESCRIPTION column), slog's text handler and the
|
|
* configuration error messages.
|
|
*/
|
|
|
|
/** Characters Go's strconv.IsPrint accepts: letters, marks, numbers, punctuation, symbols, and ASCII space. */
|
|
const PRINTABLE = /^[\p{L}\p{M}\p{N}\p{P}\p{S} ]$/u;
|
|
|
|
/**
|
|
* Go strconv.Quote of a byte string (Buffer) or a JS string (encoded as UTF-8).
|
|
* Note: relies on Node's Unicode tables; avoid code points newer than Go's tables in tests.
|
|
*/
|
|
export function goQuote(input: string | Buffer): string {
|
|
const bytes = typeof input === 'string' ? Buffer.from(input, 'utf8') : input;
|
|
let out = '"';
|
|
let i = 0;
|
|
while (i < bytes.length) {
|
|
const b = bytes[i];
|
|
if (b < 0x80) {
|
|
i++;
|
|
const c = String.fromCharCode(b);
|
|
switch (c) {
|
|
case '"':
|
|
out += '\\"';
|
|
continue;
|
|
case '\\':
|
|
out += '\\\\';
|
|
continue;
|
|
case '\x07':
|
|
out += '\\a';
|
|
continue;
|
|
case '\b':
|
|
out += '\\b';
|
|
continue;
|
|
case '\f':
|
|
out += '\\f';
|
|
continue;
|
|
case '\n':
|
|
out += '\\n';
|
|
continue;
|
|
case '\r':
|
|
out += '\\r';
|
|
continue;
|
|
case '\t':
|
|
out += '\\t';
|
|
continue;
|
|
case '\v':
|
|
out += '\\v';
|
|
continue;
|
|
}
|
|
if (b < 0x20 || b === 0x7f) out += '\\x' + hex2(b);
|
|
else out += c;
|
|
continue;
|
|
}
|
|
// Multi-byte: decode one UTF-8 sequence strictly
|
|
const cp = decodeUtf8At(bytes, i);
|
|
if (!cp) {
|
|
out += '\\x' + hex2(b);
|
|
i++;
|
|
continue;
|
|
}
|
|
const ch = String.fromCodePoint(cp.codePoint);
|
|
if (PRINTABLE.test(ch)) out += ch;
|
|
else if (cp.codePoint < 0x10000) out += '\\u' + cp.codePoint.toString(16).padStart(4, '0');
|
|
else out += '\\U' + cp.codePoint.toString(16).padStart(8, '0');
|
|
i += cp.length;
|
|
}
|
|
return out + '"';
|
|
}
|
|
|
|
function hex2(b: number): string {
|
|
return b.toString(16).padStart(2, '0');
|
|
}
|
|
|
|
/** Strict UTF-8 decode of the sequence at i (rejects overlongs, surrogates, > U+10FFFF). */
|
|
function decodeUtf8At(buf: Buffer, i: number): { codePoint: number; length: number } | undefined {
|
|
const b0 = buf[i];
|
|
let len: number;
|
|
let cp: number;
|
|
let min: number;
|
|
if (b0 >= 0xc2 && b0 <= 0xdf) {
|
|
len = 2;
|
|
cp = b0 & 0x1f;
|
|
min = 0x80;
|
|
} else if (b0 >= 0xe0 && b0 <= 0xef) {
|
|
len = 3;
|
|
cp = b0 & 0x0f;
|
|
min = 0x800;
|
|
} else if (b0 >= 0xf0 && b0 <= 0xf4) {
|
|
len = 4;
|
|
cp = b0 & 0x07;
|
|
min = 0x10000;
|
|
} else return undefined;
|
|
if (i + len > buf.length) return undefined;
|
|
for (let k = 1; k < len; k++) {
|
|
const b = buf[i + k];
|
|
if ((b & 0xc0) !== 0x80) return undefined;
|
|
cp = (cp << 6) | (b & 0x3f);
|
|
}
|
|
if (cp < min || cp > 0x10ffff || (cp >= 0xd800 && cp <= 0xdfff)) return undefined;
|
|
return { codePoint: cp, length: len };
|
|
}
|
|
|
|
/**
|
|
* Go strconv.Unquote for double-quoted strings. Returns the raw bytes (descriptions may
|
|
* contain invalid UTF-8). Throws on malformed input.
|
|
*/
|
|
export function goUnquoteBytes(quoted: string): Buffer {
|
|
if (quoted.length < 2 || quoted[0] !== '"' || quoted[quoted.length - 1] !== '"') {
|
|
throw new Error(`not a Go double-quoted string: ${JSON.stringify(quoted)}`);
|
|
}
|
|
const s = quoted.slice(1, -1);
|
|
const out: number[] = [];
|
|
const pushStr = (str: string) => out.push(...Buffer.from(str, 'utf8'));
|
|
for (let i = 0; i < s.length; ) {
|
|
const c = s[i];
|
|
if (c === '"') throw new Error(`unescaped quote in Go string: ${JSON.stringify(quoted)}`);
|
|
if (c !== '\\') {
|
|
const cp = s.codePointAt(i)!;
|
|
const ch = String.fromCodePoint(cp);
|
|
pushStr(ch);
|
|
i += ch.length;
|
|
continue;
|
|
}
|
|
const e = s[i + 1];
|
|
const simple: Record<string, number> = { a: 7, b: 8, f: 12, n: 10, r: 13, t: 9, v: 11, '\\': 92, '"': 34, "'": 39 };
|
|
if (e in simple) {
|
|
out.push(simple[e]);
|
|
i += 2;
|
|
} else if (e === 'x') {
|
|
out.push(parseHex(s.slice(i + 2, i + 4), quoted));
|
|
i += 4;
|
|
} else if (e === 'u') {
|
|
pushStr(String.fromCodePoint(parseHex(s.slice(i + 2, i + 6), quoted)));
|
|
i += 6;
|
|
} else if (e === 'U') {
|
|
pushStr(String.fromCodePoint(parseHex(s.slice(i + 2, i + 10), quoted)));
|
|
i += 10;
|
|
} else if (e >= '0' && e <= '7') {
|
|
const oct = s.slice(i + 1, i + 4);
|
|
if (!/^[0-7]{3}$/.test(oct)) throw new Error(`bad octal escape in ${JSON.stringify(quoted)}`);
|
|
out.push(parseInt(oct, 8));
|
|
i += 4;
|
|
} else {
|
|
throw new Error(`bad escape \\${e} in Go string ${JSON.stringify(quoted)}`);
|
|
}
|
|
}
|
|
return Buffer.from(out);
|
|
}
|
|
|
|
/** goUnquoteBytes decoded as UTF-8 (invalid sequences become U+FFFD). */
|
|
export function goUnquote(quoted: string): string {
|
|
return goUnquoteBytes(quoted).toString('utf8');
|
|
}
|
|
|
|
function parseHex(h: string, ctx: string): number {
|
|
if (!/^[0-9a-fA-F]+$/.test(h)) throw new Error(`bad hex escape in ${JSON.stringify(ctx)}`);
|
|
return parseInt(h, 16);
|
|
}
|
|
|
|
/** POSIX sh single-quoting: the result is always one shell word with the exact bytes of s. */
|
|
export function shellQuote(s: string): string {
|
|
return `'${s.split("'").join(`'\\''`)}'`;
|
|
}
|