Skip to content

Commit

Permalink
url: throw invalid this on detached accessors
Browse files Browse the repository at this point in the history
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 <[email protected]>

PR-URL: #39752
Reviewed-By: Michaël Zasso <[email protected]>
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: Zijian Liu <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
  • Loading branch information
jasnell committed Aug 15, 2021
1 parent 75bd4da commit e1e669b
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 0 deletions.
52 changes: 52 additions & 0 deletions lib/internal/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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) {
Expand All @@ -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)
Expand All @@ -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])
Expand All @@ -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])
Expand All @@ -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)
Expand All @@ -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}`;
Expand All @@ -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}`;
Expand All @@ -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])
Expand All @@ -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];
Expand All @@ -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])
Expand All @@ -900,13 +940,17 @@ class URL {
}

get search() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
const { query } = this[context];
if (query === null || query === '')
return '';
return `?${query}`;
}

set search(search) {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
const ctx = this[context];
search = toUSVString(search);
if (search === '') {
Expand All @@ -926,17 +970,23 @@ 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 '';
return `#${fragment}`;
}

set hash(hash) {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
const ctx = this[context];
// toUSVString is not needed.
hash = `${hash}`;
Expand All @@ -953,6 +1003,8 @@ class URL {
}

toJSON() {
if (!isURLThis(this))
throw new ERR_INVALID_THIS('URL');
return this[kFormat]({});
}

Expand Down
45 changes: 45 additions & 0 deletions test/parallel/test-whatwg-url-invalidthis.js
Original file line number Diff line number Diff line change
@@ -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',
});
});

0 comments on commit e1e669b

Please sign in to comment.