import { Buffer } from "./buffer.ts";import { encodeStr, hexTable } from "./internal/querystring.ts";
export const decode = parse;
export const encode = stringify;
function qsEscape(str: unknown): string { if (typeof str !== "string") { if (typeof str === "object") { str = String(str); } else { str += ""; } } return encodeStr(str as string, noEscape, hexTable);}
export const escape = qsEscape;
export interface ParsedUrlQuery { [key: string]: string | string[] | undefined;}
export interface ParsedUrlQueryInput { [key: string]: | string | number | boolean | ReadonlyArray<string> | ReadonlyArray<number> | ReadonlyArray<boolean> | null | undefined;}
interface ParseOptions { decodeURIComponent?: (string: string) => string; maxKeys?: number;}
const isHexTable = new Int8Array([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]);
function charCodes(str: string): number[] { const ret = new Array(str.length); for (let i = 0; i < str.length; ++i) { ret[i] = str.charCodeAt(i); } return ret;}
function addKeyVal( obj: ParsedUrlQuery, key: string, value: string, keyEncoded: boolean, valEncoded: boolean, decode: (encodedURIComponent: string) => string,) { if (key.length > 0 && keyEncoded) { key = decode(key); } if (value.length > 0 && valEncoded) { value = decode(value); }
if (obj[key] === undefined) { obj[key] = value; } else { const curValue = obj[key]; if ((curValue as string[]).pop) { (curValue as string[])[curValue!.length] = value; } else { obj[key] = [curValue as string, value]; } }}
export function parse( str: string, sep = "&", eq = "=", { decodeURIComponent = unescape, maxKeys = 1000 }: ParseOptions = {},): ParsedUrlQuery { const obj: ParsedUrlQuery = Object.create(null);
if (typeof str !== "string" || str.length === 0) { return obj; }
const sepCodes = (!sep ? [38] : charCodes(String(sep))); const eqCodes = (!eq ? [61] : charCodes(String(eq))); const sepLen = sepCodes.length; const eqLen = eqCodes.length;
let pairs = 1000; if (typeof maxKeys === "number") { pairs = maxKeys > 0 ? maxKeys : -1; }
let decode = unescape; if (decodeURIComponent) { decode = decodeURIComponent; } const customDecode = (decode !== unescape);
let lastPos = 0; let sepIdx = 0; let eqIdx = 0; let key = ""; let value = ""; let keyEncoded = customDecode; let valEncoded = customDecode; const plusChar = (customDecode ? "%20" : " "); let encodeCheck = 0; for (let i = 0; i < str.length; ++i) { const code = str.charCodeAt(i);
if (code === sepCodes[sepIdx]) { if (++sepIdx === sepLen) { const end = i - sepIdx + 1; if (eqIdx < eqLen) { if (lastPos < end) { key += str.slice(lastPos, end); } else if (key.length === 0) { if (--pairs === 0) { return obj; } lastPos = i + 1; sepIdx = eqIdx = 0; continue; } } else if (lastPos < end) { value += str.slice(lastPos, end); }
addKeyVal(obj, key, value, keyEncoded, valEncoded, decode);
if (--pairs === 0) { return obj; } key = value = ""; encodeCheck = 0; lastPos = i + 1; sepIdx = eqIdx = 0; } } else { sepIdx = 0; if (eqIdx < eqLen) { if (code === eqCodes[eqIdx]) { if (++eqIdx === eqLen) { const end = i - eqIdx + 1; if (lastPos < end) { key += str.slice(lastPos, end); } encodeCheck = 0; lastPos = i + 1; } continue; } else { eqIdx = 0; if (!keyEncoded) { if (code === 37 ) { encodeCheck = 1; continue; } else if (encodeCheck > 0) { if (isHexTable[code] === 1) { if (++encodeCheck === 3) { keyEncoded = true; } continue; } else { encodeCheck = 0; } } } } if (code === 43 ) { if (lastPos < i) { key += str.slice(lastPos, i); } key += plusChar; lastPos = i + 1; continue; } } if (code === 43 ) { if (lastPos < i) { value += str.slice(lastPos, i); } value += plusChar; lastPos = i + 1; } else if (!valEncoded) { if (code === 37 ) { encodeCheck = 1; } else if (encodeCheck > 0) { if (isHexTable[code] === 1) { if (++encodeCheck === 3) { valEncoded = true; } } else { encodeCheck = 0; } } } } }
if (lastPos < str.length) { if (eqIdx < eqLen) { key += str.slice(lastPos); } else if (sepIdx < sepLen) { value += str.slice(lastPos); } } else if (eqIdx === 0 && key.length === 0) { return obj; }
addKeyVal(obj, key, value, keyEncoded, valEncoded, decode);
return obj;}
interface StringifyOptions { encodeURIComponent: (string: string) => string;}
const noEscape = new Int8Array([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, ]);
function stringifyPrimitive(v: any): string { if (typeof v === "string") { return v; } if (typeof v === "number" && isFinite(v)) { return "" + v; } if (typeof v === "bigint") { return "" + v; } if (typeof v === "boolean") { return v ? "true" : "false"; } return "";}
function encodeStringifiedCustom( v: any, encode: (string: string) => string,): string { return encode(stringifyPrimitive(v));}
function encodeStringified(v: any, encode: (string: string) => string): string { if (typeof v === "string") { return (v.length ? encode(v) : ""); } if (typeof v === "number" && isFinite(v)) { return (Math.abs(v) < 1e21 ? "" + v : encode("" + v)); } if (typeof v === "bigint") { return "" + v; } if (typeof v === "boolean") { return v ? "true" : "false"; } return "";}
export function stringify( obj: Record<string, any>, sep?: string, eq?: string, options?: StringifyOptions,): string { sep ||= "&"; eq ||= "="; const encode = options ? options.encodeURIComponent : qsEscape; const convert = options ? encodeStringifiedCustom : encodeStringified;
if (obj !== null && typeof obj === "object") { const keys = Object.keys(obj); const len = keys.length; let fields = ""; for (let i = 0; i < len; ++i) { const k = keys[i]; const v = obj[k]; let ks = convert(k, encode); ks += eq;
if (Array.isArray(v)) { const vlen = v.length; if (vlen === 0) continue; if (fields) { fields += sep; } for (let j = 0; j < vlen; ++j) { if (j) { fields += sep; } fields += ks; fields += convert(v[j], encode); } } else { if (fields) { fields += sep; } fields += ks; fields += convert(v, encode); } } return fields; } return "";}
const unhexTable = new Int8Array([ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +0, +1, +2, +3, +4, +5, +6, +7, +8, +9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ]);
export function unescapeBuffer(s: string, decodeSpaces = false): Buffer { const out = Buffer.alloc(s.length); let index = 0; let outIndex = 0; let currentChar; let nextChar; let hexHigh; let hexLow; const maxLength = s.length - 2; let hasHex = false; while (index < s.length) { currentChar = s.charCodeAt(index); if (currentChar === 43 && decodeSpaces) { out[outIndex++] = 32; index++; continue; } if (currentChar === 37 && index < maxLength) { currentChar = s.charCodeAt(++index); hexHigh = unhexTable[currentChar]; if (!(hexHigh >= 0)) { out[outIndex++] = 37; continue; } else { nextChar = s.charCodeAt(++index); hexLow = unhexTable[nextChar]; if (!(hexLow >= 0)) { out[outIndex++] = 37; index--; } else { hasHex = true; currentChar = hexHigh * 16 + hexLow; } } } out[outIndex++] = currentChar; index++; } return hasHex ? out.slice(0, outIndex) : out;}
function qsUnescape(s: string): string { try { return decodeURIComponent(s); } catch { return unescapeBuffer(s).toString(); }}
export const unescape = qsUnescape;
export default { parse, stringify, decode, encode, unescape, escape, unescapeBuffer,};