mirror of https://github.com/nodejs/node.git
url: use private properties for brand check
PR-URL: https://github.com/nodejs/node/pull/46904 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
This commit is contained in:
parent
5a7491b5ac
commit
027841c964
|
@ -79,7 +79,7 @@ const { BuiltinModule } = require('internal/bootstrap/loaders');
|
||||||
const {
|
const {
|
||||||
maybeCacheSourceMap,
|
maybeCacheSourceMap,
|
||||||
} = require('internal/source_map/source_map_cache');
|
} = require('internal/source_map/source_map_cache');
|
||||||
const { pathToFileURL, fileURLToPath, isURLInstance } = require('internal/url');
|
const { pathToFileURL, fileURLToPath, isURL } = require('internal/url');
|
||||||
const {
|
const {
|
||||||
deprecate,
|
deprecate,
|
||||||
emitExperimentalWarning,
|
emitExperimentalWarning,
|
||||||
|
@ -1396,7 +1396,7 @@ const createRequireError = 'must be a file URL object, file URL string, or ' +
|
||||||
function createRequire(filename) {
|
function createRequire(filename) {
|
||||||
let filepath;
|
let filepath;
|
||||||
|
|
||||||
if (isURLInstance(filename) ||
|
if (isURL(filename) ||
|
||||||
(typeof filename === 'string' && !path.isAbsolute(filename))) {
|
(typeof filename === 'string' && !path.isAbsolute(filename))) {
|
||||||
try {
|
try {
|
||||||
filepath = fileURLToPath(filename);
|
filepath = fileURLToPath(filename);
|
||||||
|
|
|
@ -21,7 +21,7 @@ const {
|
||||||
ERR_INVALID_RETURN_PROPERTY_VALUE,
|
ERR_INVALID_RETURN_PROPERTY_VALUE,
|
||||||
ERR_INVALID_RETURN_VALUE,
|
ERR_INVALID_RETURN_VALUE,
|
||||||
} = require('internal/errors').codes;
|
} = require('internal/errors').codes;
|
||||||
const { isURLInstance, URL } = require('internal/url');
|
const { isURL, URL } = require('internal/url');
|
||||||
const {
|
const {
|
||||||
isAnyArrayBuffer,
|
isAnyArrayBuffer,
|
||||||
isArrayBufferView,
|
isArrayBufferView,
|
||||||
|
@ -263,7 +263,7 @@ class Hooks {
|
||||||
if (
|
if (
|
||||||
!isMain &&
|
!isMain &&
|
||||||
typeof parentURL !== 'string' &&
|
typeof parentURL !== 'string' &&
|
||||||
!isURLInstance(parentURL)
|
!isURL(parentURL)
|
||||||
) {
|
) {
|
||||||
throw new ERR_INVALID_ARG_TYPE(
|
throw new ERR_INVALID_ARG_TYPE(
|
||||||
'parentURL',
|
'parentURL',
|
||||||
|
|
|
@ -7,6 +7,7 @@ const {
|
||||||
ArrayPrototypePush,
|
ArrayPrototypePush,
|
||||||
ArrayPrototypeReduce,
|
ArrayPrototypeReduce,
|
||||||
ArrayPrototypeSlice,
|
ArrayPrototypeSlice,
|
||||||
|
Boolean,
|
||||||
Int8Array,
|
Int8Array,
|
||||||
IteratorPrototype,
|
IteratorPrototype,
|
||||||
Number,
|
Number,
|
||||||
|
@ -15,7 +16,6 @@ const {
|
||||||
ObjectGetOwnPropertySymbols,
|
ObjectGetOwnPropertySymbols,
|
||||||
ObjectGetPrototypeOf,
|
ObjectGetPrototypeOf,
|
||||||
ObjectKeys,
|
ObjectKeys,
|
||||||
ObjectPrototypeHasOwnProperty,
|
|
||||||
ReflectGetOwnPropertyDescriptor,
|
ReflectGetOwnPropertyDescriptor,
|
||||||
ReflectOwnKeys,
|
ReflectOwnKeys,
|
||||||
RegExpPrototypeSymbolReplace,
|
RegExpPrototypeSymbolReplace,
|
||||||
|
@ -534,15 +534,27 @@ ObjectDefineProperties(URLSearchParams.prototype, {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a value has the shape of a WHATWG URL object.
|
||||||
|
*
|
||||||
|
* Using a symbol or instanceof would not be able to recognize URL objects
|
||||||
|
* coming from other implementations (e.g. in Electron), so instead we are
|
||||||
|
* checking some well known properties for a lack of a better test.
|
||||||
|
*
|
||||||
|
* @param {*} self
|
||||||
|
* @returns {self is URL}
|
||||||
|
*/
|
||||||
function isURL(self) {
|
function isURL(self) {
|
||||||
return self != null && ObjectPrototypeHasOwnProperty(self, context);
|
return Boolean(self?.href && self.origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
class URL {
|
class URL {
|
||||||
|
#context = new URLContext();
|
||||||
|
#searchParams;
|
||||||
|
|
||||||
constructor(input, base = undefined) {
|
constructor(input, base = undefined) {
|
||||||
// toUSVString is not needed.
|
// toUSVString is not needed.
|
||||||
input = `${input}`;
|
input = `${input}`;
|
||||||
this[context] = new URLContext();
|
|
||||||
|
|
||||||
if (base !== undefined) {
|
if (base !== undefined) {
|
||||||
base = `${base}`;
|
base = `${base}`;
|
||||||
|
@ -558,11 +570,6 @@ class URL {
|
||||||
}
|
}
|
||||||
|
|
||||||
[inspect.custom](depth, opts) {
|
[inspect.custom](depth, opts) {
|
||||||
if (this == null ||
|
|
||||||
ObjectGetPrototypeOf(this[context]) !== URLContext.prototype) {
|
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof depth === 'number' && depth < 0)
|
if (typeof depth === 'number' && depth < 0)
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
|
@ -583,7 +590,7 @@ class URL {
|
||||||
obj.hash = this.hash;
|
obj.hash = this.hash;
|
||||||
|
|
||||||
if (opts.showHidden) {
|
if (opts.showHidden) {
|
||||||
obj[context] = this[context];
|
obj[context] = this.#context;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${constructor.name} ${inspect(obj, opts)}`;
|
return `${constructor.name} ${inspect(obj, opts)}`;
|
||||||
|
@ -591,174 +598,125 @@ class URL {
|
||||||
|
|
||||||
#onParseComplete = (href, origin, protocol, hostname, pathname,
|
#onParseComplete = (href, origin, protocol, hostname, pathname,
|
||||||
search, username, password, port, hash) => {
|
search, username, password, port, hash) => {
|
||||||
const ctx = this[context];
|
this.#context.href = href;
|
||||||
ctx.href = href;
|
this.#context.origin = origin;
|
||||||
ctx.origin = origin;
|
this.#context.protocol = protocol;
|
||||||
ctx.protocol = protocol;
|
this.#context.hostname = hostname;
|
||||||
ctx.hostname = hostname;
|
this.#context.pathname = pathname;
|
||||||
ctx.pathname = pathname;
|
this.#context.search = search;
|
||||||
ctx.search = search;
|
this.#context.username = username;
|
||||||
ctx.username = username;
|
this.#context.password = password;
|
||||||
ctx.password = password;
|
this.#context.port = port;
|
||||||
ctx.port = port;
|
this.#context.hash = hash;
|
||||||
ctx.hash = hash;
|
if (this.#searchParams) {
|
||||||
if (this[searchParams]) {
|
this.#searchParams[searchParams] = parseParams(search);
|
||||||
this[searchParams][searchParams] = parseParams(search);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
if (!isURL(this))
|
return this.#context.href;
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
return this[context].href;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get href() {
|
get href() {
|
||||||
if (!isURL(this))
|
return this.#context.href;
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
return this[context].href;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set href(value) {
|
set href(value) {
|
||||||
if (!isURL(this))
|
const valid = updateUrl(this.#context.href, updateActions.kHref, `${value}`, this.#onParseComplete);
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
const valid = updateUrl(this[context].href, updateActions.kHref, `${value}`, this.#onParseComplete);
|
|
||||||
if (!valid) { throw ERR_INVALID_URL(`${value}`); }
|
if (!valid) { throw ERR_INVALID_URL(`${value}`); }
|
||||||
}
|
}
|
||||||
|
|
||||||
// readonly
|
// readonly
|
||||||
get origin() {
|
get origin() {
|
||||||
if (!isURL(this))
|
return this.#context.origin;
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
return this[context].origin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get protocol() {
|
get protocol() {
|
||||||
if (!isURL(this))
|
return this.#context.protocol;
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
return this[context].protocol;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set protocol(value) {
|
set protocol(value) {
|
||||||
if (!isURL(this))
|
updateUrl(this.#context.href, updateActions.kProtocol, `${value}`, this.#onParseComplete);
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
updateUrl(this[context].href, updateActions.kProtocol, `${value}`, this.#onParseComplete);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get username() {
|
get username() {
|
||||||
if (!isURL(this))
|
return this.#context.username;
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
return this[context].username;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set username(value) {
|
set username(value) {
|
||||||
if (!isURL(this))
|
updateUrl(this.#context.href, updateActions.kUsername, `${value}`, this.#onParseComplete);
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
updateUrl(this[context].href, updateActions.kUsername, `${value}`, this.#onParseComplete);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get password() {
|
get password() {
|
||||||
if (!isURL(this))
|
return this.#context.password;
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
return this[context].password;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set password(value) {
|
set password(value) {
|
||||||
if (!isURL(this))
|
updateUrl(this.#context.href, updateActions.kPassword, `${value}`, this.#onParseComplete);
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
updateUrl(this[context].href, updateActions.kPassword, `${value}`, this.#onParseComplete);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get host() {
|
get host() {
|
||||||
if (!isURL(this))
|
const port = this.#context.port;
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
const port = this[context].port;
|
|
||||||
const suffix = port.length > 0 ? `:${port}` : '';
|
const suffix = port.length > 0 ? `:${port}` : '';
|
||||||
return this[context].hostname + suffix;
|
return this.#context.hostname + suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
set host(value) {
|
set host(value) {
|
||||||
if (!isURL(this))
|
updateUrl(this.#context.href, updateActions.kHost, `${value}`, this.#onParseComplete);
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
updateUrl(this[context].href, updateActions.kHost, `${value}`, this.#onParseComplete);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get hostname() {
|
get hostname() {
|
||||||
if (!isURL(this))
|
return this.#context.hostname;
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
return this[context].hostname;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set hostname(value) {
|
set hostname(value) {
|
||||||
if (!isURL(this))
|
updateUrl(this.#context.href, updateActions.kHostname, `${value}`, this.#onParseComplete);
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
updateUrl(this[context].href, updateActions.kHostname, `${value}`, this.#onParseComplete);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get port() {
|
get port() {
|
||||||
if (!isURL(this))
|
return this.#context.port;
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
return this[context].port;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set port(value) {
|
set port(value) {
|
||||||
if (!isURL(this))
|
updateUrl(this.#context.href, updateActions.kPort, `${value}`, this.#onParseComplete);
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
updateUrl(this[context].href, updateActions.kPort, `${value}`, this.#onParseComplete);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get pathname() {
|
get pathname() {
|
||||||
if (!isURL(this))
|
return this.#context.pathname;
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
return this[context].pathname;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set pathname(value) {
|
set pathname(value) {
|
||||||
if (!isURL(this))
|
updateUrl(this.#context.href, updateActions.kPathname, `${value}`, this.#onParseComplete);
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
updateUrl(this[context].href, updateActions.kPathname, `${value}`, this.#onParseComplete);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get search() {
|
get search() {
|
||||||
if (!isURL(this))
|
return this.#context.search;
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
return this[context].search;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set search(value) {
|
set search(value) {
|
||||||
if (!isURL(this))
|
updateUrl(this.#context.href, updateActions.kSearch, toUSVString(value), this.#onParseComplete);
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
updateUrl(this[context].href, updateActions.kSearch, toUSVString(value), this.#onParseComplete);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// readonly
|
// readonly
|
||||||
get searchParams() {
|
get searchParams() {
|
||||||
if (!isURL(this))
|
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
// Create URLSearchParams on demand to greatly improve the URL performance.
|
// Create URLSearchParams on demand to greatly improve the URL performance.
|
||||||
if (this[searchParams] == null) {
|
if (this.#searchParams == null) {
|
||||||
this[searchParams] = new URLSearchParams(this[context].search);
|
this.#searchParams = new URLSearchParams(this.#context.search);
|
||||||
this[searchParams][context] = this;
|
this.#searchParams[context] = this;
|
||||||
}
|
}
|
||||||
return this[searchParams];
|
return this.#searchParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
get hash() {
|
get hash() {
|
||||||
if (!isURL(this))
|
return this.#context.hash;
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
return this[context].hash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set hash(value) {
|
set hash(value) {
|
||||||
if (!isURL(this))
|
updateUrl(this.#context.href, updateActions.kHash, `${value}`, this.#onParseComplete);
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
updateUrl(this[context].href, updateActions.kHash, `${value}`, this.#onParseComplete);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
if (!isURL(this))
|
return this.#context.href;
|
||||||
throw new ERR_INVALID_THIS('URL');
|
|
||||||
return this[context].href;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static createObjectURL(obj) {
|
static createObjectURL(obj) {
|
||||||
|
@ -1206,7 +1164,7 @@ function getPathFromURLPosix(url) {
|
||||||
function fileURLToPath(path) {
|
function fileURLToPath(path) {
|
||||||
if (typeof path === 'string')
|
if (typeof path === 'string')
|
||||||
path = new URL(path);
|
path = new URL(path);
|
||||||
else if (!isURLInstance(path))
|
else if (!isURL(path))
|
||||||
throw new ERR_INVALID_ARG_TYPE('path', ['string', 'URL'], path);
|
throw new ERR_INVALID_ARG_TYPE('path', ['string', 'URL'], path);
|
||||||
if (path.protocol !== 'file:')
|
if (path.protocol !== 'file:')
|
||||||
throw new ERR_INVALID_URL_SCHEME('file');
|
throw new ERR_INVALID_URL_SCHEME('file');
|
||||||
|
@ -1282,12 +1240,8 @@ function pathToFileURL(filepath) {
|
||||||
return outURL;
|
return outURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isURLInstance(fileURLOrPath) {
|
|
||||||
return fileURLOrPath != null && fileURLOrPath.href && fileURLOrPath.origin;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toPathIfFileURL(fileURLOrPath) {
|
function toPathIfFileURL(fileURLOrPath) {
|
||||||
if (!isURLInstance(fileURLOrPath))
|
if (!isURL(fileURLOrPath))
|
||||||
return fileURLOrPath;
|
return fileURLOrPath;
|
||||||
return fileURLToPath(fileURLOrPath);
|
return fileURLToPath(fileURLOrPath);
|
||||||
}
|
}
|
||||||
|
@ -1297,7 +1251,6 @@ module.exports = {
|
||||||
fileURLToPath,
|
fileURLToPath,
|
||||||
pathToFileURL,
|
pathToFileURL,
|
||||||
toPathIfFileURL,
|
toPathIfFileURL,
|
||||||
isURLInstance,
|
|
||||||
URL,
|
URL,
|
||||||
URLSearchParams,
|
URLSearchParams,
|
||||||
domainToASCII,
|
domainToASCII,
|
||||||
|
|
|
@ -55,7 +55,7 @@ const {
|
||||||
WritableWorkerStdio,
|
WritableWorkerStdio,
|
||||||
} = workerIo;
|
} = workerIo;
|
||||||
const { deserializeError } = require('internal/error_serdes');
|
const { deserializeError } = require('internal/error_serdes');
|
||||||
const { fileURLToPath, isURLInstance, pathToFileURL } = require('internal/url');
|
const { fileURLToPath, isURL, pathToFileURL } = require('internal/url');
|
||||||
const { kEmptyObject } = require('internal/util');
|
const { kEmptyObject } = require('internal/util');
|
||||||
const { validateArray } = require('internal/validators');
|
const { validateArray } = require('internal/validators');
|
||||||
|
|
||||||
|
@ -148,13 +148,13 @@ class Worker extends EventEmitter {
|
||||||
}
|
}
|
||||||
url = null;
|
url = null;
|
||||||
doEval = 'classic';
|
doEval = 'classic';
|
||||||
} else if (isURLInstance(filename) && filename.protocol === 'data:') {
|
} else if (isURL(filename) && filename.protocol === 'data:') {
|
||||||
url = null;
|
url = null;
|
||||||
doEval = 'module';
|
doEval = 'module';
|
||||||
filename = `import ${JSONStringify(`${filename}`)}`;
|
filename = `import ${JSONStringify(`${filename}`)}`;
|
||||||
} else {
|
} else {
|
||||||
doEval = false;
|
doEval = false;
|
||||||
if (isURLInstance(filename)) {
|
if (isURL(filename)) {
|
||||||
url = filename;
|
url = filename;
|
||||||
filename = fileURLToPath(filename);
|
filename = fileURLToPath(filename);
|
||||||
} else if (typeof filename !== 'string') {
|
} else if (typeof filename !== 'string') {
|
||||||
|
|
|
@ -61,7 +61,7 @@ assert.strictEqual(
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
util.inspect({ a: url }, { depth: 0 }),
|
util.inspect({ a: url }, { depth: 0 }),
|
||||||
'{ a: [URL] }');
|
'{ a: URL {} }');
|
||||||
|
|
||||||
class MyURL extends URL {}
|
class MyURL extends URL {}
|
||||||
assert(util.inspect(new MyURL(url.href)).startsWith('MyURL {'));
|
assert(util.inspect(new MyURL(url.href)).startsWith('MyURL {'));
|
||||||
|
|
|
@ -10,7 +10,8 @@ const assert = require('assert');
|
||||||
'toJSON',
|
'toJSON',
|
||||||
].forEach((i) => {
|
].forEach((i) => {
|
||||||
assert.throws(() => Reflect.apply(URL.prototype[i], [], {}), {
|
assert.throws(() => Reflect.apply(URL.prototype[i], [], {}), {
|
||||||
code: 'ERR_INVALID_THIS',
|
name: 'TypeError',
|
||||||
|
message: /Cannot read private member/,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -27,11 +28,13 @@ const assert = require('assert');
|
||||||
'hash',
|
'hash',
|
||||||
].forEach((i) => {
|
].forEach((i) => {
|
||||||
assert.throws(() => Reflect.get(URL.prototype, i, {}), {
|
assert.throws(() => Reflect.get(URL.prototype, i, {}), {
|
||||||
code: 'ERR_INVALID_THIS',
|
name: 'TypeError',
|
||||||
|
message: /Cannot read private member/,
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.throws(() => Reflect.set(URL.prototype, i, null, {}), {
|
assert.throws(() => Reflect.set(URL.prototype, i, null, {}), {
|
||||||
code: 'ERR_INVALID_THIS',
|
name: 'TypeError',
|
||||||
|
message: /Cannot read private member/,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -40,6 +43,7 @@ const assert = require('assert');
|
||||||
'searchParams',
|
'searchParams',
|
||||||
].forEach((i) => {
|
].forEach((i) => {
|
||||||
assert.throws(() => Reflect.get(URL.prototype, i, {}), {
|
assert.throws(() => Reflect.get(URL.prototype, i, {}), {
|
||||||
code: 'ERR_INVALID_THIS',
|
name: 'TypeError',
|
||||||
|
message: /Cannot read private member/,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue