diff --git a/lib/internal/url.js b/lib/internal/url.js index 378248b9486b52..9cb14654381e48 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -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]({}); } diff --git a/test/parallel/test-whatwg-url-invalidthis.js b/test/parallel/test-whatwg-url-invalidthis.js new file mode 100644 index 00000000000000..790c28e37c13ed --- /dev/null +++ b/test/parallel/test-whatwg-url-invalidthis.js @@ -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', + }); +});