Skip to content

Commit d591115

Browse files
authored
fix(url): make URLSearchParams record constructor spec compliant (#295)
Fixes the issue where byte replacement characters could result in duplicate entries when constructing `URLSearchParams` from a record. While current implementation follows the [spec](https://url.spec.whatwg.org/#urlsearchparams-initialize) to the word: > Otherwise, if init is a [record](https://webidl.spec.whatwg.org/#idl-record), then [for each](https://infra.spec.whatwg.org/#map-iterate) name → value of init, [append](https://infra.spec.whatwg.org/#list-append) (name, value) to query’s [list](https://url.spec.whatwg.org/#concept-urlsearchparams-list). using `params_append()` directly happens before the `USVString` conversion so that keys that are supposed to collide after conversion can be added as separate entries. By using `params_set()` we get the correct behavior without having to normalize the record beforehand. Extending `maybe_consume_sequence_or_record` with separate sequence and record handlers allows applying different behavior. Passes 2 remaining tests for `URLSearchParams` constructor.
1 parent 27d1d5d commit d591115

File tree

4 files changed

+22
-10
lines changed

4 files changed

+22
-10
lines changed

builtins/web/fetch/headers.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ bool Headers::init_entries(JSContext *cx, HandleObject self, HandleValue initv)
650650
// TODO: But note: forbidden headers have to be applied correctly.
651651
bool consumed = false;
652652
if (!core::maybe_consume_sequence_or_record<host_api::HostString, validate_header_name,
653-
append_valid_header>(cx, initv, self, &consumed,
653+
append_valid_header, append_valid_header>(cx, initv, self, &consumed,
654654
"Headers")) {
655655
return false;
656656
}

builtins/web/url.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ jsurl::JSUrlSearchParams *URLSearchParams::get_params(JSObject *self) {
184184
}
185185

186186
namespace {
187-
jsurl::SpecString append_impl_validate(JSContext *cx, JS::HandleValue key, const char *_) {
187+
jsurl::SpecString validate(JSContext *cx, JS::HandleValue key, const char *_) {
188188
return core::encode_spec_string(cx, key);
189189
}
190190
bool append_impl(JSContext *cx, JS::HandleObject self, jsurl::SpecString key, JS::HandleValue val,
@@ -199,6 +199,18 @@ bool append_impl(JSContext *cx, JS::HandleObject self, jsurl::SpecString key, JS
199199
jsurl::params_append(params, key, value);
200200
return true;
201201
}
202+
bool set_impl(JSContext *cx, JS::HandleObject self, jsurl::SpecString key,
203+
JS::HandleValue val, const char *_) {
204+
auto *const params = URLSearchParams::get_params(self);
205+
206+
auto value = core::encode_spec_string(cx, val);
207+
if (!value.data) {
208+
return false;
209+
}
210+
211+
jsurl::params_set(params, key, value);
212+
return true;
213+
}
202214
} // namespace
203215

204216
jsurl::SpecSlice URLSearchParams::serialize(JSContext *cx, JS::HandleObject self) {
@@ -207,7 +219,7 @@ jsurl::SpecSlice URLSearchParams::serialize(JSContext *cx, JS::HandleObject self
207219

208220
bool URLSearchParams::append(JSContext *cx, unsigned argc, JS::Value *vp) {
209221
METHOD_HEADER(2)
210-
auto value = append_impl_validate(cx, args[0], "append");
222+
auto value = validate(cx, args[0], "append");
211223
if (!append_impl(cx, self, value, args[1], "append")) {
212224
return false;
213225
}
@@ -416,7 +428,7 @@ JSObject *URLSearchParams::create(JSContext *cx, JS::HandleObject self,
416428

417429
bool consumed = false;
418430
const char *alt_text = ", or a value that can be stringified";
419-
if (!core::maybe_consume_sequence_or_record<jsurl::SpecString, append_impl_validate, append_impl>(
431+
if (!core::maybe_consume_sequence_or_record<jsurl::SpecString, validate, append_impl, set_impl>(
420432
cx, params_val, self, &consumed, "URLSearchParams", alt_text)) {
421433
return nullptr;
422434
}

runtime/sequence.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace core {
1313
* Extract <key,value> pairs from the given value if it is either a
1414
* sequence<sequence<Value> or a record<Value, Value>.
1515
*/
16-
template <typename T, auto validate, auto apply>
16+
template <typename T, auto validate, auto apply_sequence, auto apply_record>
1717
bool maybe_consume_sequence_or_record(JSContext *cx, JS::HandleValue initv, JS::HandleObject target,
1818
bool *consumed, const char *ctor_name,
1919
const char *alt_text = "") {
@@ -92,7 +92,7 @@ bool maybe_consume_sequence_or_record(JSContext *cx, JS::HandleValue initv, JS::
9292
return api::throw_error(cx, api::Errors::InvalidSequence, ctor_name, alt_text);
9393
}
9494

95-
if (!apply(cx, target, std::move(validated_key), value, ctor_name)) {
95+
if (!apply_sequence(cx, target, std::move(validated_key), value, ctor_name)) {
9696
return false;
9797
}
9898
}
@@ -128,7 +128,7 @@ bool maybe_consume_sequence_or_record(JSContext *cx, JS::HandleValue initv, JS::
128128
if (!JS_GetPropertyById(cx, init, curId, &value)) {
129129
return false;
130130
}
131-
if (!apply(cx, target, std::move(validated_key), value, ctor_name)) {
131+
if (!apply_record(cx, target, std::move(validated_key), value, ctor_name)) {
132132
return false;
133133
}
134134
}

tests/wpt-harness/expectations/url/urlsearchparams-constructor.any.js.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,15 @@
6969
"status": "PASS"
7070
},
7171
"Construct with 2 unpaired surrogates (no trailing)": {
72-
"status": "FAIL"
72+
"status": "PASS"
7373
},
7474
"Construct with 3 unpaired surrogates (no leading)": {
75-
"status": "FAIL"
75+
"status": "PASS"
7676
},
7777
"Construct with object with NULL, non-ASCII, and surrogate keys": {
7878
"status": "PASS"
7979
},
8080
"Custom [Symbol.iterator]": {
8181
"status": "PASS"
8282
}
83-
}
83+
}

0 commit comments

Comments
 (0)