mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
[autofix.ci] apply automated fixes
This commit is contained in:
@@ -14,26 +14,26 @@ static bool isArrayIndex(const String& key, unsigned& index)
|
||||
{
|
||||
if (key.isEmpty())
|
||||
return false;
|
||||
|
||||
|
||||
// Check if all characters are digits
|
||||
for (auto c : StringView(key).codeUnits()) {
|
||||
if (!isASCIIDigit(c))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Parse the integer
|
||||
auto parseResult = parseInteger<unsigned>(StringView(key));
|
||||
if (!parseResult.has_value())
|
||||
return false;
|
||||
|
||||
|
||||
index = parseResult.value();
|
||||
|
||||
|
||||
// Prevent creating huge sparse arrays - limit to reasonable size
|
||||
// Rails typically limits array indices to prevent DoS
|
||||
// We'll use a high limit that prevents obvious abuse
|
||||
if (index > 10000)
|
||||
return false;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -41,70 +41,70 @@ static bool isArrayIndex(const String& key, unsigned& index)
|
||||
static void parseRailsStyleParams(JSC::JSGlobalObject* globalObject, JSC::JSObject* result, const String& key, const String& value)
|
||||
{
|
||||
auto& vm = globalObject->vm();
|
||||
|
||||
|
||||
// Find the first bracket
|
||||
size_t bracketPos = key.find('[');
|
||||
|
||||
|
||||
// No brackets - simple key-value pair
|
||||
if (bracketPos == notFound) {
|
||||
// Ignore __proto__ for security
|
||||
if (key == "__proto__"_s)
|
||||
return;
|
||||
|
||||
|
||||
// Simple key-value assignment - last value wins
|
||||
result->putDirect(vm, Identifier::fromString(vm, key), jsString(vm, value));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Extract the base key
|
||||
String baseKey = key.substring(0, bracketPos);
|
||||
if (baseKey == "__proto__"_s)
|
||||
return;
|
||||
|
||||
|
||||
// Parse the rest of the key to determine structure
|
||||
String remainder = key.substring(bracketPos);
|
||||
|
||||
|
||||
// Get existing value at baseKey
|
||||
JSValue existing = result->getDirect(vm, Identifier::fromString(vm, baseKey));
|
||||
|
||||
|
||||
// Handle [] notation (array append)
|
||||
if (remainder.startsWith("[]"_s)) {
|
||||
JSArray* array = nullptr;
|
||||
|
||||
|
||||
// Check if we already have a value at this key
|
||||
if (!existing.isEmpty()) {
|
||||
if (!existing.isObject())
|
||||
return; // Can't convert primitive to array
|
||||
|
||||
|
||||
JSObject* obj = asObject(existing);
|
||||
if (!obj->inherits<JSArray>())
|
||||
return; // Type conflict - it's an object, not an array
|
||||
|
||||
|
||||
array = jsCast<JSArray*>(obj);
|
||||
} else {
|
||||
// Create new array
|
||||
array = JSArray::create(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0);
|
||||
result->putDirect(vm, Identifier::fromString(vm, baseKey), array);
|
||||
}
|
||||
|
||||
|
||||
// Check if there's more nesting after []
|
||||
if (remainder.length() > 2 && remainder[2] == '[') {
|
||||
// Handle cases like users[][name] - create object and recursively parse
|
||||
String nestedRemainder = remainder.substring(2);
|
||||
|
||||
|
||||
// Create a new object for this array element
|
||||
JSObject* nestedObj = constructEmptyObject(vm, globalObject->nullPrototypeObjectStructure());
|
||||
array->putDirectIndex(globalObject, array->length(), nestedObj);
|
||||
|
||||
|
||||
// Recursively parse the nested structure
|
||||
// Remove the leading [ and find the closing ]
|
||||
size_t closeBracket = nestedRemainder.find(']');
|
||||
if (closeBracket != notFound) {
|
||||
String nestedKey = nestedRemainder.substring(1, closeBracket - 1);
|
||||
String afterBracket = closeBracket + 1 < nestedRemainder.length()
|
||||
? nestedRemainder.substring(closeBracket + 1)
|
||||
String afterBracket = closeBracket + 1 < nestedRemainder.length()
|
||||
? nestedRemainder.substring(closeBracket + 1)
|
||||
: String();
|
||||
|
||||
|
||||
if (afterBracket.isEmpty()) {
|
||||
// Simple nested property like users[][name]
|
||||
if (nestedKey != "__proto__"_s) {
|
||||
@@ -123,31 +123,31 @@ static void parseRailsStyleParams(JSC::JSGlobalObject* globalObject, JSC::JSObje
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Handle [key] notation (could be array index or object property)
|
||||
size_t closeBracket = remainder.find(']');
|
||||
if (closeBracket == notFound)
|
||||
return; // Malformed
|
||||
|
||||
|
||||
String innerKey = remainder.substring(1, closeBracket - 1);
|
||||
if (innerKey == "__proto__"_s)
|
||||
return;
|
||||
|
||||
|
||||
// Determine if this should be an array (numeric index) or object (string key)
|
||||
unsigned index = 0;
|
||||
bool isIndex = isArrayIndex(innerKey, index);
|
||||
|
||||
|
||||
// Get or create the container (array or object)
|
||||
JSObject* container = nullptr;
|
||||
bool isArray = false;
|
||||
|
||||
|
||||
if (!existing.isEmpty()) {
|
||||
if (!existing.isObject())
|
||||
return; // Can't index into primitive
|
||||
|
||||
|
||||
container = asObject(existing);
|
||||
isArray = container->inherits<JSArray>();
|
||||
|
||||
|
||||
// Type consistency check
|
||||
if (isIndex && !isArray)
|
||||
return; // Trying to use array index on object
|
||||
@@ -164,20 +164,20 @@ static void parseRailsStyleParams(JSC::JSGlobalObject* globalObject, JSC::JSObje
|
||||
}
|
||||
result->putDirect(vm, Identifier::fromString(vm, baseKey), container);
|
||||
}
|
||||
|
||||
|
||||
// Check if there's more nesting
|
||||
size_t nextBracket = remainder.find('[', closeBracket + 1);
|
||||
if (nextBracket != notFound) {
|
||||
// More nesting - recursively parse
|
||||
String nestedRemainder = remainder.substring(closeBracket + 1);
|
||||
|
||||
|
||||
// Get or create nested object
|
||||
JSObject* nestedObj = nullptr;
|
||||
|
||||
|
||||
if (isArray) {
|
||||
JSArray* array = jsCast<JSArray*>(container);
|
||||
JSValue existingAtIndex = index < array->length() ? array->getIndexQuickly(index) : JSValue();
|
||||
|
||||
|
||||
if (!existingAtIndex.isEmpty() && existingAtIndex.isObject()) {
|
||||
nestedObj = asObject(existingAtIndex);
|
||||
} else {
|
||||
@@ -186,7 +186,7 @@ static void parseRailsStyleParams(JSC::JSGlobalObject* globalObject, JSC::JSObje
|
||||
}
|
||||
} else {
|
||||
JSValue existingNested = container->getDirect(vm, Identifier::fromString(vm, innerKey));
|
||||
|
||||
|
||||
if (!existingNested.isEmpty() && existingNested.isObject()) {
|
||||
nestedObj = asObject(existingNested);
|
||||
} else {
|
||||
@@ -194,16 +194,16 @@ static void parseRailsStyleParams(JSC::JSGlobalObject* globalObject, JSC::JSObje
|
||||
container->putDirect(vm, Identifier::fromString(vm, innerKey), nestedObj);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Parse the nested structure
|
||||
if (nestedRemainder.startsWith("["_s) && nestedRemainder.length() > 1) {
|
||||
size_t endBracket = nestedRemainder.find(']');
|
||||
if (endBracket != notFound) {
|
||||
String propertyName = nestedRemainder.substring(1, endBracket - 1);
|
||||
String afterProperty = endBracket + 1 < nestedRemainder.length()
|
||||
? nestedRemainder.substring(endBracket + 1)
|
||||
String afterProperty = endBracket + 1 < nestedRemainder.length()
|
||||
? nestedRemainder.substring(endBracket + 1)
|
||||
: String();
|
||||
|
||||
|
||||
if (afterProperty.isEmpty()) {
|
||||
// Simple property assignment
|
||||
if (propertyName != "__proto__"_s) {
|
||||
@@ -231,22 +231,22 @@ static void parseRailsStyleParams(JSC::JSGlobalObject* globalObject, JSC::JSObje
|
||||
JSObject* parseQueryParams(JSGlobalObject* globalObject, const String& queryString)
|
||||
{
|
||||
auto& vm = globalObject->vm();
|
||||
|
||||
|
||||
// Create result object with null prototype for security
|
||||
JSObject* queryObject = constructEmptyObject(vm, globalObject->nullPrototypeObjectStructure());
|
||||
|
||||
|
||||
if (queryString.isEmpty()) {
|
||||
return queryObject;
|
||||
}
|
||||
|
||||
|
||||
// Parse query string using WebKit's URLParser
|
||||
auto params = WTF::URLParser::parseURLEncodedForm(queryString);
|
||||
|
||||
|
||||
// Process each parameter with Rails-style parsing
|
||||
for (const auto& param : params) {
|
||||
parseRailsStyleParams(globalObject, queryObject, param.key, param.value);
|
||||
}
|
||||
|
||||
|
||||
return queryObject;
|
||||
}
|
||||
|
||||
@@ -256,35 +256,35 @@ JSObject* parseURLQueryParams(JSGlobalObject* globalObject, const String& urlStr
|
||||
URL url(urlString);
|
||||
StringView queryView = url.query();
|
||||
String queryString = queryView.toString();
|
||||
|
||||
|
||||
return parseQueryParams(globalObject, queryString);
|
||||
}
|
||||
|
||||
// Export for testing
|
||||
JSC_DEFINE_HOST_FUNCTION(jsBunParseQueryParams, (JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame))
|
||||
JSC_DEFINE_HOST_FUNCTION(jsBunParseQueryParams, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
auto& vm = globalObject->vm();
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
|
||||
if (callFrame->argumentCount() < 1) {
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
|
||||
JSValue arg = callFrame->argument(0);
|
||||
if (!arg.isString()) {
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
|
||||
String queryString = arg.toWTFString(globalObject);
|
||||
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
|
||||
|
||||
|
||||
JSObject* result = parseQueryParams(globalObject, queryString);
|
||||
return JSValue::encode(result);
|
||||
}
|
||||
|
||||
extern "C" JSC::EncodedJSValue Bun__parseQueryParams(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame)
|
||||
extern "C" JSC::EncodedJSValue Bun__parseQueryParams(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame)
|
||||
{
|
||||
return jsBunParseQueryParams(globalObject, callFrame);
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
} // namespace Bun
|
||||
|
||||
@@ -18,4 +18,4 @@ JSC::JSObject* parseURLQueryParams(JSC::JSGlobalObject* globalObject, const WTF:
|
||||
JSC_DECLARE_HOST_FUNCTION(jsBunParseQueryParams);
|
||||
extern "C" JSC::EncodedJSValue Bun__parseQueryParams(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame);
|
||||
|
||||
} // namespace Bun
|
||||
} // namespace Bun
|
||||
|
||||
@@ -250,26 +250,25 @@ JSC_DEFINE_CUSTOM_GETTER(jsJSBunRequestGetCookies, (JSC::JSGlobalObject * global
|
||||
return JSValue::encode(cookies);
|
||||
}
|
||||
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(jsJSBunRequestGetQuery, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName))
|
||||
{
|
||||
auto& vm = globalObject->vm();
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
|
||||
JSBunRequest* request = jsDynamicCast<JSBunRequest*>(JSValue::decode(thisValue));
|
||||
if (!request)
|
||||
return JSValue::encode(jsUndefined());
|
||||
|
||||
|
||||
// Get the URL from the request
|
||||
JSValue urlValue = request->get(globalObject, Identifier::fromString(vm, "url"_s));
|
||||
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
|
||||
|
||||
|
||||
if (!urlValue.isString())
|
||||
return JSValue::encode(jsUndefined());
|
||||
|
||||
|
||||
String urlString = urlValue.toWTFString(globalObject);
|
||||
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
|
||||
|
||||
|
||||
// Use the extracted parsing function
|
||||
JSObject* queryObject = parseURLQueryParams(globalObject, urlString);
|
||||
return JSValue::encode(queryObject);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { bunEnv, bunExe } from "harness";
|
||||
import { expect, test } from "bun:test";
|
||||
|
||||
console.log("Test file loaded");
|
||||
|
||||
@@ -17,7 +16,7 @@ test("req.query - simple parameters", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}?name=john&age=30&active=true`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
expect(data).toEqual({
|
||||
name: "john",
|
||||
age: "30",
|
||||
@@ -39,7 +38,7 @@ test("req.query - empty query string", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
expect(data).toEqual({});
|
||||
});
|
||||
|
||||
@@ -57,7 +56,7 @@ test("req.query - URL encoded values", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}?message=Hello%20World&special=%40%23%24%25`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
expect(data).toEqual({
|
||||
message: "Hello World",
|
||||
special: "@#$%",
|
||||
@@ -78,7 +77,7 @@ test("req.query - Rails-style nested objects", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}?user[name]=john&user[age]=30&user[email]=john@example.com`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
expect(data).toEqual({
|
||||
user: {
|
||||
name: "john",
|
||||
@@ -100,9 +99,11 @@ test("req.query - Rails-style deeply nested objects", async () => {
|
||||
},
|
||||
});
|
||||
|
||||
const res = await fetch(`${server.url}?person[address][street]=123%20Main&person[address][city]=Portland&person[name]=Bob`);
|
||||
const res = await fetch(
|
||||
`${server.url}?person[address][street]=123%20Main&person[address][city]=Portland&person[name]=Bob`,
|
||||
);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
expect(data).toEqual({
|
||||
person: {
|
||||
address: {
|
||||
@@ -128,7 +129,7 @@ test("req.query - Rails-style arrays with empty brackets", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}?ids[]=1&ids[]=2&ids[]=3`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
expect(data).toEqual({
|
||||
ids: ["1", "2", "3"],
|
||||
});
|
||||
@@ -148,7 +149,7 @@ test("req.query - Rails-style indexed arrays", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}?items[0]=apple&items[1]=banana&items[2]=orange`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
expect(data).toEqual({
|
||||
items: ["apple", "banana", "orange"],
|
||||
});
|
||||
@@ -170,7 +171,7 @@ test("req.query - Rails-style indexed arrays", async () => {
|
||||
//
|
||||
// const res = await fetch(`${server.url}?user[tags][]=admin&user[tags][]=developer&user[name]=alice`);
|
||||
// const data = await res.json();
|
||||
//
|
||||
//
|
||||
// expect(data).toEqual({
|
||||
// user: {
|
||||
// tags: ["admin", "developer"],
|
||||
@@ -193,7 +194,7 @@ test("req.query - duplicate keys (last wins)", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}?color=red&color=blue&color=green`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
// In simple key-value pairs, last value wins
|
||||
expect(data).toEqual({
|
||||
color: "green",
|
||||
@@ -214,7 +215,7 @@ test("req.query - mixed simple and nested parameters", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}?simple=value&nested[key]=nestedValue&array[]=1&array[]=2`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
expect(data).toEqual({
|
||||
simple: "value",
|
||||
nested: {
|
||||
@@ -238,7 +239,7 @@ test("req.query - numeric-looking keys", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}?123=numeric&0=zero&normal=text`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
expect(data).toEqual({
|
||||
"123": "numeric",
|
||||
"0": "zero",
|
||||
@@ -260,7 +261,7 @@ test("req.query - empty values", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}?empty=&also_empty&has_value=yes`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
expect(data).toEqual({
|
||||
empty: "",
|
||||
also_empty: "",
|
||||
@@ -282,7 +283,7 @@ test("req.query - complex nested structure", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}?users[0][name]=alice&users[0][age]=25&users[1][name]=bob&users[1][age]=30`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
expect(data).toEqual({
|
||||
users: [
|
||||
{ name: "alice", age: "25" },
|
||||
@@ -305,13 +306,13 @@ test("req.query - __proto__ is ignored for security", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}?__proto__=evil&user[__proto__]=bad&normal=ok`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
// __proto__ keys should be ignored
|
||||
expect(data).toEqual({
|
||||
normal: "ok",
|
||||
user: {},
|
||||
});
|
||||
|
||||
|
||||
// Verify prototype wasn't polluted
|
||||
expect(Object.prototype.hasOwnProperty("evil")).toBe(false);
|
||||
});
|
||||
@@ -336,7 +337,7 @@ test("req.query - null prototype object", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}?test=value`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
expect(data.hasNullProto).toBe(true);
|
||||
expect(data.query).toEqual({ test: "value" });
|
||||
});
|
||||
@@ -355,7 +356,7 @@ test("req.query - special characters in keys", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}?key%20with%20spaces=value&symbols!%40%23=test`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
expect(data).toEqual({
|
||||
"key with spaces": "value",
|
||||
"symbols!@#": "test",
|
||||
@@ -377,7 +378,7 @@ test("req.query - works only with routes (Bun.serve)", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}?test=value`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
// Without routes, req.query won't be available (regular Request, not BunRequest)
|
||||
expect(data.hasQuery).toBe(false);
|
||||
});
|
||||
@@ -388,7 +389,7 @@ test("req.query - with routes", async () => {
|
||||
routes: {
|
||||
"/test": {
|
||||
GET(req) {
|
||||
return Response.json({
|
||||
return Response.json({
|
||||
hasQuery: "query" in req,
|
||||
query: req.query,
|
||||
});
|
||||
@@ -399,7 +400,7 @@ test("req.query - with routes", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}/test?foo=bar`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
expect(data.hasQuery).toBe(true);
|
||||
expect(data.query).toEqual({ foo: "bar" });
|
||||
});
|
||||
@@ -418,7 +419,7 @@ test("req.query - sparse indexed arrays", async () => {
|
||||
|
||||
const res = await fetch(`${server.url}?arr[0]=first&arr[2]=third&arr[5]=sixth`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
// Sparse arrays will have null in JSON for missing indices
|
||||
expect(data).toEqual({
|
||||
arr: ["first", null, "third", null, null, "sixth"],
|
||||
@@ -441,9 +442,9 @@ test("req.query - array and object type conflict", async () => {
|
||||
// the first type wins and conflicting params are ignored
|
||||
const res = await fetch(`${server.url}?items[]=array&items[key]=object`);
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
// First param established items as array, so object notation is ignored
|
||||
expect(data).toEqual({
|
||||
items: ["array"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { parseQueryParams } from "bun:internal-for-testing";
|
||||
import { expect, test } from "bun:test";
|
||||
|
||||
test("parseQueryParams - simple parameters", () => {
|
||||
const result = parseQueryParams("name=john&age=30&active=true");
|
||||
@@ -108,7 +108,7 @@ test("parseQueryParams - __proto__ is ignored for security", () => {
|
||||
expect(result).toEqual({
|
||||
normal: "ok",
|
||||
});
|
||||
|
||||
|
||||
// Verify prototype wasn't polluted
|
||||
expect(Object.prototype.hasOwnProperty("evil")).toBe(false);
|
||||
});
|
||||
@@ -135,4 +135,4 @@ test("parseQueryParams - array and object type conflict", () => {
|
||||
expect(result).toEqual({
|
||||
items: ["array"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user