Compare commits

...

3 Commits

Author SHA1 Message Date
pfg
029f62baa1 pass test-querystring by updating querystring.ts to new node code 2024-12-10 19:47:14 -08:00
pfg
46513c4da3 pass test-querystring-escape 2024-12-10 19:24:13 -08:00
pfg
35bc011c28 skip flaky tests 2024-12-10 14:23:49 -08:00
14 changed files with 1069 additions and 380 deletions

View File

@@ -47,6 +47,9 @@ static JSC::JSObject* createErrorPrototype(JSC::VM& vm, JSC::JSGlobalObject* glo
case JSC::ErrorType::Error:
prototype = JSC::constructEmptyObject(globalObject, globalObject->errorPrototype());
break;
case JSC::ErrorType::URIError:
prototype = JSC::constructEmptyObject(globalObject, globalObject->m_URIErrorStructure.prototype(globalObject));
break;
default: {
RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("TODO: Add support for more error types");
break;

View File

@@ -46,6 +46,7 @@ export default [
["ERR_UNKNOWN_SIGNAL", TypeError, "TypeError"],
["ERR_SOCKET_BAD_PORT", RangeError, "RangeError"],
["ERR_STREAM_RELEASE_LOCK", Error, "AbortError"],
["ERR_INVALID_URI", URIError, "URIError"],
// Bun-specific
["ERR_FORMDATA_PARSE_ERROR", TypeError, "TypeError"],

View File

@@ -124,6 +124,7 @@ export default {
MathRound: Math.round,
MathSqrt: Math.sqrt,
MathTrunc: Math.trunc,
MathAbs: Math.abs,
Number,
NumberIsFinite: Number.isFinite,
NumberIsNaN: Number.isNaN,
@@ -195,6 +196,7 @@ export default {
StringPrototypeSplit: uncurryThis(String.prototype.split),
StringPrototypeStartsWith: uncurryThis(String.prototype.startsWith),
StringPrototypeToLowerCase: uncurryThis(String.prototype.toLowerCase),
StringPrototypeToUpperCase: uncurryThis(String.prototype.toUpperCase),
StringPrototypeTrim: uncurryThis(String.prototype.trim),
StringPrototypeValueOf: uncurryThis(String.prototype.valueOf),
SymbolPrototypeToString: uncurryThis(Symbol.prototype.toString),

View File

@@ -1,398 +1,546 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var __commonJS =
(cb, mod: typeof module | undefined = undefined) =>
() => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
var Buffer = require("node:buffer").Buffer;
// src/node-fallbacks/node_modules/querystring-es3/src/object-keys.js
var require_object_keys = __commonJS((exports, module) => {
var objectKeys =
Object.keys ||
(function () {
var hasOwnProperty = Object.prototype.hasOwnProperty;
var hasDontEnumBug = !{ toString: null }.propertyIsEnumerable("toString");
var dontEnums = [
"toString",
"toLocaleString",
"valueOf",
"hasOwnProperty",
"isPrototypeOf",
"propertyIsEnumerable",
"constructor",
];
var dontEnumsLength = dontEnums.length;
return function (obj) {
if (typeof obj !== "function" && (typeof obj !== "object" || obj === null)) {
throw new TypeError("Object.keys called on non-object");
}
var result = [];
var prop;
var i;
for (prop in obj) {
if (hasOwnProperty.$call(obj, prop)) {
result.push(prop);
}
}
if (hasDontEnumBug) {
for (i = 0; i < dontEnumsLength; i++) {
if (hasOwnProperty.$call(obj, dontEnums[i])) {
result.push(dontEnums[i]);
}
}
}
return result;
};
})();
module.exports = objectKeys;
});
// src/node-fallbacks/node_modules/querystring-es3/src/index.js
var require_src = __commonJS((exports, module) => {
var ParsedQueryString = function () {};
var unescapeBuffer = function (s, decodeSpaces) {
var out = Buffer.allocUnsafe(s.length);
var state = 0;
var n, m, hexchar, c;
for (var inIndex = 0, outIndex = 0; ; inIndex++) {
if (inIndex < s.length) {
c = s.charCodeAt(inIndex);
} else {
if (state > 0) {
out[outIndex++] = 37;
if (state === 2) out[outIndex++] = hexchar;
const {
Array,
ArrayIsArray,
Int8Array,
MathAbs,
NumberIsFinite,
ObjectKeys,
String,
StringPrototypeCharCodeAt,
StringPrototypeSlice,
decodeURIComponent,
StringPrototypeToUpperCase,
NumberPrototypeToString,
} = require("internal/primordials");
const { Buffer } = require("node:buffer");
/**
* @param {string} str
* @param {Int8Array} noEscapeTable
* @param {string[]} hexTable
* @returns {string}
*/
function encodeStr(str, noEscapeTable, hexTable) {
const len = str.length;
if (len === 0) return "";
let out = "";
let lastPos = 0;
let i = 0;
outer: for (; i < len; i++) {
let c = StringPrototypeCharCodeAt(str, i);
// ASCII
while (c < 0x80) {
if (noEscapeTable[c] !== 1) {
if (lastPos < i) out += StringPrototypeSlice(str, lastPos, i);
lastPos = i + 1;
out += hexTable[c];
}
break;
if (++i === len) break outer;
c = StringPrototypeCharCodeAt(str, i);
}
switch (state) {
case 0:
switch (c) {
case 37:
n = 0;
m = 0;
state = 1;
break;
case 43:
if (decodeSpaces) c = 32;
default:
out[outIndex++] = c;
break;
}
break;
case 1:
hexchar = c;
n = unhexTable[c];
if (!(n >= 0)) {
out[outIndex++] = 37;
out[outIndex++] = c;
state = 0;
break;
}
state = 2;
break;
case 2:
state = 0;
m = unhexTable[c];
if (!(m >= 0)) {
out[outIndex++] = 37;
out[outIndex++] = hexchar;
out[outIndex++] = c;
break;
}
out[outIndex++] = 16 * n + m;
break;
if (lastPos < i) out += StringPrototypeSlice(str, lastPos, i);
// Multi-byte characters ...
if (c < 0x800) {
lastPos = i + 1;
out += hexTable[0xc0 | (c >> 6)] + hexTable[0x80 | (c & 0x3f)];
continue;
}
if (c < 0xd800 || c >= 0xe000) {
lastPos = i + 1;
out += hexTable[0xe0 | (c >> 12)] + hexTable[0x80 | ((c >> 6) & 0x3f)] + hexTable[0x80 | (c & 0x3f)];
continue;
}
// Surrogate pair
++i;
// This branch should never happen because all URLSearchParams entries
// should already be converted to USVString. But, included for
// completion's sake anyway.
if (i >= len) throw $ERR_INVALID_URI("URI malformed");
const c2 = StringPrototypeCharCodeAt(str, i) & 0x3ff;
lastPos = i + 1;
c = 0x10000 + (((c & 0x3ff) << 10) | c2);
out +=
hexTable[0xf0 | (c >> 18)] +
hexTable[0x80 | ((c >> 12) & 0x3f)] +
hexTable[0x80 | ((c >> 6) & 0x3f)] +
hexTable[0x80 | (c & 0x3f)];
}
return out.slice(0, outIndex);
};
var qsUnescape = function (s, decodeSpaces) {
if (lastPos === 0) return str;
if (lastPos < len) return out + StringPrototypeSlice(str, lastPos);
return out;
}
const hexTable = new Array(256);
for (let i = 0; i < 256; ++i)
hexTable[i] = "%" + StringPrototypeToUpperCase((i < 16 ? "0" : "") + NumberPrototypeToString(i, 16));
// prettier-ignore
const isHexTable = new Int8Array([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32 - 47
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 64 - 79
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80 - 95
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 96 - 111
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 112 - 127
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128 ...
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, // ... 256
]);
const QueryString = (module.exports = {
unescapeBuffer,
// `unescape()` is a JS global, so we need to use a different local name
unescape: qsUnescape,
// `escape()` is a JS global, so we need to use a different local name
escape: qsEscape,
stringify,
encode: stringify,
parse,
decode: parse,
});
// prettier-ignore
const unhexTable = new Int8Array([
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 - 15
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16 - 31
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32 - 47
+0, +1, +2, +3, +4, +5, +6, +7, +8, +9, -1, -1, -1, -1, -1, -1, // 48 - 63
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 64 - 79
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 - 95
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96 - 111
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 112 - 127
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128 ...
-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, // ... 255
]);
/**
* A safe fast alternative to decodeURIComponent
* @param {string} s
* @param {boolean} decodeSpaces
* @returns {string}
*/
function unescapeBuffer(s, decodeSpaces) {
const out = Buffer.allocUnsafe(s.length);
let index = 0;
let outIndex = 0;
let currentChar;
let nextChar;
let hexHigh;
let hexLow;
const maxLength = s.length - 2;
// Flag to know if some hex chars have been decoded
let hasHex = false;
while (index < s.length) {
currentChar = StringPrototypeCharCodeAt(s, index);
if (currentChar === 43 /* '+' */ && decodeSpaces) {
out[outIndex++] = 32; // ' '
index++;
continue;
}
if (currentChar === 37 /* '%' */ && index < maxLength) {
currentChar = StringPrototypeCharCodeAt(s, ++index);
hexHigh = unhexTable[currentChar];
if (!(hexHigh >= 0)) {
out[outIndex++] = 37; // '%'
continue;
} else {
nextChar = StringPrototypeCharCodeAt(s, ++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;
}
/**
* @param {string} s
* @param {boolean} decodeSpaces
* @returns {string}
*/
function qsUnescape(s, decodeSpaces) {
try {
return decodeURIComponent(s);
} catch (e) {
} catch {
return QueryString.unescapeBuffer(s, decodeSpaces).toString();
}
};
var qsEscape = function (str) {
}
// These characters do not need escaping when generating query strings:
// ! - . _ ~
// ' ( ) *
// digits
// alpha (uppercase)
// alpha (lowercase)
// prettier-ignore
const noEscape = new Int8Array([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, // 32 - 47
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 80 - 95
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, // 112 - 127
]);
/**
* QueryString.escape() replaces encodeURIComponent()
* @see https://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4
* @param {any} str
* @returns {string}
*/
function qsEscape(str) {
if (typeof str !== "string") {
if (typeof str === "object") str = String(str);
else str += "";
}
var out = "";
var lastPos = 0;
for (var i2 = 0; i2 < str.length; ++i2) {
var c = str.charCodeAt(i2);
if (c < 128) {
if (noEscape[c] === 1) continue;
if (lastPos < i2) out += str.slice(lastPos, i2);
lastPos = i2 + 1;
out += hexTable[c];
continue;
}
if (lastPos < i2) out += str.slice(lastPos, i2);
if (c < 2048) {
lastPos = i2 + 1;
out += hexTable[192 | (c >> 6)] + hexTable[128 | (c & 63)];
continue;
}
if (c < 55296 || c >= 57344) {
lastPos = i2 + 1;
out += hexTable[224 | (c >> 12)] + hexTable[128 | ((c >> 6) & 63)] + hexTable[128 | (c & 63)];
continue;
}
++i2;
var c2;
if (i2 < str.length) c2 = str.charCodeAt(i2) & 1023;
else throw new URIError("URI malformed");
lastPos = i2 + 1;
c = 65536 + (((c & 1023) << 10) | c2);
out +=
hexTable[240 | (c >> 18)] +
hexTable[128 | ((c >> 12) & 63)] +
hexTable[128 | ((c >> 6) & 63)] +
hexTable[128 | (c & 63)];
}
if (lastPos === 0) return str;
if (lastPos < str.length) return out + str.slice(lastPos);
return out;
};
var stringifyPrimitive = function (v) {
return encodeStr(str, noEscape, hexTable);
}
/**
* @param {string | number | bigint | boolean | symbol | undefined | null} v
* @returns {string}
*/
function stringifyPrimitive(v) {
if (typeof v === "string") return v;
if (typeof v === "number" && isFinite(v)) return "" + v;
if (typeof v === "number" && NumberIsFinite(v)) return "" + v;
if (typeof v === "bigint") return "" + v;
if (typeof v === "boolean") return v ? "true" : "false";
return "";
};
var stringify = function (obj, sep, eq, options) {
sep = sep || "&";
eq = eq || "=";
var encode = QueryString.escape;
}
/**
* @param {string | number | bigint | boolean} v
* @param {(v: string) => string} encode
* @returns {string}
*/
function encodeStringified(v, encode) {
if (typeof v === "string") return v.length ? encode(v) : "";
if (typeof v === "number" && NumberIsFinite(v)) {
// Values >= 1e21 automatically switch to scientific notation which requires
// escaping due to the inclusion of a '+' in the output
return MathAbs(v) < 1e21 ? "" + v : encode("" + v);
}
if (typeof v === "bigint") return "" + v;
if (typeof v === "boolean") return v ? "true" : "false";
return "";
}
/**
* @param {string | number | boolean | null} v
* @param {(v: string) => string} encode
* @returns {string}
*/
function encodeStringifiedCustom(v, encode) {
return encode(stringifyPrimitive(v));
}
/**
* @param {Record<string, string | number | boolean
* | ReadonlyArray<string | number | boolean> | null>} obj
* @param {string} [sep]
* @param {string} [eq]
* @param {{ encodeURIComponent?: (v: string) => string }} [options]
* @returns {string}
*/
function stringify(obj, sep, eq, options) {
sep ||= "&";
eq ||= "=";
let encode = QueryString.escape;
if (options && typeof options.encodeURIComponent === "function") {
encode = options.encodeURIComponent;
}
const convert = encode === qsEscape ? encodeStringified : encodeStringifiedCustom;
if (obj !== null && typeof obj === "object") {
var keys = objectKeys(obj);
var len = keys.length;
var flast = len - 1;
var fields = "";
for (var i2 = 0; i2 < len; ++i2) {
var k = keys[i2];
var v = obj[k];
var ks = encode(stringifyPrimitive(k)) + eq;
if (isArray(v)) {
var vlen = v.length;
var vlast = vlen - 1;
for (var j = 0; j < vlen; ++j) {
fields += ks + encode(stringifyPrimitive(v[j]));
if (j < vlast) fields += sep;
const keys = ObjectKeys(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 (ArrayIsArray(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);
}
if (vlen && i2 < flast) fields += sep;
} else {
fields += ks + encode(stringifyPrimitive(v));
if (i2 < flast) fields += sep;
if (fields) fields += sep;
fields += ks;
fields += convert(v, encode);
}
}
return fields;
}
return "";
};
var charCodes = function (str) {
}
/**
* @param {string} str
* @returns {number[]}
*/
function charCodes(str) {
if (str.length === 0) return [];
if (str.length === 1) return [str.charCodeAt(0)];
const ret = [];
for (var i2 = 0; i2 < str.length; ++i2) ret[ret.length] = str.charCodeAt(i2);
if (str.length === 1) return [StringPrototypeCharCodeAt(str, 0)];
const ret = new Array(str.length);
for (let i = 0; i < str.length; ++i) ret[i] = StringPrototypeCharCodeAt(str, i);
return ret;
};
var parse = function (qs, sep, eq, options) {
const obj = new ParsedQueryString();
}
const defSepCodes = [38]; // &
const defEqCodes = [61]; // =
function addKeyVal(obj, key, value, keyEncoded, valEncoded, decode) {
if (key.length > 0 && keyEncoded) key = decodeStr(key, decode);
if (value.length > 0 && valEncoded) value = decodeStr(value, decode);
if (obj[key] === undefined) {
obj[key] = value;
} else {
const curValue = obj[key];
// A simple Array-specific property check is enough here to
// distinguish from a string value and is faster and still safe
// since we are generating all of the values being assigned.
if (curValue.pop) curValue[curValue.length] = value;
else obj[key] = [curValue, value];
}
}
/**
* Parse a key/val string.
* @param {string} qs
* @param {string} sep
* @param {string} eq
* @param {{
* maxKeys?: number;
* decodeURIComponent?(v: string): string;
* }} [options]
* @returns {Record<string, string | string[]>}
*/
function parse(qs, sep, eq, options) {
const obj = { __proto__: null };
if (typeof qs !== "string" || qs.length === 0) {
return obj;
}
var sepCodes = !sep ? defSepCodes : charCodes(sep + "");
var eqCodes = !eq ? defEqCodes : charCodes(eq + "");
const sepCodes = !sep ? defSepCodes : charCodes(String(sep));
const eqCodes = !eq ? defEqCodes : charCodes(String(eq));
const sepLen = sepCodes.length;
const eqLen = eqCodes.length;
var pairs = 1000;
let pairs = 1000;
if (options && typeof options.maxKeys === "number") {
// -1 is used in place of a value like Infinity for meaning
// "unlimited pairs" because of additional checks V8 (at least as of v5.4)
// has to do when using variables that contain values like Infinity. Since
// `pairs` is always decremented and checked explicitly for 0, -1 works
// effectively the same as Infinity, while providing a significant
// performance boost.
pairs = options.maxKeys > 0 ? options.maxKeys : -1;
}
var decode = QueryString.unescape;
let decode = QueryString.unescape;
if (options && typeof options.decodeURIComponent === "function") {
decode = options.decodeURIComponent;
}
const customDecode = decode !== qsUnescape;
const keys = [];
var posIdx = 0;
var lastPos = 0;
var sepIdx = 0;
var eqIdx = 0;
var key = "";
var value = "";
var keyEncoded = customDecode;
var valEncoded = customDecode;
var encodeCheck = 0;
for (var i2 = 0; i2 < qs.length; ++i2) {
const code = qs.charCodeAt(i2);
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 < qs.length; ++i) {
const code = StringPrototypeCharCodeAt(qs, i);
// Try matching key/value pair separator (e.g. '&')
if (code === sepCodes[sepIdx]) {
if (++sepIdx === sepLen) {
const end = i2 - sepIdx + 1;
// Key/value pair separator match!
const end = i - sepIdx + 1;
if (eqIdx < eqLen) {
if (lastPos < end) key += qs.slice(lastPos, end);
} else if (lastPos < end) value += qs.slice(lastPos, end);
if (keyEncoded) key = decodeStr(key, decode);
if (valEncoded) value = decodeStr(value, decode);
if (key || value || lastPos - posIdx > sepLen || i2 === 0) {
if (indexOf(keys, key) === -1) {
obj[key] = value;
keys[keys.length] = key;
} else {
const curValue = obj[key] || "";
if (curValue.pop) curValue[curValue.length] = value;
else if (curValue) obj[key] = [curValue, value];
// We didn't find the (entire) key/value separator
if (lastPos < end) {
// Treat the substring as part of the key instead of the value
key += StringPrototypeSlice(qs, lastPos, end);
} else if (key.length === 0) {
// We saw an empty substring between separators
if (--pairs === 0) return obj;
lastPos = i + 1;
sepIdx = eqIdx = 0;
continue;
}
} else if (i2 === 1) {
delete obj[key];
} else if (lastPos < end) {
value += StringPrototypeSlice(qs, lastPos, end);
}
if (--pairs === 0) break;
addKeyVal(obj, key, value, keyEncoded, valEncoded, decode);
if (--pairs === 0) return obj;
keyEncoded = valEncoded = customDecode;
encodeCheck = 0;
key = value = "";
posIdx = lastPos;
lastPos = i2 + 1;
encodeCheck = 0;
lastPos = i + 1;
sepIdx = eqIdx = 0;
}
continue;
} else {
sepIdx = 0;
if (!valEncoded) {
if (code === 37) {
encodeCheck = 1;
} else if (
encodeCheck > 0 &&
((code >= 48 && code <= 57) || (code >= 65 && code <= 70) || (code >= 97 && code <= 102))
) {
if (++encodeCheck === 3) valEncoded = true;
// Try matching key/value separator (e.g. '=') if we haven't already
if (eqIdx < eqLen) {
if (code === eqCodes[eqIdx]) {
if (++eqIdx === eqLen) {
// Key/value separator match!
const end = i - eqIdx + 1;
if (lastPos < end) key += StringPrototypeSlice(qs, lastPos, end);
encodeCheck = 0;
lastPos = i + 1;
}
continue;
} else {
encodeCheck = 0;
eqIdx = 0;
if (!keyEncoded) {
// Try to match an (valid) encoded byte once to minimize unnecessary
// calls to string decoding functions
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 += StringPrototypeSlice(qs, lastPos, i);
key += plusChar;
lastPos = i + 1;
continue;
}
}
}
if (eqIdx < eqLen) {
if (code === eqCodes[eqIdx]) {
if (++eqIdx === eqLen) {
const end = i2 - eqIdx + 1;
if (lastPos < end) key += qs.slice(lastPos, end);
encodeCheck = 0;
lastPos = i2 + 1;
}
continue;
} else {
eqIdx = 0;
if (!keyEncoded) {
if (code === 37) {
encodeCheck = 1;
} else if (
encodeCheck > 0 &&
((code >= 48 && code <= 57) || (code >= 65 && code <= 70) || (code >= 97 && code <= 102))
) {
if (++encodeCheck === 3) keyEncoded = true;
if (code === 43 /* + */) {
if (lastPos < i) value += StringPrototypeSlice(qs, lastPos, i);
value += plusChar;
lastPos = i + 1;
} else if (!valEncoded) {
// Try to match an (valid) encoded byte (once) to minimize unnecessary
// calls to string decoding functions
if (code === 37 /* % */) {
encodeCheck = 1;
} else if (encodeCheck > 0) {
if (isHexTable[code] === 1) {
if (++encodeCheck === 3) valEncoded = true;
} else {
encodeCheck = 0;
}
}
}
}
if (code === 43) {
if (eqIdx < eqLen) {
if (lastPos < i2) key += qs.slice(lastPos, i2);
key += "%20";
keyEncoded = true;
} else {
if (lastPos < i2) value += qs.slice(lastPos, i2);
value += "%20";
valEncoded = true;
}
lastPos = i2 + 1;
}
}
if (pairs !== 0 && (lastPos < qs.length || eqIdx > 0)) {
if (lastPos < qs.length) {
if (eqIdx < eqLen) key += qs.slice(lastPos);
else if (sepIdx < sepLen) value += qs.slice(lastPos);
}
if (keyEncoded) key = decodeStr(key, decode);
if (valEncoded) value = decodeStr(value, decode);
if (indexOf(keys, key) === -1) {
obj[key] = value;
keys[keys.length] = key;
} else {
const curValue = obj[key];
if (curValue.pop) curValue[curValue.length] = value;
else obj[key] = [curValue, value];
}
// Deal with any leftover key or value data
if (lastPos < qs.length) {
if (eqIdx < eqLen) key += StringPrototypeSlice(qs, lastPos);
else if (sepIdx < sepLen) value += StringPrototypeSlice(qs, lastPos);
} else if (eqIdx === 0 && key.length === 0) {
// We ended on an empty substring
return obj;
}
addKeyVal(obj, key, value, keyEncoded, valEncoded, decode);
return obj;
};
var decodeStr = function (s, decoder) {
}
/**
* V8 does not optimize functions with try-catch blocks, so we isolate them here
* to minimize the damage (Note: no longer true as of V8 5.4 -- but still will
* not be inlined).
* @param {string} s
* @param {(v: string) => string} decoder
* @returns {string}
*/
function decodeStr(s, decoder) {
try {
return decoder(s);
} catch (e) {
} catch {
return QueryString.unescape(s, true);
}
};
var QueryString = (module.exports = {
unescapeBuffer,
unescape: qsUnescape,
escape: qsEscape,
stringify,
encode: stringify,
parse,
decode: parse,
});
var objectKeys = require_object_keys();
var isArray = arg => Object.prototype.toString.$call(arg) === "[object Array]";
var indexOf = (arr, searchElement, fromIndex) => {
var k;
if (arr == null) {
throw new TypeError('"arr" is null or not defined');
}
var o = Object(arr);
var len = o.length >>> 0;
if (len === 0) {
return -1;
}
var n = fromIndex | 0;
if (n >= len) {
return -1;
}
k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
while (k < len) {
if (k in o && o[k] === searchElement) {
return k;
}
k++;
}
return -1;
};
ParsedQueryString.prototype = Object.create ? Object.create(null) : {};
var unhexTable = [
-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,
];
var hexTable = [];
for (i = 0; i < 256; ++i) hexTable[i] = "%" + ((i < 16 ? "0" : "") + i.toString(16)).toUpperCase();
var i;
var noEscape = [
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,
];
var defSepCodes = [38];
var defEqCodes = [61];
}
});
export default require_src();

View File

@@ -313,6 +313,7 @@ it(
const contents = readFileSync(root, "utf-8");
rmSync(root);
writeFileSync(root, contents);
Bun.sleepSync(100);
}
var str = "";
@@ -370,13 +371,10 @@ it(
async function onReload() {
const contents = readFileSync(root, "utf-8");
rmSync(root + ".tmpfile", { force: true });
await 1;
writeFileSync(root + ".tmpfile", contents);
await 1;
rmSync(root);
await 1;
renameSync(root + ".tmpfile", root);
await 1;
await Bun.sleep(100);
}
var str = "";
@@ -440,6 +438,7 @@ throw new Error('0');`,
${comment_spam}
${" ".repeat(reloadCounter * 2)}throw new Error(${reloadCounter});`,
);
Bun.sleepSync(100);
}
let str = "";
outer: for await (const chunk of runner.stderr) {
@@ -520,6 +519,7 @@ throw new Error('0');`,
// etc etc
${" ".repeat(reloadCounter * 2)}throw new Error(${reloadCounter});`,
);
Bun.sleepSync(100);
}
let str = "";
outer: for await (const chunk of runner.stderr) {

View File

@@ -279,16 +279,20 @@ describe("files transpiled and loaded don't leak the AST", () => {
// These tests are extra slow in debug builds
describe("files transpiled and loaded don't leak file paths", () => {
test("via require()", () => {
const { stdout, exitCode } = Bun.spawnSync({
cmd: [bunExe(), "--smol", "run", join(import.meta.dir, "cjs-fixture-leak-small.js")],
env: bunEnv,
stderr: "inherit",
});
test.todoIf(isWindows)(
"via require()",
() => {
const { stdout, exitCode } = Bun.spawnSync({
cmd: [bunExe(), "--smol", "run", join(import.meta.dir, "cjs-fixture-leak-small.js")],
env: bunEnv,
stderr: "inherit",
});
expect(stdout.toString().trim()).toEndWith("--pass--");
expect(exitCode).toBe(0);
}, 30000);
expect(stdout.toString().trim()).toEndWith("--pass--");
expect(exitCode).toBe(0);
},
30000,
);
test(
"via import()",

View File

@@ -3,7 +3,7 @@ import { expect, test } from "bun:test";
import { copyFileSync, cpSync, promises as fs, readFileSync, rmSync } from "fs";
import { cp } from "fs/promises";
import { join } from "path";
import { bunEnv, bunExe, isDebug, tmpdirSync, toMatchNodeModulesAt } from "../../../harness";
import { bunEnv, bunExe, isDebug, isLinux, isWindows, tmpdirSync, toMatchNodeModulesAt } from "../../../harness";
const { parseLockfile } = install_test_helpers;
expect.extend({ toMatchNodeModulesAt });
@@ -93,7 +93,7 @@ function normalizeOutput(stdout: string) {
);
}
test(
test.skipIf(isWindows || (isLinux && process.arch === "x64"))(
"next build works",
async () => {
rmSync(join(root, ".next"), { recursive: true, force: true });

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "bun:test";
import { fileDescriptorLeakChecker, isWindows, tmpdirSync } from "harness";
import { fileDescriptorLeakChecker, isWindows, libcFamily, tmpdirSync } from "harness";
import { mkfifo } from "mkfifo";
import { join } from "node:path";
@@ -170,7 +170,8 @@ import fs from "node:fs";
import path from "node:path";
import util from "node:util";
it("end doesn't close when backed by a file descriptor", async () => {
// leaks fd on musl
it.todoIf(libcFamily == "musl")("end doesn't close when backed by a file descriptor", async () => {
using _ = fileDescriptorLeakChecker();
const x = tmpdirSync();
const fd = await util.promisify(fs.open)(path.join(x, "test.txt"), "w");

View File

@@ -8,7 +8,7 @@ test("sleep should saturate timeout values", async () => {
"999999999999999.999999999999999",
"999999999999999",
];
const fixturesThatSHouldCompleteInstantly = [
const fixturesThatShouldCompleteInstantly = [
"0",
"0.0",
"-0",
@@ -31,7 +31,7 @@ test("sleep should saturate timeout values", async () => {
});
const start = performance.now();
const toWait = fixturesThatSHouldCompleteInstantly.map(async timeout => {
const toWait = fixturesThatShouldCompleteInstantly.map(async timeout => {
const proc = Bun.spawn({
cmd: [bunExe(), "sleep-4ever.js", timeout],
stderr: "inherit",
@@ -41,7 +41,7 @@ test("sleep should saturate timeout values", async () => {
cwd: import.meta.dir,
});
expect(await proc.exited).toBe(0);
expect(performance.now() - start).toBeLessThan(1000);
expect(performance.now() - start).toBeLessThan(2000);
});
await Promise.all(toWait);

View File

@@ -93,60 +93,69 @@ export function createDenoTest(path: string, defaultTimeout = 5000) {
// Deno's assertions implemented using expect().
// https://github.com/denoland/deno/blob/main/cli/tests/unit/test_util.ts
const assert = (condition: unknown, message?: string) => {
const handleError = (message: string | undefined, cb: () => void) => {
try {
cb();
} catch (error) {
console.error(message);
throw error;
}
};
const assert = (condition: unknown, message?: string) => handleError(message, () => {
expect(condition).toBeTruthy();
};
});
const assertFalse = (condition: unknown, message?: string) => {
const assertFalse = (condition: unknown, message?: string) => handleError(message, () => {
expect(condition).toBeFalsy();
};
});
const assertEquals = (actual: unknown, expected: unknown, message?: string) => {
const assertEquals = (actual: unknown, expected: unknown, message?: string) => handleError(message, () => {
expect(actual).toEqual(expected);
};
});
const assertExists = (value: unknown, message?: string) => {
const assertExists = (value: unknown, message?: string) => handleError(message, () => {
expect(value).toBeDefined();
};
});
const assertNotEquals = (actual: unknown, expected: unknown, message?: string) => {
const assertNotEquals = (actual: unknown, expected: unknown, message?: string) => handleError(message, () => {
expect(actual).not.toEqual(expected);
};
});
const assertStrictEquals = (actual: unknown, expected: unknown, message?: string) => {
const assertStrictEquals = (actual: unknown, expected: unknown, message?: string) => handleError(message, () => {
expect(actual).toStrictEqual(expected);
};
});
const assertNotStrictEquals = (actual: unknown, expected: unknown, message?: string) => {
const assertNotStrictEquals = (actual: unknown, expected: unknown, message?: string) => handleError(message, () => {
expect(actual).not.toStrictEqual(expected);
};
});
const assertAlmostEquals = (actual: unknown, expected: number, epsilon: number = 1e-7, message?: string) => {
const assertAlmostEquals = (actual: unknown, expected: number, epsilon: number = 1e-7, message?: string) => handleError(message, () => {
if (typeof actual === "number") {
// TODO: toBeCloseTo()
expect(Math.abs(actual - expected)).toBeLessThanOrEqual(epsilon);
} else {
expect(typeof actual).toBe("number");
}
};
});
const assertInstanceOf = (actual: unknown, expected: unknown, message?: string) => {
const assertInstanceOf = (actual: unknown, expected: unknown, message?: string) => handleError(message, () => {
expect(actual).toBeInstanceOf(expected);
};
});
const assertNotInstanceOf = (actual: unknown, expected: unknown, message?: string) => {
const assertNotInstanceOf = (actual: unknown, expected: unknown, message?: string) => handleError(message, () => {
expect(actual).not.toBeInstanceOf(expected);
};
});
const assertStringIncludes = (actual: unknown, expected: string, message?: string) => {
const assertStringIncludes = (actual: unknown, expected: string, message?: string) => handleError(message, () => {
if (typeof actual === "string") {
expect(actual).toContain(expected);
} else {
expect(typeof actual).toBe("string");
}
};
});
const assertArrayIncludes = (actual: unknown, expected: unknown[], message?: string) => {
const assertArrayIncludes = (actual: unknown, expected: unknown[], message?: string) => handleError(message, () => {
if (Array.isArray(actual)) {
for (const value of expected) {
expect(actual).toContain(value);
@@ -154,25 +163,25 @@ export function createDenoTest(path: string, defaultTimeout = 5000) {
} else {
expect(Array.isArray(actual)).toBe(true);
}
};
});
const assertMatch = (actual: unknown, expected: RegExp, message?: string) => {
const assertMatch = (actual: unknown, expected: RegExp, message?: string) => handleError(message, () => {
if (typeof actual === "string") {
expect(expected.test(actual)).toBe(true);
} else {
expect(typeof actual).toBe("string");
}
};
});
const assertNotMatch = (actual: unknown, expected: RegExp, message?: string) => {
const assertNotMatch = (actual: unknown, expected: RegExp, message?: string) => handleError(message, () => {
if (typeof actual === "string") {
expect(expected.test(actual)).toBe(false);
} else {
expect(typeof actual).toBe("string");
}
};
});
const assertObjectMatch = (actual: unknown, expected: Record<PropertyKey, unknown>, message?: string) => {
const assertObjectMatch = (actual: unknown, expected: Record<PropertyKey, unknown>, message?: string) => handleError(message, () => {
if (typeof actual === "object") {
// TODO: toMatchObject()
if (actual !== null) {
@@ -190,9 +199,9 @@ export function createDenoTest(path: string, defaultTimeout = 5000) {
} else {
expect(typeof actual).toBe("object");
}
};
});
const assertThrows = (fn: () => void, message?: string) => {
const assertThrows = (fn: () => void, message?: string) => handleError(message, () => {
try {
fn();
} catch (error) {
@@ -200,9 +209,9 @@ export function createDenoTest(path: string, defaultTimeout = 5000) {
return;
}
throw new Error("Expected an error to be thrown");
};
});
const assertRejects = async (fn: () => Promise<unknown>, message?: string) => {
const assertRejects = async (fn: () => Promise<unknown>, message?: string) => handleError(message, async () => {
try {
await fn();
} catch (error) {
@@ -210,7 +219,7 @@ export function createDenoTest(path: string, defaultTimeout = 5000) {
return;
}
throw new Error("Expected an error to be thrown");
};
});
const equal = (a: unknown, b: unknown) => {
return deepEquals(a, b);

View File

@@ -90,7 +90,7 @@ test(function performanceMeasure() {
assertEquals(measure2.startTime, 0);
assertEquals(mark1.startTime, measure1.startTime);
assertEquals(mark1.startTime, measure2.duration);
assert(measure1.duration >= 100, `duration below 100ms: ${measure1.duration}`);
assert(measure1.duration >= 85, `duration below 85ms: ${measure1.duration}`);
assert(
measure1.duration < (later - now) * 1.5,
`duration exceeds 150% of wallclock time: ${measure1.duration}ms vs ${later - now}ms`,

View File

@@ -2225,7 +2225,7 @@ describe("fs.ReadStream", () => {
});
describe("createWriteStream", () => {
it("simple write stream finishes", async () => {
it.todoIf(isWindows)("simple write stream finishes", async () => {
const path = `${tmpdir()}/fs.test.ts/${Date.now()}.createWriteStream.txt`;
const stream = createWriteStream(path);
stream.write("Test file written successfully");

View File

@@ -0,0 +1,41 @@
'use strict';
require('../common');
const assert = require('assert');
const qs = require('querystring');
assert.strictEqual(qs.escape(5), '5');
assert.strictEqual(qs.escape('test'), 'test');
assert.strictEqual(qs.escape({}), '%5Bobject%20Object%5D');
assert.strictEqual(qs.escape([5, 10]), '5%2C10');
assert.strictEqual(qs.escape('Ŋōđĕ'), '%C5%8A%C5%8D%C4%91%C4%95');
assert.strictEqual(qs.escape('testŊōđĕ'), 'test%C5%8A%C5%8D%C4%91%C4%95');
assert.strictEqual(qs.escape(`${String.fromCharCode(0xD800 + 1)}test`),
'%F0%90%91%B4est');
assert.throws(
() => qs.escape(String.fromCharCode(0xD800 + 1)),
{
code: 'ERR_INVALID_URI',
name: 'URIError',
message: 'URI malformed'
}
);
// Using toString for objects
assert.strictEqual(
qs.escape({ test: 5, toString: () => 'test', valueOf: () => 10 }),
'test'
);
// `toString` is not callable, must throw an error.
// Error message will vary between different JavaScript engines, so only check
// that it is a `TypeError`.
assert.throws(() => qs.escape({ toString: 5 }), TypeError);
// Should use valueOf instead of non-callable toString.
assert.strictEqual(qs.escape({ toString: 5, valueOf: () => 'test' }), 'test');
// Error message will vary between different JavaScript engines, so only check
// that it is a `TypeError`.
assert.throws(() => qs.escape(Symbol('test')), TypeError);

View File

@@ -0,0 +1,480 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
require('../common');
const assert = require('assert');
const inspect = require('util').inspect;
// test using assert
const qs = require('querystring');
function createWithNoPrototype(properties) {
const noProto = { __proto__: null };
properties.forEach((property) => {
noProto[property.key] = property.value;
});
return noProto;
}
// Folding block, commented to pass gjslint
// {{{
// [ wonkyQS, canonicalQS, obj ]
const qsTestCases = [
['__proto__=1',
'__proto__=1',
createWithNoPrototype([{ key: '__proto__', value: '1' }])],
['__defineGetter__=asdf',
'__defineGetter__=asdf',
JSON.parse('{"__defineGetter__":"asdf"}')],
['foo=918854443121279438895193',
'foo=918854443121279438895193',
{ 'foo': '918854443121279438895193' }],
['foo=bar', 'foo=bar', { 'foo': 'bar' }],
['foo=bar&foo=quux', 'foo=bar&foo=quux', { 'foo': ['bar', 'quux'] }],
['foo=1&bar=2', 'foo=1&bar=2', { 'foo': '1', 'bar': '2' }],
['my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F',
'my%20weird%20field=q1!2%22\'w%245%267%2Fz8)%3F',
{ 'my weird field': 'q1!2"\'w$5&7/z8)?' }],
['foo%3Dbaz=bar', 'foo%3Dbaz=bar', { 'foo=baz': 'bar' }],
['foo=baz=bar', 'foo=baz%3Dbar', { 'foo': 'baz=bar' }],
['str=foo&arr=1&arr=2&arr=3&somenull=&undef=',
'str=foo&arr=1&arr=2&arr=3&somenull=&undef=',
{ 'str': 'foo',
'arr': ['1', '2', '3'],
'somenull': '',
'undef': '' }],
[' foo = bar ', '%20foo%20=%20bar%20', { ' foo ': ' bar ' }],
['foo=%zx', 'foo=%25zx', { 'foo': '%zx' }],
['foo=%EF%BF%BD', 'foo=%EF%BF%BD', { 'foo': '\ufffd' }],
// See: https://github.com/joyent/node/issues/1707
['hasOwnProperty=x&toString=foo&valueOf=bar&__defineGetter__=baz',
'hasOwnProperty=x&toString=foo&valueOf=bar&__defineGetter__=baz',
{ hasOwnProperty: 'x',
toString: 'foo',
valueOf: 'bar',
__defineGetter__: 'baz' }],
// See: https://github.com/joyent/node/issues/3058
['foo&bar=baz', 'foo=&bar=baz', { foo: '', bar: 'baz' }],
['a=b&c&d=e', 'a=b&c=&d=e', { a: 'b', c: '', d: 'e' }],
['a=b&c=&d=e', 'a=b&c=&d=e', { a: 'b', c: '', d: 'e' }],
['a=b&=c&d=e', 'a=b&=c&d=e', { 'a': 'b', '': 'c', 'd': 'e' }],
['a=b&=&c=d', 'a=b&=&c=d', { 'a': 'b', '': '', 'c': 'd' }],
['&&foo=bar&&', 'foo=bar', { foo: 'bar' }],
['&', '', {}],
['&&&&', '', {}],
['&=&', '=', { '': '' }],
['&=&=', '=&=', { '': [ '', '' ] }],
['=', '=', { '': '' }],
['+', '%20=', { ' ': '' }],
['+=', '%20=', { ' ': '' }],
['+&', '%20=', { ' ': '' }],
['=+', '=%20', { '': ' ' }],
['+=&', '%20=', { ' ': '' }],
['a&&b', 'a=&b=', { 'a': '', 'b': '' }],
['a=a&&b=b', 'a=a&b=b', { 'a': 'a', 'b': 'b' }],
['&a', 'a=', { 'a': '' }],
['&=', '=', { '': '' }],
['a&a&', 'a=&a=', { a: [ '', '' ] }],
['a&a&a&', 'a=&a=&a=', { a: [ '', '', '' ] }],
['a&a&a&a&', 'a=&a=&a=&a=', { a: [ '', '', '', '' ] }],
['a=&a=value&a=', 'a=&a=value&a=', { a: [ '', 'value', '' ] }],
['foo+bar=baz+quux', 'foo%20bar=baz%20quux', { 'foo bar': 'baz quux' }],
['+foo=+bar', '%20foo=%20bar', { ' foo': ' bar' }],
['a+', 'a%20=', { 'a ': '' }],
['=a+', '=a%20', { '': 'a ' }],
['a+&', 'a%20=', { 'a ': '' }],
['=a+&', '=a%20', { '': 'a ' }],
['%20+', '%20%20=', { ' ': '' }],
['=%20+', '=%20%20', { '': ' ' }],
['%20+&', '%20%20=', { ' ': '' }],
['=%20+&', '=%20%20', { '': ' ' }],
[null, '', {}],
[undefined, '', {}],
];
// [ wonkyQS, canonicalQS, obj ]
const qsColonTestCases = [
['foo:bar', 'foo:bar', { 'foo': 'bar' }],
['foo:bar;foo:quux', 'foo:bar;foo:quux', { 'foo': ['bar', 'quux'] }],
['foo:1&bar:2;baz:quux',
'foo:1%26bar%3A2;baz:quux',
{ 'foo': '1&bar:2', 'baz': 'quux' }],
['foo%3Abaz:bar', 'foo%3Abaz:bar', { 'foo:baz': 'bar' }],
['foo:baz:bar', 'foo:baz%3Abar', { 'foo': 'baz:bar' }],
];
// [wonkyObj, qs, canonicalObj]
function extendedFunction() {}
extendedFunction.prototype = { a: 'b' };
const qsWeirdObjects = [
// eslint-disable-next-line node-core/no-unescaped-regexp-dot
[{ regexp: /./g }, 'regexp=', { 'regexp': '' }],
// eslint-disable-next-line node-core/no-unescaped-regexp-dot
[{ regexp: new RegExp('.', 'g') }, 'regexp=', { 'regexp': '' }],
[{ fn: () => {} }, 'fn=', { 'fn': '' }],
[{ fn: new Function('') }, 'fn=', { 'fn': '' }],
[{ math: Math }, 'math=', { 'math': '' }],
[{ e: extendedFunction }, 'e=', { 'e': '' }],
[{ d: new Date() }, 'd=', { 'd': '' }],
[{ d: Date }, 'd=', { 'd': '' }],
[
{ f: new Boolean(false), t: new Boolean(true) },
'f=&t=',
{ 'f': '', 't': '' },
],
[{ f: false, t: true }, 'f=false&t=true', { 'f': 'false', 't': 'true' }],
[{ n: null }, 'n=', { 'n': '' }],
[{ nan: NaN }, 'nan=', { 'nan': '' }],
[{ inf: Infinity }, 'inf=', { 'inf': '' }],
[{ a: [], b: [] }, '', {}],
[{ a: 1, b: [] }, 'a=1', { 'a': '1' }],
];
// }}}
const vm = require('vm');
const foreignObject = vm.runInNewContext('({"foo": ["bar", "baz"]})');
const qsNoMungeTestCases = [
['', {}],
['foo=bar&foo=baz', { 'foo': ['bar', 'baz'] }],
['foo=bar&foo=baz', foreignObject],
['blah=burp', { 'blah': 'burp' }],
['a=!-._~\'()*', { 'a': '!-._~\'()*' }],
['a=abcdefghijklmnopqrstuvwxyz', { 'a': 'abcdefghijklmnopqrstuvwxyz' }],
['a=ABCDEFGHIJKLMNOPQRSTUVWXYZ', { 'a': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' }],
['a=0123456789', { 'a': '0123456789' }],
['gragh=1&gragh=3&goo=2', { 'gragh': ['1', '3'], 'goo': '2' }],
['frappucino=muffin&goat%5B%5D=scone&pond=moose',
{ 'frappucino': 'muffin', 'goat[]': 'scone', 'pond': 'moose' }],
['trololol=yes&lololo=no', { 'trololol': 'yes', 'lololo': 'no' }],
];
const qsUnescapeTestCases = [
['there is nothing to unescape here',
'there is nothing to unescape here'],
['there%20are%20several%20spaces%20that%20need%20to%20be%20unescaped',
'there are several spaces that need to be unescaped'],
['there%2Qare%0-fake%escaped values in%%%%this%9Hstring',
'there%2Qare%0-fake%escaped values in%%%%this%9Hstring'],
['%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%30%31%32%33%34%35%36%37',
' !"#$%&\'()*+,-./01234567'],
['%%2a', '%*'],
['%2sf%2a', '%2sf*'],
['%2%2af%2a', '%2*f*'],
];
assert.strictEqual(qs.parse('id=918854443121279438895193').id,
'918854443121279438895193');
function check(actual, expected, input) {
assert(!(actual instanceof Object));
const actualKeys = Object.keys(actual).sort();
const expectedKeys = Object.keys(expected).sort();
let msg;
if (typeof input === 'string') {
msg = `Input: ${inspect(input)}\n` +
`Actual keys: ${inspect(actualKeys)}\n` +
`Expected keys: ${inspect(expectedKeys)}`;
}
assert.deepStrictEqual(actualKeys, expectedKeys, msg);
expectedKeys.forEach((key) => {
if (typeof input === 'string') {
msg = `Input: ${inspect(input)}\n` +
`Key: ${inspect(key)}\n` +
`Actual value: ${inspect(actual[key])}\n` +
`Expected value: ${inspect(expected[key])}`;
} else {
msg = undefined;
}
assert.deepStrictEqual(actual[key], expected[key], msg);
});
}
// Test that the canonical qs is parsed properly.
qsTestCases.forEach((testCase) => {
check(qs.parse(testCase[0]), testCase[2], testCase[0]);
});
// Test that the colon test cases can do the same
qsColonTestCases.forEach((testCase) => {
check(qs.parse(testCase[0], ';', ':'), testCase[2], testCase[0]);
});
// Test the weird objects, that they get parsed properly
qsWeirdObjects.forEach((testCase) => {
check(qs.parse(testCase[1]), testCase[2], testCase[1]);
});
qsNoMungeTestCases.forEach((testCase) => {
assert.deepStrictEqual(qs.stringify(testCase[1], '&', '='), testCase[0]);
});
// Test the nested qs-in-qs case
{
const f = qs.parse('a=b&q=x%3Dy%26y%3Dz');
check(f, createWithNoPrototype([
{ key: 'a', value: 'b' },
{ key: 'q', value: 'x=y&y=z' },
]));
f.q = qs.parse(f.q);
const expectedInternal = createWithNoPrototype([
{ key: 'x', value: 'y' },
{ key: 'y', value: 'z' },
]);
check(f.q, expectedInternal);
}
// nested in colon
{
const f = qs.parse('a:b;q:x%3Ay%3By%3Az', ';', ':');
check(f, createWithNoPrototype([
{ key: 'a', value: 'b' },
{ key: 'q', value: 'x:y;y:z' },
]));
f.q = qs.parse(f.q, ';', ':');
const expectedInternal = createWithNoPrototype([
{ key: 'x', value: 'y' },
{ key: 'y', value: 'z' },
]);
check(f.q, expectedInternal);
}
// Now test stringifying
// basic
qsTestCases.forEach((testCase) => {
assert.strictEqual(qs.stringify(testCase[2]), testCase[1]);
});
qsColonTestCases.forEach((testCase) => {
assert.strictEqual(qs.stringify(testCase[2], ';', ':'), testCase[1]);
});
qsWeirdObjects.forEach((testCase) => {
assert.strictEqual(qs.stringify(testCase[0]), testCase[1]);
});
// BigInt values
assert.strictEqual(qs.stringify({ foo: 2n ** 1023n }),
'foo=' + 2n ** 1023n);
assert.strictEqual(qs.stringify([0n, 1n, 2n]),
'0=0&1=1&2=2');
assert.strictEqual(qs.stringify({ foo: 2n ** 1023n },
null,
null,
{ encodeURIComponent: (c) => c }),
'foo=' + 2n ** 1023n);
assert.strictEqual(qs.stringify([0n, 1n, 2n],
null,
null,
{ encodeURIComponent: (c) => c }),
'0=0&1=1&2=2');
// Invalid surrogate pair throws URIError
assert.throws(
() => qs.stringify({ foo: '\udc00' }),
{
code: 'ERR_INVALID_URI',
name: 'URIError',
message: 'URI malformed'
}
);
// Coerce numbers to string
assert.strictEqual(qs.stringify({ foo: 0 }), 'foo=0');
assert.strictEqual(qs.stringify({ foo: -0 }), 'foo=0');
assert.strictEqual(qs.stringify({ foo: 3 }), 'foo=3');
assert.strictEqual(qs.stringify({ foo: -72.42 }), 'foo=-72.42');
assert.strictEqual(qs.stringify({ foo: NaN }), 'foo=');
assert.strictEqual(qs.stringify({ foo: 1e21 }), 'foo=1e%2B21');
assert.strictEqual(qs.stringify({ foo: Infinity }), 'foo=');
// nested
{
const f = qs.stringify({
a: 'b',
q: qs.stringify({
x: 'y',
y: 'z'
})
});
assert.strictEqual(f, 'a=b&q=x%3Dy%26y%3Dz');
}
qs.parse(undefined); // Should not throw.
// nested in colon
{
const f = qs.stringify({
a: 'b',
q: qs.stringify({
x: 'y',
y: 'z'
}, ';', ':')
}, ';', ':');
assert.strictEqual(f, 'a:b;q:x%3Ay%3By%3Az');
}
// empty string
assert.strictEqual(qs.stringify(), '');
assert.strictEqual(qs.stringify(0), '');
assert.strictEqual(qs.stringify([]), '');
assert.strictEqual(qs.stringify(null), '');
assert.strictEqual(qs.stringify(true), '');
check(qs.parse(), {});
// empty sep
check(qs.parse('a', []), { a: '' });
// empty eq
check(qs.parse('a', null, []), { '': 'a' });
// Test limiting
assert.strictEqual(
Object.keys(qs.parse('a=1&b=1&c=1', null, null, { maxKeys: 1 })).length,
1);
// Test limiting with a case that starts from `&`
assert.strictEqual(
Object.keys(qs.parse('&a', null, null, { maxKeys: 1 })).length,
0);
// Test removing limit
{
function testUnlimitedKeys() {
const query = {};
for (let i = 0; i < 2000; i++) query[i] = i;
const url = qs.stringify(query);
assert.strictEqual(
Object.keys(qs.parse(url, null, null, { maxKeys: 0 })).length,
2000);
}
testUnlimitedKeys();
}
{
const b = qs.unescapeBuffer('%d3%f2Ug%1f6v%24%5e%98%cb' +
'%0d%ac%a2%2f%9d%eb%d8%a2%e6');
// <Buffer d3 f2 55 67 1f 36 76 24 5e 98 cb 0d ac a2 2f 9d eb d8 a2 e6>
assert.strictEqual(b[0], 0xd3);
assert.strictEqual(b[1], 0xf2);
assert.strictEqual(b[2], 0x55);
assert.strictEqual(b[3], 0x67);
assert.strictEqual(b[4], 0x1f);
assert.strictEqual(b[5], 0x36);
assert.strictEqual(b[6], 0x76);
assert.strictEqual(b[7], 0x24);
assert.strictEqual(b[8], 0x5e);
assert.strictEqual(b[9], 0x98);
assert.strictEqual(b[10], 0xcb);
assert.strictEqual(b[11], 0x0d);
assert.strictEqual(b[12], 0xac);
assert.strictEqual(b[13], 0xa2);
assert.strictEqual(b[14], 0x2f);
assert.strictEqual(b[15], 0x9d);
assert.strictEqual(b[16], 0xeb);
assert.strictEqual(b[17], 0xd8);
assert.strictEqual(b[18], 0xa2);
assert.strictEqual(b[19], 0xe6);
}
assert.strictEqual(qs.unescapeBuffer('a+b', true).toString(), 'a b');
assert.strictEqual(qs.unescapeBuffer('a+b').toString(), 'a+b');
assert.strictEqual(qs.unescapeBuffer('a%').toString(), 'a%');
assert.strictEqual(qs.unescapeBuffer('a%2').toString(), 'a%2');
assert.strictEqual(qs.unescapeBuffer('a%20').toString(), 'a ');
assert.strictEqual(qs.unescapeBuffer('a%2g').toString(), 'a%2g');
assert.strictEqual(qs.unescapeBuffer('a%%').toString(), 'a%%');
// Test invalid encoded string
check(qs.parse('%\u0100=%\u0101'), { '%Ā': '%ā' });
// Test custom decode
{
function demoDecode(str) {
return str + str;
}
check(
qs.parse('a=a&b=b&c=c', null, null, { decodeURIComponent: demoDecode }),
{ aa: 'aa', bb: 'bb', cc: 'cc' });
check(
qs.parse('a=a&b=b&c=c', null, '==', { decodeURIComponent: (str) => str }),
{ 'a=a': '', 'b=b': '', 'c=c': '' });
}
// Test QueryString.unescape
{
function errDecode(str) {
throw new Error('To jump to the catch scope');
}
check(qs.parse('a=a', null, null, { decodeURIComponent: errDecode }),
{ a: 'a' });
}
// Test custom encode
{
function demoEncode(str) {
return str[0];
}
const obj = { aa: 'aa', bb: 'bb', cc: 'cc' };
assert.strictEqual(
qs.stringify(obj, null, null, { encodeURIComponent: demoEncode }),
'a=a&b=b&c=c');
}
// Test custom encode for different types
{
const obj = { number: 1, bigint: 2n, true: true, false: false, object: {} };
assert.strictEqual(
qs.stringify(obj, null, null, { encodeURIComponent: (v) => v }),
'number=1&bigint=2&true=true&false=false&object=');
}
// Test QueryString.unescapeBuffer
qsUnescapeTestCases.forEach((testCase) => {
assert.strictEqual(qs.unescape(testCase[0]), testCase[1]);
assert.strictEqual(qs.unescapeBuffer(testCase[0]).toString(), testCase[1]);
});
// Test overriding .unescape
{
const prevUnescape = qs.unescape;
qs.unescape = (str) => {
return str.replace(/o/g, '_');
};
check(
qs.parse('foo=bor'),
createWithNoPrototype([{ key: 'f__', value: 'b_r' }]));
qs.unescape = prevUnescape;
}
// Test separator and "equals" parsing order
check(qs.parse('foo&bar', '&', '&'), { foo: '', bar: '' });