mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
url: enforce valid UTF-8 in WHATWG parser
This commit implements the Web IDL USVString conversion, which mandates all unpaired Unicode surrogates be turned into U+FFFD REPLACEMENT CHARACTER. It also disallows Symbols to be used as USVString per spec. Certain functions call into C++ methods in the binding that use the Utf8Value class to access string arguments. Utf8Value already does the normalization using V8's String::Write, so in those cases, instead of doing the full USVString normalization, only a symbol check is done (`'' + val`, which uses ES's ToString, versus `String()` which has special provisions for symbols). PR-URL: https://github.com/nodejs/node/pull/11436 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
a7f7724167
commit
b610a4db1c
@ -23,6 +23,18 @@ const IteratorPrototype = Object.getPrototypeOf(
|
||||
Object.getPrototypeOf([][Symbol.iterator]())
|
||||
);
|
||||
|
||||
const unpairedSurrogateRe =
|
||||
/([^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])/;
|
||||
function toUSVString(val) {
|
||||
const str = '' + val;
|
||||
// As of V8 5.5, `str.search()` (and `unpairedSurrogateRe[@@search]()`) are
|
||||
// slower than `unpairedSurrogateRe.exec()`.
|
||||
const match = unpairedSurrogateRe.exec(str);
|
||||
if (!match)
|
||||
return str;
|
||||
return binding.toUSVString(str, match.index);
|
||||
}
|
||||
|
||||
class OpaqueOrigin {
|
||||
toString() {
|
||||
return 'null';
|
||||
@ -104,7 +116,6 @@ function onParseComplete(flags, protocol, username, password,
|
||||
|
||||
// Reused by URL constructor and URL#href setter.
|
||||
function parse(url, input, base) {
|
||||
input = String(input);
|
||||
const base_context = base ? base[context] : undefined;
|
||||
url[context] = new StorageObject();
|
||||
binding.parse(input.trim(), -1,
|
||||
@ -206,8 +217,10 @@ function onParseHashComplete(flags, protocol, username, password,
|
||||
|
||||
class URL {
|
||||
constructor(input, base) {
|
||||
// toUSVString is not needed.
|
||||
input = '' + input;
|
||||
if (base !== undefined && !(base instanceof URL))
|
||||
base = new URL(String(base));
|
||||
base = new URL(base);
|
||||
parse(this, input, base);
|
||||
}
|
||||
|
||||
@ -315,6 +328,8 @@ Object.defineProperties(URL.prototype, {
|
||||
return this[kFormat]({});
|
||||
},
|
||||
set(input) {
|
||||
// toUSVString is not needed.
|
||||
input = '' + input;
|
||||
parse(this, input);
|
||||
}
|
||||
},
|
||||
@ -332,7 +347,8 @@ Object.defineProperties(URL.prototype, {
|
||||
return this[context].scheme;
|
||||
},
|
||||
set(scheme) {
|
||||
scheme = String(scheme);
|
||||
// toUSVString is not needed.
|
||||
scheme = '' + scheme;
|
||||
if (scheme.length === 0)
|
||||
return;
|
||||
binding.parse(scheme, binding.kSchemeStart, null, this[context],
|
||||
@ -346,7 +362,8 @@ Object.defineProperties(URL.prototype, {
|
||||
return this[context].username || '';
|
||||
},
|
||||
set(username) {
|
||||
username = String(username);
|
||||
// toUSVString is not needed.
|
||||
username = '' + username;
|
||||
if (!this.hostname)
|
||||
return;
|
||||
const ctx = this[context];
|
||||
@ -366,7 +383,8 @@ Object.defineProperties(URL.prototype, {
|
||||
return this[context].password || '';
|
||||
},
|
||||
set(password) {
|
||||
password = String(password);
|
||||
// toUSVString is not needed.
|
||||
password = '' + password;
|
||||
if (!this.hostname)
|
||||
return;
|
||||
const ctx = this[context];
|
||||
@ -391,7 +409,8 @@ Object.defineProperties(URL.prototype, {
|
||||
},
|
||||
set(host) {
|
||||
const ctx = this[context];
|
||||
host = String(host);
|
||||
// toUSVString is not needed.
|
||||
host = '' + host;
|
||||
if (this[cannotBeBase] ||
|
||||
(this[special] && host.length === 0)) {
|
||||
// Cannot set the host if cannot-be-base is set or
|
||||
@ -415,7 +434,8 @@ Object.defineProperties(URL.prototype, {
|
||||
},
|
||||
set(host) {
|
||||
const ctx = this[context];
|
||||
host = String(host);
|
||||
// toUSVString is not needed.
|
||||
host = '' + host;
|
||||
if (this[cannotBeBase] ||
|
||||
(this[special] && host.length === 0)) {
|
||||
// Cannot set the host if cannot-be-base is set or
|
||||
@ -439,11 +459,12 @@ Object.defineProperties(URL.prototype, {
|
||||
return port === undefined ? '' : String(port);
|
||||
},
|
||||
set(port) {
|
||||
// toUSVString is not needed.
|
||||
port = '' + port;
|
||||
const ctx = this[context];
|
||||
if (!ctx.host || this[cannotBeBase] ||
|
||||
this.protocol === 'file:')
|
||||
return;
|
||||
port = String(port);
|
||||
if (port === '') {
|
||||
ctx.port = undefined;
|
||||
return;
|
||||
@ -462,9 +483,11 @@ Object.defineProperties(URL.prototype, {
|
||||
return ctx.path !== undefined ? `/${ctx.path.join('/')}` : '';
|
||||
},
|
||||
set(path) {
|
||||
// toUSVString is not needed.
|
||||
path = '' + path;
|
||||
if (this[cannotBeBase])
|
||||
return;
|
||||
binding.parse(String(path), binding.kPathStart, null, this[context],
|
||||
binding.parse(path, binding.kPathStart, null, this[context],
|
||||
onParsePathComplete.bind(this));
|
||||
}
|
||||
},
|
||||
@ -477,7 +500,7 @@ Object.defineProperties(URL.prototype, {
|
||||
},
|
||||
set(search) {
|
||||
const ctx = this[context];
|
||||
search = String(search);
|
||||
search = toUSVString(search);
|
||||
if (!search) {
|
||||
ctx.query = null;
|
||||
ctx.flags &= ~binding.URL_FLAGS_HAS_QUERY;
|
||||
@ -509,7 +532,8 @@ Object.defineProperties(URL.prototype, {
|
||||
},
|
||||
set(hash) {
|
||||
const ctx = this[context];
|
||||
hash = String(hash);
|
||||
// toUSVString is not needed.
|
||||
hash = '' + hash;
|
||||
if (this.protocol === 'javascript:')
|
||||
return;
|
||||
if (!hash) {
|
||||
@ -652,19 +676,22 @@ class URLSearchParams {
|
||||
if (pair.length !== 2) {
|
||||
throw new TypeError('Each query pair must be a name/value tuple');
|
||||
}
|
||||
this[searchParams].push(String(pair[0]), String(pair[1]));
|
||||
const key = toUSVString(pair[0]);
|
||||
const value = toUSVString(pair[1]);
|
||||
this[searchParams].push(key, value);
|
||||
}
|
||||
} else {
|
||||
// record<USVString, USVString>
|
||||
this[searchParams] = [];
|
||||
for (const key of Object.keys(init)) {
|
||||
const value = String(init[key]);
|
||||
for (var key of Object.keys(init)) {
|
||||
key = toUSVString(key);
|
||||
const value = toUSVString(init[key]);
|
||||
this[searchParams].push(key, value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// USVString
|
||||
init = String(init);
|
||||
init = toUSVString(init);
|
||||
if (init[0] === '?') init = init.slice(1);
|
||||
initSearchParams(this, init);
|
||||
}
|
||||
@ -743,8 +770,8 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', {
|
||||
throw new TypeError('"name" and "value" arguments must be specified');
|
||||
}
|
||||
|
||||
name = String(name);
|
||||
value = String(value);
|
||||
name = toUSVString(name);
|
||||
value = toUSVString(value);
|
||||
this[searchParams].push(name, value);
|
||||
update(this[context], this);
|
||||
},
|
||||
@ -758,7 +785,7 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', {
|
||||
}
|
||||
|
||||
const list = this[searchParams];
|
||||
name = String(name);
|
||||
name = toUSVString(name);
|
||||
for (var i = 0; i < list.length;) {
|
||||
const cur = list[i];
|
||||
if (cur === name) {
|
||||
@ -779,7 +806,7 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', {
|
||||
}
|
||||
|
||||
const list = this[searchParams];
|
||||
name = String(name);
|
||||
name = toUSVString(name);
|
||||
for (var i = 0; i < list.length; i += 2) {
|
||||
if (list[i] === name) {
|
||||
return list[i + 1];
|
||||
@ -798,7 +825,7 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', {
|
||||
|
||||
const list = this[searchParams];
|
||||
const values = [];
|
||||
name = String(name);
|
||||
name = toUSVString(name);
|
||||
for (var i = 0; i < list.length; i += 2) {
|
||||
if (list[i] === name) {
|
||||
values.push(list[i + 1]);
|
||||
@ -816,7 +843,7 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', {
|
||||
}
|
||||
|
||||
const list = this[searchParams];
|
||||
name = String(name);
|
||||
name = toUSVString(name);
|
||||
for (var i = 0; i < list.length; i += 2) {
|
||||
if (list[i] === name) {
|
||||
return true;
|
||||
@ -834,8 +861,8 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', {
|
||||
}
|
||||
|
||||
const list = this[searchParams];
|
||||
name = String(name);
|
||||
value = String(value);
|
||||
name = toUSVString(name);
|
||||
value = toUSVString(value);
|
||||
|
||||
// If there are any name-value pairs whose name is `name`, in `list`, set
|
||||
// the value of the first such name-value pair to `value` and remove the
|
||||
@ -1098,11 +1125,13 @@ function originFor(url, base) {
|
||||
}
|
||||
|
||||
function domainToASCII(domain) {
|
||||
return binding.domainToASCII(String(domain));
|
||||
// toUSVString is not needed.
|
||||
return binding.domainToASCII('' + domain);
|
||||
}
|
||||
|
||||
function domainToUnicode(domain) {
|
||||
return binding.domainToUnicode(String(domain));
|
||||
// toUSVString is not needed.
|
||||
return binding.domainToUnicode('' + domain);
|
||||
}
|
||||
|
||||
// Utility function that converts a URL object into an ordinary
|
||||
@ -1188,11 +1217,14 @@ function getPathFromURL(path) {
|
||||
return isWindows ? getPathFromURLWin32(path) : getPathFromURLPosix(path);
|
||||
}
|
||||
|
||||
exports.getPathFromURL = getPathFromURL;
|
||||
exports.URL = URL;
|
||||
exports.URLSearchParams = URLSearchParams;
|
||||
exports.domainToASCII = domainToASCII;
|
||||
exports.domainToUnicode = domainToUnicode;
|
||||
exports.urlToOptions = urlToOptions;
|
||||
exports.formatSymbol = kFormat;
|
||||
exports.searchParamsSymbol = searchParams;
|
||||
module.exports = {
|
||||
toUSVString,
|
||||
getPathFromURL,
|
||||
URL,
|
||||
URLSearchParams,
|
||||
domainToASCII,
|
||||
domainToUnicode,
|
||||
urlToOptions,
|
||||
formatSymbol: kFormat,
|
||||
searchParamsSymbol: searchParams
|
||||
};
|
||||
|
@ -20,6 +20,8 @@
|
||||
#include <unicode/utf.h>
|
||||
#endif
|
||||
|
||||
#define UNICODE_REPLACEMENT_CHARACTER 0xFFFD
|
||||
|
||||
namespace node {
|
||||
|
||||
using v8::Array;
|
||||
@ -143,6 +145,21 @@ namespace url {
|
||||
}
|
||||
#endif
|
||||
|
||||
// If a UTF-16 character is a low/trailing surrogate.
|
||||
static inline bool IsUnicodeTrail(uint16_t c) {
|
||||
return (c & 0xFC00) == 0xDC00;
|
||||
}
|
||||
|
||||
// If a UTF-16 character is a surrogate.
|
||||
static inline bool IsUnicodeSurrogate(uint16_t c) {
|
||||
return (c & 0xF800) == 0xD800;
|
||||
}
|
||||
|
||||
// If a UTF-16 surrogate is a low/trailing one.
|
||||
static inline bool IsUnicodeSurrogateTrail(uint16_t c) {
|
||||
return (c & 0x400) != 0;
|
||||
}
|
||||
|
||||
static url_host_type ParseIPv6Host(url_host* host,
|
||||
const char* input,
|
||||
size_t length) {
|
||||
@ -1351,6 +1368,41 @@ namespace url {
|
||||
v8::NewStringType::kNormal).ToLocalChecked());
|
||||
}
|
||||
|
||||
static void ToUSVString(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
CHECK_GE(args.Length(), 2);
|
||||
CHECK(args[0]->IsString());
|
||||
CHECK(args[1]->IsNumber());
|
||||
|
||||
TwoByteValue value(env->isolate(), args[0]);
|
||||
const size_t n = value.length();
|
||||
|
||||
const int64_t start = args[1]->IntegerValue(env->context()).FromJust();
|
||||
CHECK_GE(start, 0);
|
||||
|
||||
for (size_t i = start; i < n; i++) {
|
||||
uint16_t c = value[i];
|
||||
if (!IsUnicodeSurrogate(c)) {
|
||||
continue;
|
||||
} else if (IsUnicodeSurrogateTrail(c) || i == n - 1) {
|
||||
value[i] = UNICODE_REPLACEMENT_CHARACTER;
|
||||
} else {
|
||||
uint16_t d = value[i + 1];
|
||||
if (IsUnicodeTrail(d)) {
|
||||
i++;
|
||||
} else {
|
||||
value[i] = UNICODE_REPLACEMENT_CHARACTER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
args.GetReturnValue().Set(
|
||||
String::NewFromTwoByte(env->isolate(),
|
||||
*value,
|
||||
v8::NewStringType::kNormal,
|
||||
n).ToLocalChecked());
|
||||
}
|
||||
|
||||
static void DomainToASCII(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
CHECK_GE(args.Length(), 1);
|
||||
@ -1398,6 +1450,7 @@ namespace url {
|
||||
Environment* env = Environment::GetCurrent(context);
|
||||
env->SetMethod(target, "parse", Parse);
|
||||
env->SetMethod(target, "encodeAuth", EncodeAuthSet);
|
||||
env->SetMethod(target, "toUSVString", ToUSVString);
|
||||
env->SetMethod(target, "domainToASCII", DomainToASCII);
|
||||
env->SetMethod(target, "domainToUnicode", DomainToUnicode);
|
||||
|
||||
|
237
test/fixtures/url-setter-tests-additional.js
vendored
Normal file
237
test/fixtures/url-setter-tests-additional.js
vendored
Normal file
@ -0,0 +1,237 @@
|
||||
module.exports = {
|
||||
'username': [
|
||||
{
|
||||
'comment': 'Surrogate pair',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uD83D\uDE00',
|
||||
'expected': {
|
||||
'href': 'https://%F0%9F%98%80@github.com/',
|
||||
'username': '%F0%9F%98%80'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired low surrogate 1',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uD83D',
|
||||
'expected': {
|
||||
'href': 'https://%EF%BF%BD@github.com/',
|
||||
'username': '%EF%BF%BD'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired low surrogate 2',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uD83Dnode',
|
||||
'expected': {
|
||||
'href': 'https://%EF%BF%BDnode@github.com/',
|
||||
'username': '%EF%BF%BDnode'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired high surrogate 1',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uDE00',
|
||||
'expected': {
|
||||
'href': 'https://%EF%BF%BD@github.com/',
|
||||
'username': '%EF%BF%BD'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired high surrogate 2',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uDE00node',
|
||||
'expected': {
|
||||
'href': 'https://%EF%BF%BDnode@github.com/',
|
||||
'username': '%EF%BF%BDnode'
|
||||
}
|
||||
}
|
||||
],
|
||||
'password': [
|
||||
{
|
||||
'comment': 'Surrogate pair',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uD83D\uDE00',
|
||||
'expected': {
|
||||
'href': 'https://:%F0%9F%98%80@github.com/',
|
||||
'password': '%F0%9F%98%80'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired low surrogate 1',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uD83D',
|
||||
'expected': {
|
||||
'href': 'https://:%EF%BF%BD@github.com/',
|
||||
'password': '%EF%BF%BD'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired low surrogate 2',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uD83Dnode',
|
||||
'expected': {
|
||||
'href': 'https://:%EF%BF%BDnode@github.com/',
|
||||
'password': '%EF%BF%BDnode'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired high surrogate 1',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uDE00',
|
||||
'expected': {
|
||||
'href': 'https://:%EF%BF%BD@github.com/',
|
||||
'password': '%EF%BF%BD'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired high surrogate 2',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uDE00node',
|
||||
'expected': {
|
||||
'href': 'https://:%EF%BF%BDnode@github.com/',
|
||||
'password': '%EF%BF%BDnode'
|
||||
}
|
||||
}
|
||||
],
|
||||
'pathname': [
|
||||
{
|
||||
'comment': 'Surrogate pair',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '/\uD83D\uDE00',
|
||||
'expected': {
|
||||
'href': 'https://github.com/%F0%9F%98%80',
|
||||
'pathname': '/%F0%9F%98%80'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired low surrogate 1',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '/\uD83D',
|
||||
'expected': {
|
||||
'href': 'https://github.com/%EF%BF%BD',
|
||||
'pathname': '/%EF%BF%BD'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired low surrogate 2',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '/\uD83Dnode',
|
||||
'expected': {
|
||||
'href': 'https://github.com/%EF%BF%BDnode',
|
||||
'pathname': '/%EF%BF%BDnode'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired high surrogate 1',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '/\uDE00',
|
||||
'expected': {
|
||||
'href': 'https://github.com/%EF%BF%BD',
|
||||
'pathname': '/%EF%BF%BD'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired high surrogate 2',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '/\uDE00node',
|
||||
'expected': {
|
||||
'href': 'https://github.com/%EF%BF%BDnode',
|
||||
'pathname': '/%EF%BF%BDnode'
|
||||
}
|
||||
}
|
||||
],
|
||||
'search': [
|
||||
{
|
||||
'comment': 'Surrogate pair',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uD83D\uDE00',
|
||||
'expected': {
|
||||
'href': 'https://github.com/?%F0%9F%98%80',
|
||||
'search': '?%F0%9F%98%80'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired low surrogate 1',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uD83D',
|
||||
'expected': {
|
||||
'href': 'https://github.com/?%EF%BF%BD',
|
||||
'search': '?%EF%BF%BD'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired low surrogate 2',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uD83Dnode',
|
||||
'expected': {
|
||||
'href': 'https://github.com/?%EF%BF%BDnode',
|
||||
'search': '?%EF%BF%BDnode'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired high surrogate 1',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uDE00',
|
||||
'expected': {
|
||||
'href': 'https://github.com/?%EF%BF%BD',
|
||||
'search': '?%EF%BF%BD'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired high surrogate 2',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uDE00node',
|
||||
'expected': {
|
||||
'href': 'https://github.com/?%EF%BF%BDnode',
|
||||
'search': '?%EF%BF%BDnode'
|
||||
}
|
||||
}
|
||||
],
|
||||
'hash': [
|
||||
{
|
||||
'comment': 'Surrogate pair',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uD83D\uDE00',
|
||||
'expected': {
|
||||
'href': 'https://github.com/#%F0%9F%98%80',
|
||||
'hash': '#%F0%9F%98%80'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired low surrogate 1',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uD83D',
|
||||
'expected': {
|
||||
'href': 'https://github.com/#%EF%BF%BD',
|
||||
'hash': '#%EF%BF%BD'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired low surrogate 2',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uD83Dnode',
|
||||
'expected': {
|
||||
'href': 'https://github.com/#%EF%BF%BDnode',
|
||||
'hash': '#%EF%BF%BDnode'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired high surrogate 1',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uDE00',
|
||||
'expected': {
|
||||
'href': 'https://github.com/#%EF%BF%BD',
|
||||
'hash': '#%EF%BF%BD'
|
||||
}
|
||||
},
|
||||
{
|
||||
'comment': 'Unpaired high surrogate 2',
|
||||
'href': 'https://github.com/',
|
||||
'new_value': '\uDE00node',
|
||||
'expected': {
|
||||
'href': 'https://github.com/#%EF%BF%BDnode',
|
||||
'hash': '#%EF%BF%BDnode'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
30
test/fixtures/url-tests-additional.js
vendored
30
test/fixtures/url-tests-additional.js
vendored
@ -3,4 +3,34 @@
|
||||
// This file contains test cases not part of the WPT
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
// surrogate pair
|
||||
'url': 'https://github.com/nodejs/\uD83D\uDE00node',
|
||||
'protocol': 'https:',
|
||||
'pathname': '/nodejs/%F0%9F%98%80node'
|
||||
},
|
||||
{
|
||||
// unpaired low surrogate
|
||||
'url': 'https://github.com/nodejs/\uD83D',
|
||||
'protocol': 'https:',
|
||||
'pathname': '/nodejs/%EF%BF%BD'
|
||||
},
|
||||
{
|
||||
// unpaired low surrogate
|
||||
'url': 'https://github.com/nodejs/\uD83Dnode',
|
||||
'protocol': 'https:',
|
||||
'pathname': '/nodejs/%EF%BF%BDnode'
|
||||
},
|
||||
{
|
||||
// unmatched high surrogate
|
||||
'url': 'https://github.com/nodejs/\uDE00',
|
||||
'protocol': 'https:',
|
||||
'pathname': '/nodejs/%EF%BF%BD'
|
||||
},
|
||||
{
|
||||
// unmatched high surrogate
|
||||
'url': 'https://github.com/nodejs/\uDE00node',
|
||||
'protocol': 'https:',
|
||||
'pathname': '/nodejs/%EF%BF%BDnode'
|
||||
}
|
||||
];
|
||||
|
@ -57,4 +57,13 @@ test(function() {
|
||||
assert.throws(() => {
|
||||
params.set('a');
|
||||
}, /^TypeError: "name" and "value" arguments must be specified$/);
|
||||
|
||||
const obj = { toString() { throw new Error('toString'); } };
|
||||
const sym = Symbol();
|
||||
assert.throws(() => params.set(obj, 'b'), /^Error: toString$/);
|
||||
assert.throws(() => params.set('a', obj), /^Error: toString$/);
|
||||
assert.throws(() => params.set(sym, 'b'),
|
||||
/^TypeError: Cannot convert a Symbol value to a string$/);
|
||||
assert.throws(() => params.set('a', sym),
|
||||
/^TypeError: Cannot convert a Symbol value to a string$/);
|
||||
}
|
||||
|
@ -207,3 +207,19 @@ test(() => {
|
||||
assert.throws(() => new URLSearchParams([{ [Symbol.iterator]: 42 }]),
|
||||
/^TypeError: Each query pair must be iterable$/);
|
||||
}
|
||||
|
||||
{
|
||||
const obj = { toString() { throw new Error('toString'); } };
|
||||
const sym = Symbol();
|
||||
|
||||
assert.throws(() => new URLSearchParams({ a: obj }), /^Error: toString$/);
|
||||
assert.throws(() => new URLSearchParams([['a', obj]]), /^Error: toString$/);
|
||||
assert.throws(() => new URLSearchParams(sym),
|
||||
/^TypeError: Cannot convert a Symbol value to a string$/);
|
||||
assert.throws(() => new URLSearchParams({ a: sym }),
|
||||
/^TypeError: Cannot convert a Symbol value to a string$/);
|
||||
assert.throws(() => new URLSearchParams([[sym, 'a']]),
|
||||
/^TypeError: Cannot convert a Symbol value to a string$/);
|
||||
assert.throws(() => new URLSearchParams([['a', sym]]),
|
||||
/^TypeError: Cannot convert a Symbol value to a string$/);
|
||||
}
|
||||
|
@ -51,6 +51,12 @@ test(function() {
|
||||
assert.throws(() => {
|
||||
params.delete();
|
||||
}, /^TypeError: "name" argument must be specified$/);
|
||||
|
||||
const obj = { toString() { throw new Error('toString'); } };
|
||||
const sym = Symbol();
|
||||
assert.throws(() => params.delete(obj), /^Error: toString$/);
|
||||
assert.throws(() => params.delete(sym),
|
||||
/^TypeError: Cannot convert a Symbol value to a string$/);
|
||||
}
|
||||
|
||||
// https://github.com/nodejs/node/issues/10480
|
||||
|
@ -42,4 +42,10 @@ test(function() {
|
||||
assert.throws(() => {
|
||||
params.get();
|
||||
}, /^TypeError: "name" argument must be specified$/);
|
||||
|
||||
const obj = { toString() { throw new Error('toString'); } };
|
||||
const sym = Symbol();
|
||||
assert.throws(() => params.get(obj), /^Error: toString$/);
|
||||
assert.throws(() => params.get(sym),
|
||||
/^TypeError: Cannot convert a Symbol value to a string$/);
|
||||
}
|
||||
|
@ -46,4 +46,10 @@ test(function() {
|
||||
assert.throws(() => {
|
||||
params.getAll();
|
||||
}, /^TypeError: "name" argument must be specified$/);
|
||||
|
||||
const obj = { toString() { throw new Error('toString'); } };
|
||||
const sym = Symbol();
|
||||
assert.throws(() => params.getAll(obj), /^Error: toString$/);
|
||||
assert.throws(() => params.getAll(sym),
|
||||
/^TypeError: Cannot convert a Symbol value to a string$/);
|
||||
}
|
||||
|
@ -45,4 +45,10 @@ test(function() {
|
||||
assert.throws(() => {
|
||||
params.has();
|
||||
}, /^TypeError: "name" argument must be specified$/);
|
||||
|
||||
const obj = { toString() { throw new Error('toString'); } };
|
||||
const sym = Symbol();
|
||||
assert.throws(() => params.has(obj), /^Error: toString$/);
|
||||
assert.throws(() => params.has(sym),
|
||||
/^TypeError: Cannot convert a Symbol value to a string$/);
|
||||
}
|
||||
|
@ -43,4 +43,13 @@ test(function() {
|
||||
assert.throws(() => {
|
||||
params.set('a');
|
||||
}, /^TypeError: "name" and "value" arguments must be specified$/);
|
||||
|
||||
const obj = { toString() { throw new Error('toString'); } };
|
||||
const sym = Symbol();
|
||||
assert.throws(() => params.append(obj, 'b'), /^Error: toString$/);
|
||||
assert.throws(() => params.append('a', obj), /^Error: toString$/);
|
||||
assert.throws(() => params.append(sym, 'b'),
|
||||
/^TypeError: Cannot convert a Symbol value to a string$/);
|
||||
assert.throws(() => params.append('a', sym),
|
||||
/^TypeError: Cannot convert a Symbol value to a string$/);
|
||||
}
|
||||
|
@ -5,8 +5,14 @@ const assert = require('assert');
|
||||
const URL = require('url').URL;
|
||||
|
||||
// Tests below are not from WPT.
|
||||
const serialized = 'a=a&a=1&a=true&a=undefined&a=null&a=%5Bobject%20Object%5D';
|
||||
const values = ['a', 1, true, undefined, null, {}];
|
||||
const serialized = 'a=a&a=1&a=true&a=undefined&a=null&a=%EF%BF%BD' +
|
||||
'&a=%EF%BF%BD&a=%F0%9F%98%80&a=%EF%BF%BD%EF%BF%BD' +
|
||||
'&a=%5Bobject%20Object%5D';
|
||||
const values = ['a', 1, true, undefined, null, '\uD83D', '\uDE00',
|
||||
'\uD83D\uDE00', '\uDE00\uD83D', {}];
|
||||
const normalizedValues = ['a', '1', 'true', 'undefined', 'null', '\uFFFD',
|
||||
'\uFFFD', '\uD83D\uDE00', '\uFFFD\uFFFD',
|
||||
'[object Object]'];
|
||||
|
||||
const m = new URL('http://example.org');
|
||||
const sp = m.searchParams;
|
||||
@ -27,7 +33,7 @@ assert.strictEqual(sp.toString(), '');
|
||||
|
||||
values.forEach((i) => sp.append('a', i));
|
||||
assert(sp.has('a'));
|
||||
assert.strictEqual(sp.getAll('a').length, 6);
|
||||
assert.strictEqual(sp.getAll('a').length, values.length);
|
||||
assert.strictEqual(sp.get('a'), 'a');
|
||||
|
||||
assert.strictEqual(sp.toString(), serialized);
|
||||
@ -39,23 +45,27 @@ assert.strictEqual(sp[Symbol.iterator], sp.entries);
|
||||
let key, val;
|
||||
let n = 0;
|
||||
for ([key, val] of sp) {
|
||||
assert.strictEqual(key, 'a');
|
||||
assert.strictEqual(val, String(values[n++]));
|
||||
assert.strictEqual(key, 'a', n);
|
||||
assert.strictEqual(val, normalizedValues[n], n);
|
||||
n++;
|
||||
}
|
||||
n = 0;
|
||||
for (key of sp.keys()) {
|
||||
assert.strictEqual(key, 'a');
|
||||
assert.strictEqual(key, 'a', n);
|
||||
n++;
|
||||
}
|
||||
n = 0;
|
||||
for (val of sp.values()) {
|
||||
assert.strictEqual(val, String(values[n++]));
|
||||
assert.strictEqual(val, normalizedValues[n], n);
|
||||
n++;
|
||||
}
|
||||
n = 0;
|
||||
sp.forEach(function(val, key, obj) {
|
||||
assert.strictEqual(this, undefined);
|
||||
assert.strictEqual(key, 'a');
|
||||
assert.strictEqual(val, String(values[n++]));
|
||||
assert.strictEqual(obj, sp);
|
||||
assert.strictEqual(this, undefined, n);
|
||||
assert.strictEqual(key, 'a', n);
|
||||
assert.strictEqual(val, normalizedValues[n], n);
|
||||
assert.strictEqual(obj, sp, n);
|
||||
n++;
|
||||
});
|
||||
sp.forEach(function() {
|
||||
assert.strictEqual(this, m);
|
||||
|
@ -1,9 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const URL = require('url').URL;
|
||||
const { test, assert_equals } = common.WPT;
|
||||
const additionalTestCases = require(
|
||||
path.join(common.fixturesDir, 'url-setter-tests-additional.js'));
|
||||
|
||||
if (!common.hasIntl) {
|
||||
// A handful of the tests fail when ICU is not included.
|
||||
@ -76,3 +79,45 @@ function runURLSettersTests(all_test_cases) {
|
||||
|
||||
startURLSettersTests()
|
||||
/* eslint-enable */
|
||||
|
||||
// Tests below are not from WPT.
|
||||
|
||||
{
|
||||
for (const attributeToBeSet in additionalTestCases) {
|
||||
if (attributeToBeSet === 'comment') {
|
||||
continue;
|
||||
}
|
||||
const testCases = additionalTestCases[attributeToBeSet];
|
||||
for (const testCase of testCases) {
|
||||
let name = `Setting <${testCase.href}>.${attributeToBeSet}` +
|
||||
` = "${testCase.new_value}"`;
|
||||
if ('comment' in testCase) {
|
||||
name += ' ' + testCase.comment;
|
||||
}
|
||||
test(function() {
|
||||
const url = new URL(testCase.href);
|
||||
url[attributeToBeSet] = testCase.new_value;
|
||||
for (const attribute in testCase.expected) {
|
||||
assert_equals(url[attribute], testCase.expected[attribute]);
|
||||
}
|
||||
}, 'URL: ' + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('http://example.com/');
|
||||
const obj = { toString() { throw new Error('toString'); } };
|
||||
const sym = Symbol();
|
||||
const props = Object.getOwnPropertyDescriptors(Object.getPrototypeOf(url));
|
||||
for (const [name, { set }] of Object.entries(props)) {
|
||||
if (set) {
|
||||
assert.throws(() => url[name] = obj,
|
||||
/^Error: toString$/,
|
||||
`url.${name} = { toString() { throw ... } }`);
|
||||
assert.throws(() => url[name] = sym,
|
||||
/^TypeError: Cannot convert a Symbol value to a string$/,
|
||||
`url.${name} = ${String(sym)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user