url: throw invalid this on detached accessors

Previously, using Reflect.get/set or calling a member
method like toString() detached from the instance would
result in an obscure internal error. This adds a proper
brand check and throws `ERR_INVALID_THIS` when appropriate.

Signed-off-by: James M Snell <jasnell@gmail.com>

PR-URL: https://github.com/nodejs/node/pull/39752
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Zijian Liu <lxxyxzj@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
This commit is contained in:
James M Snell committed 2021-08-15 07:26:52 -07:00
1 parent 75bd4da553
commit e1e669b109
2 files changed
+97

No files matched your search

+52
View File
@@ -625,6 +625,10 @@ function onParseHashComplete(flags, protocol, username, password,
this[context].fragment = fragment;
}
function isURLThis(self) {
return self?.[context] !== undefined;
}
class URL {
constructor(input, base) {
// toUSVString is not needed.
@@ -737,14 +741,20 @@ class URL {
// https://heycam.github.io/webidl/#es-stringifier
toString() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
return this[kFormat]({});
}
get href() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
return this[kFormat]({});
}
set href(input) {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
// toUSVString is not needed.
input = `${input}`;
parse(input, -1, undefined, undefined,
@@ -753,6 +763,8 @@ class URL {
// readonly
get origin() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
// Refs: https://url.spec.whatwg.org/#concept-url-origin
const ctx = this[context];
switch (ctx.scheme) {
@@ -776,10 +788,14 @@ class URL {
}
get protocol() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
return this[context].scheme;
}
set protocol(scheme) {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
// toUSVString is not needed.
scheme = `${scheme}`;
if (scheme.length === 0)
@@ -790,10 +806,14 @@ class URL {
}
get username() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
return this[context].username;
}
set username(username) {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
// toUSVString is not needed.
username = `${username}`;
if (this[cannotHaveUsernamePasswordPort])
@@ -809,10 +829,14 @@ class URL {
}
get password() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
return this[context].password;
}
set password(password) {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
// toUSVString is not needed.
password = `${password}`;
if (this[cannotHaveUsernamePasswordPort])
@@ -828,6 +852,8 @@ class URL {
}
get host() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
const ctx = this[context];
let ret = ctx.host || '';
if (ctx.port !== null)
@@ -836,6 +862,8 @@ class URL {
}
set host(host) {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
const ctx = this[context];
// toUSVString is not needed.
host = `${host}`;
@@ -848,10 +876,14 @@ class URL {
}
get hostname() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
return this[context].host || '';
}
set hostname(host) {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
const ctx = this[context];
// toUSVString is not needed.
host = `${host}`;
@@ -863,11 +895,15 @@ class URL {
}
get port() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
const port = this[context].port;
return port === null ? '' : String(port);
}
set port(port) {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
// toUSVString is not needed.
port = `${port}`;
if (this[cannotHaveUsernamePasswordPort])
@@ -882,6 +918,8 @@ class URL {
}
get pathname() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
const ctx = this[context];
if (this[cannotBeBase])
return ctx.path[0];
@@ -891,6 +929,8 @@ class URL {
}
set pathname(path) {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
// toUSVString is not needed.
path = `${path}`;
if (this[cannotBeBase])
@@ -900,6 +940,8 @@ class URL {
}
get search() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
const { query } = this[context];
if (query === null || query === '')
return '';
@@ -907,6 +949,8 @@ class URL {
}
set search(search) {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
const ctx = this[context];
search = toUSVString(search);
if (search === '') {
@@ -926,10 +970,14 @@ class URL {
// readonly
get searchParams() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
return this[searchParams];
}
get hash() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
const { fragment } = this[context];
if (fragment === null || fragment === '')
return '';
@@ -937,6 +985,8 @@ class URL {
}
set hash(hash) {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
const ctx = this[context];
// toUSVString is not needed.
hash = `${hash}`;
@@ -953,6 +1003,8 @@ class URL {
}
toJSON() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
return this[kFormat]({});
}
@@ -0,0 +1,45 @@
'use strict';
require('../common');
const { URL } = require('url');
const assert = require('assert');
[
'toString',
'toJSON',
].forEach((i) => {
assert.throws(() => Reflect.apply(URL.prototype[i], [], {}), {
code: 'ERR_INVALID_THIS',
});
});
[
'href',
'protocol',
'username',
'password',
'host',
'hostname',
'port',
'pathname',
'search',
'hash',
].forEach((i) => {
assert.throws(() => Reflect.get(URL.prototype, i, {}), {
code: 'ERR_INVALID_THIS',
});
assert.throws(() => Reflect.set(URL.prototype, i, null, {}), {
code: 'ERR_INVALID_THIS',
});
});
[
'origin',
'searchParams',
].forEach((i) => {
assert.throws(() => Reflect.get(URL.prototype, i, {}), {
code: 'ERR_INVALID_THIS',
});
});