Files
dpsapi/e2e/harness/gostrings.ts
T
bryanpedini fe0cd4bf6d test(e2e): add playwright e2e harness
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.
2026-10-06 10:52:25 +02:00

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(`'\\''`)}'`;
}