[autofix.ci] apply automated fixes

This commit is contained in:
autofix-ci[bot]
2025-09-02 14:43:45 +00:00
committed by GitHub
parent ec76f45369
commit ca8d2fad2e
5 changed files with 86 additions and 86 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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"],
});
});
});

View File

@@ -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"],
});
});
});