mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 13:48:44 +02:00
url: use ada::url_aggregator for parsing urls
PR-URL: https://github.com/nodejs/node/pull/47339 Backport-PR-URL: https://github.com/nodejs/node/pull/48345 Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
This commit is contained in:
parent
96c50ba71f
commit
b395b16c40
9 changed files with 486 additions and 265 deletions
|
@ -87,13 +87,7 @@ const querystring = require('querystring');
|
||||||
const { platform } = process;
|
const { platform } = process;
|
||||||
const isWindows = platform === 'win32';
|
const isWindows = platform === 'win32';
|
||||||
|
|
||||||
const {
|
const bindingUrl = internalBinding('url');
|
||||||
domainToASCII: _domainToASCII,
|
|
||||||
domainToUnicode: _domainToUnicode,
|
|
||||||
parse,
|
|
||||||
canParse: _canParse,
|
|
||||||
updateUrl,
|
|
||||||
} = internalBinding('url');
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
storeDataObject,
|
storeDataObject,
|
||||||
|
@ -142,16 +136,46 @@ function lazyCryptoRandom() {
|
||||||
// the C++ binding.
|
// the C++ binding.
|
||||||
// Refs: https://url.spec.whatwg.org/#concept-url
|
// Refs: https://url.spec.whatwg.org/#concept-url
|
||||||
class URLContext {
|
class URLContext {
|
||||||
|
// This is the maximum value uint32_t can get.
|
||||||
|
// Ada uses uint32_t(-1) for declaring omitted values.
|
||||||
|
static #omitted = 4294967295;
|
||||||
|
|
||||||
href = '';
|
href = '';
|
||||||
origin = '';
|
protocol_end = 0;
|
||||||
protocol = '';
|
username_end = 0;
|
||||||
hostname = '';
|
host_start = 0;
|
||||||
pathname = '';
|
host_end = 0;
|
||||||
search = '';
|
pathname_start = 0;
|
||||||
username = '';
|
search_start = 0;
|
||||||
password = '';
|
hash_start = 0;
|
||||||
port = '';
|
port = 0;
|
||||||
hash = '';
|
/**
|
||||||
|
* Refers to `ada::scheme::type`
|
||||||
|
*
|
||||||
|
* enum type : uint8_t {
|
||||||
|
* HTTP = 0,
|
||||||
|
* NOT_SPECIAL = 1,
|
||||||
|
* HTTPS = 2,
|
||||||
|
* WS = 3,
|
||||||
|
* FTP = 4,
|
||||||
|
* WSS = 5,
|
||||||
|
* FILE = 6
|
||||||
|
* };
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
scheme_type = 1;
|
||||||
|
|
||||||
|
get hasPort() {
|
||||||
|
return this.port !== URLContext.#omitted;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasSearch() {
|
||||||
|
return this.search_start !== URLContext.#omitted;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasHash() {
|
||||||
|
return this.hash_start !== URLContext.#omitted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isURLSearchParams(self) {
|
function isURLSearchParams(self) {
|
||||||
|
@ -581,13 +605,13 @@ class URL {
|
||||||
base = `${base}`;
|
base = `${base}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isValid = parse(input,
|
const href = bindingUrl.parse(input, base);
|
||||||
base,
|
|
||||||
this.#onParseComplete);
|
|
||||||
|
|
||||||
if (!isValid) {
|
if (!href) {
|
||||||
throw new ERR_INVALID_URL(input);
|
throw new ERR_INVALID_URL(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.#updateContext(href);
|
||||||
}
|
}
|
||||||
|
|
||||||
[inspect.custom](depth, opts) {
|
[inspect.custom](depth, opts) {
|
||||||
|
@ -622,23 +646,40 @@ class URL {
|
||||||
return `${constructor.name} ${inspect(obj, opts)}`;
|
return `${constructor.name} ${inspect(obj, opts)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
#onParseComplete = (href, origin, protocol, hostname, pathname,
|
#updateContext(href) {
|
||||||
search, username, password, port, hash) => {
|
|
||||||
const ctx = this[context];
|
const ctx = this[context];
|
||||||
ctx.href = href;
|
ctx.href = href;
|
||||||
ctx.origin = origin;
|
|
||||||
ctx.protocol = protocol;
|
const {
|
||||||
ctx.hostname = hostname;
|
0: protocol_end,
|
||||||
ctx.pathname = pathname;
|
1: username_end,
|
||||||
ctx.search = search;
|
2: host_start,
|
||||||
ctx.username = username;
|
3: host_end,
|
||||||
ctx.password = password;
|
4: port,
|
||||||
|
5: pathname_start,
|
||||||
|
6: search_start,
|
||||||
|
7: hash_start,
|
||||||
|
8: scheme_type,
|
||||||
|
} = bindingUrl.urlComponents;
|
||||||
|
|
||||||
|
ctx.protocol_end = protocol_end;
|
||||||
|
ctx.username_end = username_end;
|
||||||
|
ctx.host_start = host_start;
|
||||||
|
ctx.host_end = host_end;
|
||||||
ctx.port = port;
|
ctx.port = port;
|
||||||
ctx.hash = hash;
|
ctx.pathname_start = pathname_start;
|
||||||
|
ctx.search_start = search_start;
|
||||||
|
ctx.hash_start = hash_start;
|
||||||
|
ctx.scheme_type = scheme_type;
|
||||||
|
|
||||||
if (this[searchParams]) {
|
if (this[searchParams]) {
|
||||||
this[searchParams][searchParams] = parseParams(search);
|
if (ctx.hasSearch) {
|
||||||
|
this[searchParams][searchParams] = parseParams(this.search);
|
||||||
|
} else {
|
||||||
|
this[searchParams][searchParams] = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
|
@ -655,122 +696,210 @@ class URL {
|
||||||
set href(value) {
|
set href(value) {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
const valid = updateUrl(this[context].href, updateActions.kHref, `${value}`, this.#onParseComplete);
|
value = `${value}`;
|
||||||
if (!valid) { throw ERR_INVALID_URL(`${value}`); }
|
const href = bindingUrl.update(this[context].href, updateActions.kHref, value);
|
||||||
|
if (!href) { throw ERR_INVALID_URL(value); }
|
||||||
|
this.#updateContext(href);
|
||||||
}
|
}
|
||||||
|
|
||||||
// readonly
|
// readonly
|
||||||
get origin() {
|
get origin() {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
return this[context].origin;
|
const ctx = this[context];
|
||||||
|
const protocol = StringPrototypeSlice(ctx.href, 0, ctx.protocol_end);
|
||||||
|
|
||||||
|
// Check if scheme_type is not `NOT_SPECIAL`
|
||||||
|
if (ctx.scheme_type !== 1) {
|
||||||
|
// Check if scheme_type is `FILE`
|
||||||
|
if (ctx.scheme_type === 6) {
|
||||||
|
return 'null';
|
||||||
|
}
|
||||||
|
return `${protocol}//${this.host}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protocol === 'blob:') {
|
||||||
|
const path = this.pathname;
|
||||||
|
if (path.length > 0) {
|
||||||
|
try {
|
||||||
|
const out = new URL(path);
|
||||||
|
if (out[context].scheme_type !== 1) {
|
||||||
|
return `${out.protocol}//${out.host}`;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'null';
|
||||||
}
|
}
|
||||||
|
|
||||||
get protocol() {
|
get protocol() {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
return this[context].protocol;
|
return StringPrototypeSlice(this[context].href, 0, this[context].protocol_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
set protocol(value) {
|
set protocol(value) {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
updateUrl(this[context].href, updateActions.kProtocol, `${value}`, this.#onParseComplete);
|
const href = bindingUrl.update(this[context].href, updateActions.kProtocol, `${value}`);
|
||||||
|
if (href) {
|
||||||
|
this.#updateContext(href);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get username() {
|
get username() {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
return this[context].username;
|
const ctx = this[context];
|
||||||
|
if (ctx.protocol_end + 2 < ctx.username_end) {
|
||||||
|
return StringPrototypeSlice(ctx.href, ctx.protocol_end + 2, ctx.username_end);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
set username(value) {
|
set username(value) {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
updateUrl(this[context].href, updateActions.kUsername, `${value}`, this.#onParseComplete);
|
const href = bindingUrl.update(this[context].href, updateActions.kUsername, `${value}`);
|
||||||
|
if (href) {
|
||||||
|
this.#updateContext(href);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get password() {
|
get password() {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
return this[context].password;
|
const ctx = this[context];
|
||||||
|
if (ctx.host_start - ctx.username_end > 0) {
|
||||||
|
return StringPrototypeSlice(ctx.href, ctx.username_end + 1, ctx.host_start);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
set password(value) {
|
set password(value) {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
updateUrl(this[context].href, updateActions.kPassword, `${value}`, this.#onParseComplete);
|
const href = bindingUrl.update(this[context].href, updateActions.kPassword, `${value}`);
|
||||||
|
if (href) {
|
||||||
|
this.#updateContext(href);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get host() {
|
get host() {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
const port = this[context].port;
|
const ctx = this[context];
|
||||||
const suffix = port.length > 0 ? `:${port}` : '';
|
let startsAt = ctx.host_start;
|
||||||
return this[context].hostname + suffix;
|
if (ctx.href[startsAt] === '@') {
|
||||||
|
startsAt++;
|
||||||
|
}
|
||||||
|
// If we have an empty host, then the space between components.host_end and
|
||||||
|
// components.pathname_start may be occupied by /.
|
||||||
|
if (startsAt === ctx.host_end) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return StringPrototypeSlice(ctx.href, startsAt, ctx.pathname_start);
|
||||||
}
|
}
|
||||||
|
|
||||||
set host(value) {
|
set host(value) {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
updateUrl(this[context].href, updateActions.kHost, `${value}`, this.#onParseComplete);
|
const href = bindingUrl.update(this[context].href, updateActions.kHost, `${value}`);
|
||||||
|
if (href) {
|
||||||
|
this.#updateContext(href);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get hostname() {
|
get hostname() {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
return this[context].hostname;
|
const ctx = this[context];
|
||||||
|
let startsAt = ctx.host_start;
|
||||||
|
// host_start might be "@" if the URL has credentials
|
||||||
|
if (ctx.href[startsAt] === '@') {
|
||||||
|
startsAt++;
|
||||||
|
}
|
||||||
|
return StringPrototypeSlice(ctx.href, startsAt, ctx.host_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
set hostname(value) {
|
set hostname(value) {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
updateUrl(this[context].href, updateActions.kHostname, `${value}`, this.#onParseComplete);
|
const href = bindingUrl.update(this[context].href, updateActions.kHostname, `${value}`);
|
||||||
|
if (href) {
|
||||||
|
this.#updateContext(href);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get port() {
|
get port() {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
return this[context].port;
|
if (this[context].hasPort) {
|
||||||
|
return `${this[context].port}`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
set port(value) {
|
set port(value) {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
updateUrl(this[context].href, updateActions.kPort, `${value}`, this.#onParseComplete);
|
const href = bindingUrl.update(this[context].href, updateActions.kPort, `${value}`);
|
||||||
|
if (href) {
|
||||||
|
this.#updateContext(href);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get pathname() {
|
get pathname() {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
return this[context].pathname;
|
const ctx = this[context];
|
||||||
|
let endsAt;
|
||||||
|
if (ctx.hasSearch) {
|
||||||
|
endsAt = ctx.search_start;
|
||||||
|
} else if (ctx.hasHash) {
|
||||||
|
endsAt = ctx.hash_start;
|
||||||
|
}
|
||||||
|
return StringPrototypeSlice(ctx.href, ctx.pathname_start, endsAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
set pathname(value) {
|
set pathname(value) {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
updateUrl(this[context].href, updateActions.kPathname, `${value}`, this.#onParseComplete);
|
const href = bindingUrl.update(this[context].href, updateActions.kPathname, `${value}`);
|
||||||
|
if (href) {
|
||||||
|
this.#updateContext(href);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get search() {
|
get search() {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
return this[context].search;
|
const ctx = this[context];
|
||||||
|
if (!ctx.hasSearch) { return ''; }
|
||||||
|
let endsAt = ctx.href.length;
|
||||||
|
if (ctx.hasHash) { endsAt = ctx.hash_start; }
|
||||||
|
if (endsAt - ctx.search_start <= 1) { return ''; }
|
||||||
|
return StringPrototypeSlice(ctx.href, ctx.search_start, endsAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
set search(value) {
|
set search(value) {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
updateUrl(this[context].href, updateActions.kSearch, toUSVString(value), this.#onParseComplete);
|
const href = bindingUrl.update(this[context].href, updateActions.kSearch, toUSVString(value));
|
||||||
|
if (href) {
|
||||||
|
this.#updateContext(href);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// readonly
|
// readonly
|
||||||
get searchParams() {
|
get searchParams() {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
// 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.search);
|
||||||
this[searchParams][context] = this;
|
this[searchParams][context] = this;
|
||||||
}
|
}
|
||||||
return this[searchParams];
|
return this[searchParams];
|
||||||
|
@ -779,13 +908,20 @@ class URL {
|
||||||
get hash() {
|
get hash() {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
return this[context].hash;
|
const ctx = this[context];
|
||||||
|
if (!ctx.hasHash || (ctx.href.length - ctx.hash_start <= 1)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return StringPrototypeSlice(ctx.href, ctx.hash_start);
|
||||||
}
|
}
|
||||||
|
|
||||||
set hash(value) {
|
set hash(value) {
|
||||||
if (!isURL(this))
|
if (!isURL(this))
|
||||||
throw new ERR_INVALID_THIS('URL');
|
throw new ERR_INVALID_THIS('URL');
|
||||||
updateUrl(this[context].href, updateActions.kHash, `${value}`, this.#onParseComplete);
|
const href = bindingUrl.update(this[context].href, updateActions.kHash, `${value}`);
|
||||||
|
if (href) {
|
||||||
|
this.#updateContext(href);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
|
@ -801,7 +937,7 @@ class URL {
|
||||||
base = `${base}`;
|
base = `${base}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _canParse(url, base);
|
return bindingUrl.canParse(url, base);
|
||||||
}
|
}
|
||||||
|
|
||||||
static createObjectURL(obj) {
|
static createObjectURL(obj) {
|
||||||
|
@ -1164,7 +1300,7 @@ function domainToASCII(domain) {
|
||||||
throw new ERR_MISSING_ARGS('domain');
|
throw new ERR_MISSING_ARGS('domain');
|
||||||
|
|
||||||
// toUSVString is not needed.
|
// toUSVString is not needed.
|
||||||
return _domainToASCII(`${domain}`);
|
return bindingUrl.domainToASCII(`${domain}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function domainToUnicode(domain) {
|
function domainToUnicode(domain) {
|
||||||
|
@ -1172,7 +1308,7 @@ function domainToUnicode(domain) {
|
||||||
throw new ERR_MISSING_ARGS('domain');
|
throw new ERR_MISSING_ARGS('domain');
|
||||||
|
|
||||||
// toUSVString is not needed.
|
// toUSVString is not needed.
|
||||||
return _domainToUnicode(`${domain}`);
|
return bindingUrl.domainToUnicode(`${domain}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1355,4 +1491,6 @@ module.exports = {
|
||||||
urlToHttpOptions,
|
urlToHttpOptions,
|
||||||
encodeStr,
|
encodeStr,
|
||||||
isURL,
|
isURL,
|
||||||
|
|
||||||
|
urlUpdateActions: updateActions,
|
||||||
};
|
};
|
||||||
|
|
|
@ -59,9 +59,7 @@ const {
|
||||||
urlToHttpOptions,
|
urlToHttpOptions,
|
||||||
} = require('internal/url');
|
} = require('internal/url');
|
||||||
|
|
||||||
const {
|
const bindingUrl = internalBinding('url');
|
||||||
formatUrl,
|
|
||||||
} = internalBinding('url');
|
|
||||||
|
|
||||||
const { getOptionValue } = require('internal/options');
|
const { getOptionValue } = require('internal/options');
|
||||||
|
|
||||||
|
@ -627,7 +625,7 @@ function urlFormat(urlObject, options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return formatUrl(urlObject.href, fragment, unicode, search, auth);
|
return bindingUrl.format(urlObject.href, fragment, unicode, search, auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Url.prototype.format.call(urlObject);
|
return Url.prototype.format.call(urlObject);
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include "node_metadata.h"
|
#include "node_metadata.h"
|
||||||
#include "node_process.h"
|
#include "node_process.h"
|
||||||
#include "node_snapshot_builder.h"
|
#include "node_snapshot_builder.h"
|
||||||
|
#include "node_url.h"
|
||||||
#include "node_util.h"
|
#include "node_util.h"
|
||||||
#include "node_v8.h"
|
#include "node_v8.h"
|
||||||
#include "node_v8_platform-inl.h"
|
#include "node_v8_platform-inl.h"
|
||||||
|
|
|
@ -27,6 +27,7 @@ struct PropInfo {
|
||||||
V(v8_binding_data, v8_utils::BindingData) \
|
V(v8_binding_data, v8_utils::BindingData) \
|
||||||
V(blob_binding_data, BlobBindingData) \
|
V(blob_binding_data, BlobBindingData) \
|
||||||
V(process_binding_data, process::BindingData) \
|
V(process_binding_data, process::BindingData) \
|
||||||
|
V(url_binding_data, url::BindingData) \
|
||||||
V(util_weak_reference, util::WeakReference)
|
V(util_weak_reference, util::WeakReference)
|
||||||
|
|
||||||
enum class EmbedderObjectType : uint8_t {
|
enum class EmbedderObjectType : uint8_t {
|
||||||
|
|
395
src/node_url.cc
395
src/node_url.cc
|
@ -5,14 +5,16 @@
|
||||||
#include "node_external_reference.h"
|
#include "node_external_reference.h"
|
||||||
#include "node_i18n.h"
|
#include "node_i18n.h"
|
||||||
#include "util-inl.h"
|
#include "util-inl.h"
|
||||||
|
#include "v8.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
|
namespace url {
|
||||||
|
|
||||||
using v8::Context;
|
using v8::Context;
|
||||||
using v8::Function;
|
|
||||||
using v8::FunctionCallbackInfo;
|
using v8::FunctionCallbackInfo;
|
||||||
using v8::HandleScope;
|
using v8::HandleScope;
|
||||||
using v8::Isolate;
|
using v8::Isolate;
|
||||||
|
@ -22,102 +24,49 @@ using v8::Object;
|
||||||
using v8::String;
|
using v8::String;
|
||||||
using v8::Value;
|
using v8::Value;
|
||||||
|
|
||||||
namespace url {
|
void BindingData::MemoryInfo(MemoryTracker* tracker) const {
|
||||||
namespace {
|
tracker->TrackField("url_components_buffer", url_components_buffer_);
|
||||||
|
|
||||||
enum url_update_action {
|
|
||||||
kProtocol = 0,
|
|
||||||
kHost = 1,
|
|
||||||
kHostname = 2,
|
|
||||||
kPort = 3,
|
|
||||||
kUsername = 4,
|
|
||||||
kPassword = 5,
|
|
||||||
kPathname = 6,
|
|
||||||
kSearch = 7,
|
|
||||||
kHash = 8,
|
|
||||||
kHref = 9,
|
|
||||||
};
|
|
||||||
|
|
||||||
auto GetCallbackArgs(Environment* env, const ada::result& url) {
|
|
||||||
Local<Context> context = env->context();
|
|
||||||
Isolate* isolate = env->isolate();
|
|
||||||
|
|
||||||
auto js_string = [&](std::string_view sv) {
|
|
||||||
return ToV8Value(context, sv, isolate).ToLocalChecked();
|
|
||||||
};
|
|
||||||
return std::array{
|
|
||||||
js_string(url->get_href()),
|
|
||||||
js_string(url->get_origin()),
|
|
||||||
js_string(url->get_protocol()),
|
|
||||||
js_string(url->get_hostname()),
|
|
||||||
js_string(url->get_pathname()),
|
|
||||||
js_string(url->get_search()),
|
|
||||||
js_string(url->get_username()),
|
|
||||||
js_string(url->get_password()),
|
|
||||||
js_string(url->get_port()),
|
|
||||||
js_string(url->get_hash()),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parse(const FunctionCallbackInfo<Value>& args) {
|
BindingData::BindingData(Realm* realm, v8::Local<v8::Object> object)
|
||||||
CHECK_GE(args.Length(), 3);
|
: SnapshotableObject(realm, object, type_int),
|
||||||
CHECK(args[0]->IsString()); // input
|
url_components_buffer_(realm->isolate(), kURLComponentsLength) {
|
||||||
// args[1] // base url
|
object
|
||||||
CHECK(args[2]->IsFunction()); // complete callback
|
->Set(realm->context(),
|
||||||
|
FIXED_ONE_BYTE_STRING(realm->isolate(), "urlComponents"),
|
||||||
Local<Function> success_callback_ = args[2].As<Function>();
|
url_components_buffer_.GetJSArray())
|
||||||
|
.Check();
|
||||||
Environment* env = Environment::GetCurrent(args);
|
|
||||||
HandleScope handle_scope(env->isolate());
|
|
||||||
Context::Scope context_scope(env->context());
|
|
||||||
|
|
||||||
Utf8Value input(env->isolate(), args[0]);
|
|
||||||
ada::result base;
|
|
||||||
ada::url* base_pointer = nullptr;
|
|
||||||
if (args[1]->IsString()) {
|
|
||||||
base = ada::parse(Utf8Value(env->isolate(), args[1]).ToString());
|
|
||||||
if (!base) {
|
|
||||||
return args.GetReturnValue().Set(false);
|
|
||||||
}
|
|
||||||
base_pointer = &base.value();
|
|
||||||
}
|
|
||||||
ada::result out = ada::parse(input.ToStringView(), base_pointer);
|
|
||||||
|
|
||||||
if (!out) {
|
|
||||||
return args.GetReturnValue().Set(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto argv = GetCallbackArgs(env, out);
|
|
||||||
USE(success_callback_->Call(
|
|
||||||
env->context(), args.This(), argv.size(), argv.data()));
|
|
||||||
args.GetReturnValue().Set(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CanParse(const FunctionCallbackInfo<Value>& args) {
|
bool BindingData::PrepareForSerialization(v8::Local<v8::Context> context,
|
||||||
CHECK_GE(args.Length(), 2);
|
v8::SnapshotCreator* creator) {
|
||||||
CHECK(args[0]->IsString()); // input
|
// We'll just re-initialize the buffers in the constructor since their
|
||||||
// args[1] // base url
|
// contents can be thrown away once consumed in the previous call.
|
||||||
|
url_components_buffer_.Release();
|
||||||
Environment* env = Environment::GetCurrent(args);
|
// Return true because we need to maintain the reference to the binding from
|
||||||
HandleScope handle_scope(env->isolate());
|
// JS land.
|
||||||
Context::Scope context_scope(env->context());
|
return true;
|
||||||
|
|
||||||
Utf8Value input(env->isolate(), args[0]);
|
|
||||||
ada::result base;
|
|
||||||
ada::url* base_pointer = nullptr;
|
|
||||||
if (args[1]->IsString()) {
|
|
||||||
base = ada::parse(Utf8Value(env->isolate(), args[1]).ToString());
|
|
||||||
if (!base) {
|
|
||||||
return args.GetReturnValue().Set(false);
|
|
||||||
}
|
|
||||||
base_pointer = &base.value();
|
|
||||||
}
|
|
||||||
ada::result out = ada::parse(input.ToStringView(), base_pointer);
|
|
||||||
|
|
||||||
args.GetReturnValue().Set(out.has_value());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainToASCII(const FunctionCallbackInfo<Value>& args) {
|
InternalFieldInfoBase* BindingData::Serialize(int index) {
|
||||||
|
DCHECK_EQ(index, BaseObject::kEmbedderType);
|
||||||
|
InternalFieldInfo* info =
|
||||||
|
InternalFieldInfoBase::New<InternalFieldInfo>(type());
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingData::Deserialize(v8::Local<v8::Context> context,
|
||||||
|
v8::Local<v8::Object> holder,
|
||||||
|
int index,
|
||||||
|
InternalFieldInfoBase* info) {
|
||||||
|
DCHECK_EQ(index, BaseObject::kEmbedderType);
|
||||||
|
v8::HandleScope scope(context->GetIsolate());
|
||||||
|
Realm* realm = Realm::GetCurrent(context);
|
||||||
|
BindingData* binding = realm->AddBindingData<BindingData>(context, holder);
|
||||||
|
CHECK_NOT_NULL(binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingData::DomainToASCII(const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
CHECK_GE(args.Length(), 1);
|
CHECK_GE(args.Length(), 1);
|
||||||
CHECK(args[0]->IsString());
|
CHECK(args[0]->IsString());
|
||||||
|
@ -127,11 +76,10 @@ void DomainToASCII(const FunctionCallbackInfo<Value>& args) {
|
||||||
return args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(env->isolate(), ""));
|
return args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(env->isolate(), ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(NODE_HAVE_I18N_SUPPORT)
|
|
||||||
// It is important to have an initial value that contains a special scheme.
|
// It is important to have an initial value that contains a special scheme.
|
||||||
// Since it will change the implementation of `set_hostname` according to URL
|
// Since it will change the implementation of `set_hostname` according to URL
|
||||||
// spec.
|
// spec.
|
||||||
ada::result out = ada::parse("ws://x");
|
auto out = ada::parse<ada::url>("ws://x");
|
||||||
DCHECK(out);
|
DCHECK(out);
|
||||||
if (!out->set_hostname(input)) {
|
if (!out->set_hostname(input)) {
|
||||||
return args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(env->isolate(), ""));
|
return args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(env->isolate(), ""));
|
||||||
|
@ -139,53 +87,144 @@ void DomainToASCII(const FunctionCallbackInfo<Value>& args) {
|
||||||
std::string host = out->get_hostname();
|
std::string host = out->get_hostname();
|
||||||
args.GetReturnValue().Set(
|
args.GetReturnValue().Set(
|
||||||
String::NewFromUtf8(env->isolate(), host.c_str()).ToLocalChecked());
|
String::NewFromUtf8(env->isolate(), host.c_str()).ToLocalChecked());
|
||||||
#else
|
|
||||||
args.GetReturnValue().Set(
|
|
||||||
String::NewFromUtf8(env->isolate(), input.c_str()).ToLocalChecked());
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainToUnicode(const FunctionCallbackInfo<Value>& args) {
|
void BindingData::DomainToUnicode(const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
CHECK_GE(args.Length(), 1);
|
CHECK_GE(args.Length(), 1);
|
||||||
CHECK(args[0]->IsString());
|
CHECK(args[0]->IsString());
|
||||||
|
|
||||||
std::string input = Utf8Value(env->isolate(), args[0]).ToString();
|
std::string input = Utf8Value(env->isolate(), args[0]).ToString();
|
||||||
#if defined(NODE_HAVE_I18N_SUPPORT)
|
|
||||||
// It is important to have an initial value that contains a special scheme.
|
// It is important to have an initial value that contains a special scheme.
|
||||||
// Since it will change the implementation of `set_hostname` according to URL
|
// Since it will change the implementation of `set_hostname` according to URL
|
||||||
// spec.
|
// spec.
|
||||||
ada::result out = ada::parse("ws://x");
|
auto out = ada::parse<ada::url>("ws://x");
|
||||||
DCHECK(out);
|
DCHECK(out);
|
||||||
if (!out->set_hostname(input)) {
|
if (!out->set_hostname(input)) {
|
||||||
return args.GetReturnValue().Set(
|
return args.GetReturnValue().Set(
|
||||||
String::NewFromUtf8(env->isolate(), "").ToLocalChecked());
|
String::NewFromUtf8(env->isolate(), "").ToLocalChecked());
|
||||||
}
|
}
|
||||||
std::string host = out->get_hostname();
|
std::string result = ada::unicode::to_unicode(out->get_hostname());
|
||||||
|
|
||||||
MaybeStackBuffer<char> buf;
|
args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(),
|
||||||
int32_t len = i18n::ToUnicode(&buf, host.data(), host.length());
|
result.c_str(),
|
||||||
|
NewStringType::kNormal,
|
||||||
if (len < 0) {
|
result.length())
|
||||||
return args.GetReturnValue().Set(
|
.ToLocalChecked());
|
||||||
String::NewFromUtf8(env->isolate(), "").ToLocalChecked());
|
|
||||||
}
|
|
||||||
|
|
||||||
args.GetReturnValue().Set(
|
|
||||||
String::NewFromUtf8(env->isolate(), *buf, NewStringType::kNormal, len)
|
|
||||||
.ToLocalChecked());
|
|
||||||
#else // !defined(NODE_HAVE_I18N_SUPPORT)
|
|
||||||
args.GetReturnValue().Set(
|
|
||||||
String::NewFromUtf8(env->isolate(), input.c_str()).ToLocalChecked());
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateUrl(const FunctionCallbackInfo<Value>& args) {
|
// TODO(@anonrig): Add V8 Fast API for CanParse method
|
||||||
|
void BindingData::CanParse(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
CHECK_GE(args.Length(), 2);
|
||||||
|
CHECK(args[0]->IsString()); // input
|
||||||
|
// args[1] // base url
|
||||||
|
|
||||||
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
HandleScope handle_scope(env->isolate());
|
||||||
|
Context::Scope context_scope(env->context());
|
||||||
|
|
||||||
|
Utf8Value input(env->isolate(), args[0]);
|
||||||
|
ada::result<ada::url_aggregator> base;
|
||||||
|
ada::url_aggregator* base_pointer = nullptr;
|
||||||
|
if (args[1]->IsString()) {
|
||||||
|
base = ada::parse<ada::url_aggregator>(
|
||||||
|
Utf8Value(env->isolate(), args[1]).ToString());
|
||||||
|
if (!base) {
|
||||||
|
return args.GetReturnValue().Set(false);
|
||||||
|
}
|
||||||
|
base_pointer = &base.value();
|
||||||
|
}
|
||||||
|
auto out =
|
||||||
|
ada::parse<ada::url_aggregator>(input.ToStringView(), base_pointer);
|
||||||
|
|
||||||
|
args.GetReturnValue().Set(out.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingData::Format(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
CHECK_GT(args.Length(), 4);
|
||||||
|
CHECK(args[0]->IsString()); // url href
|
||||||
|
|
||||||
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
Isolate* isolate = env->isolate();
|
||||||
|
|
||||||
|
Utf8Value href(isolate, args[0].As<String>());
|
||||||
|
const bool fragment = args[1]->IsTrue();
|
||||||
|
const bool unicode = args[2]->IsTrue();
|
||||||
|
const bool search = args[3]->IsTrue();
|
||||||
|
const bool auth = args[4]->IsTrue();
|
||||||
|
|
||||||
|
// ada::url provides a faster alternative to ada::url_aggregator if we
|
||||||
|
// directly want to manipulate the url components without using the respective
|
||||||
|
// setters. therefore we are using ada::url here.
|
||||||
|
auto out = ada::parse<ada::url>(href.ToStringView());
|
||||||
|
CHECK(out);
|
||||||
|
|
||||||
|
if (!fragment) {
|
||||||
|
out->fragment = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unicode) {
|
||||||
|
out->host = ada::idna::to_unicode(out->get_hostname());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!search) {
|
||||||
|
out->query = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!auth) {
|
||||||
|
out->username = "";
|
||||||
|
out->password = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string result = out->get_href();
|
||||||
|
args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(),
|
||||||
|
result.data(),
|
||||||
|
NewStringType::kNormal,
|
||||||
|
result.length())
|
||||||
|
.ToLocalChecked());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingData::Parse(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
CHECK_GE(args.Length(), 1);
|
||||||
|
CHECK(args[0]->IsString()); // input
|
||||||
|
// args[1] // base url
|
||||||
|
|
||||||
|
BindingData* binding_data = Realm::GetBindingData<BindingData>(args);
|
||||||
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
HandleScope handle_scope(env->isolate());
|
||||||
|
Context::Scope context_scope(env->context());
|
||||||
|
|
||||||
|
Utf8Value input(env->isolate(), args[0]);
|
||||||
|
ada::result<ada::url_aggregator> base;
|
||||||
|
ada::url_aggregator* base_pointer = nullptr;
|
||||||
|
if (args[1]->IsString()) {
|
||||||
|
base = ada::parse<ada::url_aggregator>(
|
||||||
|
Utf8Value(env->isolate(), args[1]).ToString());
|
||||||
|
if (!base) {
|
||||||
|
return args.GetReturnValue().Set(false);
|
||||||
|
}
|
||||||
|
base_pointer = &base.value();
|
||||||
|
}
|
||||||
|
auto out =
|
||||||
|
ada::parse<ada::url_aggregator>(input.ToStringView(), base_pointer);
|
||||||
|
|
||||||
|
if (!out) {
|
||||||
|
return args.GetReturnValue().Set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
binding_data->UpdateComponents(out->get_components(), out->type);
|
||||||
|
|
||||||
|
args.GetReturnValue().Set(
|
||||||
|
ToV8Value(env->context(), out->get_href(), env->isolate())
|
||||||
|
.ToLocalChecked());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingData::Update(const FunctionCallbackInfo<Value>& args) {
|
||||||
CHECK(args[0]->IsString()); // href
|
CHECK(args[0]->IsString()); // href
|
||||||
CHECK(args[1]->IsNumber()); // action type
|
CHECK(args[1]->IsNumber()); // action type
|
||||||
CHECK(args[2]->IsString()); // new value
|
CHECK(args[2]->IsString()); // new value
|
||||||
CHECK(args[3]->IsFunction()); // success callback
|
|
||||||
|
|
||||||
|
BindingData* binding_data = Realm::GetBindingData<BindingData>(args);
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
Isolate* isolate = env->isolate();
|
Isolate* isolate = env->isolate();
|
||||||
|
|
||||||
|
@ -193,10 +232,9 @@ void UpdateUrl(const FunctionCallbackInfo<Value>& args) {
|
||||||
args[1]->Uint32Value(env->context()).FromJust());
|
args[1]->Uint32Value(env->context()).FromJust());
|
||||||
Utf8Value input(isolate, args[0].As<String>());
|
Utf8Value input(isolate, args[0].As<String>());
|
||||||
Utf8Value new_value(isolate, args[2].As<String>());
|
Utf8Value new_value(isolate, args[2].As<String>());
|
||||||
Local<Function> success_callback_ = args[3].As<Function>();
|
|
||||||
|
|
||||||
std::string_view new_value_view = new_value.ToStringView();
|
std::string_view new_value_view = new_value.ToStringView();
|
||||||
ada::result out = ada::parse(input.ToStringView());
|
auto out = ada::parse<ada::url_aggregator>(input.ToStringView());
|
||||||
CHECK(out);
|
CHECK(out);
|
||||||
|
|
||||||
bool result{true};
|
bool result{true};
|
||||||
|
@ -242,89 +280,60 @@ void UpdateUrl(const FunctionCallbackInfo<Value>& args) {
|
||||||
result = out->set_username(new_value_view);
|
result = out->set_username(new_value_view);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE("Unsupported URL update action");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto argv = GetCallbackArgs(env, out);
|
if (!result) {
|
||||||
USE(success_callback_->Call(
|
return args.GetReturnValue().Set(false);
|
||||||
env->context(), args.This(), argv.size(), argv.data()));
|
}
|
||||||
args.GetReturnValue().Set(result);
|
|
||||||
|
binding_data->UpdateComponents(out->get_components(), out->type);
|
||||||
|
args.GetReturnValue().Set(
|
||||||
|
ToV8Value(env->context(), out->get_href(), env->isolate())
|
||||||
|
.ToLocalChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
void FormatUrl(const FunctionCallbackInfo<Value>& args) {
|
void BindingData::UpdateComponents(const ada::url_components& components,
|
||||||
CHECK_GT(args.Length(), 4);
|
const ada::scheme::type type) {
|
||||||
CHECK(args[0]->IsString()); // url href
|
url_components_buffer_[0] = components.protocol_end;
|
||||||
|
url_components_buffer_[1] = components.username_end;
|
||||||
Environment* env = Environment::GetCurrent(args);
|
url_components_buffer_[2] = components.host_start;
|
||||||
Isolate* isolate = env->isolate();
|
url_components_buffer_[3] = components.host_end;
|
||||||
|
url_components_buffer_[4] = components.port;
|
||||||
Utf8Value href(isolate, args[0].As<String>());
|
url_components_buffer_[5] = components.pathname_start;
|
||||||
const bool fragment = args[1]->IsTrue();
|
url_components_buffer_[6] = components.search_start;
|
||||||
const bool unicode = args[2]->IsTrue();
|
url_components_buffer_[7] = components.hash_start;
|
||||||
const bool search = args[3]->IsTrue();
|
url_components_buffer_[8] = type;
|
||||||
const bool auth = args[4]->IsTrue();
|
static_assert(kURLComponentsLength == 9,
|
||||||
|
"kURLComponentsLength should be up-to-date");
|
||||||
ada::result out = ada::parse(href.ToStringView());
|
|
||||||
CHECK(out);
|
|
||||||
|
|
||||||
if (!fragment) {
|
|
||||||
out->fragment = std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unicode) {
|
|
||||||
#if defined(NODE_HAVE_I18N_SUPPORT)
|
|
||||||
std::string hostname = out->get_hostname();
|
|
||||||
MaybeStackBuffer<char> buf;
|
|
||||||
int32_t len = i18n::ToUnicode(&buf, hostname.data(), hostname.length());
|
|
||||||
|
|
||||||
if (len < 0) {
|
|
||||||
out->host = "";
|
|
||||||
} else {
|
|
||||||
out->host = buf.ToString();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
out->host = "";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!search) {
|
|
||||||
out->query = std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!auth) {
|
|
||||||
out->username = "";
|
|
||||||
out->password = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string result = out->get_href();
|
|
||||||
args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(),
|
|
||||||
result.data(),
|
|
||||||
NewStringType::kNormal,
|
|
||||||
result.length())
|
|
||||||
.ToLocalChecked());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Initialize(Local<Object> target,
|
void BindingData::Initialize(Local<Object> target,
|
||||||
Local<Value> unused,
|
Local<Value> unused,
|
||||||
Local<Context> context,
|
Local<Context> context,
|
||||||
void* priv) {
|
void* priv) {
|
||||||
SetMethod(context, target, "parse", Parse);
|
Realm* realm = Realm::GetCurrent(context);
|
||||||
SetMethod(context, target, "updateUrl", UpdateUrl);
|
BindingData* const binding_data =
|
||||||
SetMethodNoSideEffect(context, target, "canParse", CanParse);
|
realm->AddBindingData<BindingData>(context, target);
|
||||||
SetMethodNoSideEffect(context, target, "formatUrl", FormatUrl);
|
if (binding_data == nullptr) return;
|
||||||
|
|
||||||
SetMethodNoSideEffect(context, target, "domainToASCII", DomainToASCII);
|
SetMethodNoSideEffect(context, target, "domainToASCII", DomainToASCII);
|
||||||
SetMethodNoSideEffect(context, target, "domainToUnicode", DomainToUnicode);
|
SetMethodNoSideEffect(context, target, "domainToUnicode", DomainToUnicode);
|
||||||
|
SetMethodNoSideEffect(context, target, "canParse", CanParse);
|
||||||
|
SetMethodNoSideEffect(context, target, "format", Format);
|
||||||
|
SetMethod(context, target, "parse", Parse);
|
||||||
|
SetMethod(context, target, "update", Update);
|
||||||
}
|
}
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
|
||||||
registry->Register(Parse);
|
|
||||||
registry->Register(CanParse);
|
|
||||||
registry->Register(UpdateUrl);
|
|
||||||
registry->Register(FormatUrl);
|
|
||||||
|
|
||||||
|
void BindingData::RegisterExternalReferences(
|
||||||
|
ExternalReferenceRegistry* registry) {
|
||||||
registry->Register(DomainToASCII);
|
registry->Register(DomainToASCII);
|
||||||
registry->Register(DomainToUnicode);
|
registry->Register(DomainToUnicode);
|
||||||
|
registry->Register(CanParse);
|
||||||
|
registry->Register(Format);
|
||||||
|
registry->Register(Parse);
|
||||||
|
registry->Register(Update);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string FromFilePath(const std::string_view file_path) {
|
std::string FromFilePath(const std::string_view file_path) {
|
||||||
|
@ -338,7 +347,9 @@ std::string FromFilePath(const std::string_view file_path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace url
|
} // namespace url
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
|
||||||
NODE_BINDING_CONTEXT_AWARE_INTERNAL(url, node::url::Initialize)
|
NODE_BINDING_CONTEXT_AWARE_INTERNAL(url, node::url::BindingData::Initialize)
|
||||||
NODE_BINDING_EXTERNAL_REFERENCE(url, node::url::RegisterExternalReferences)
|
NODE_BINDING_EXTERNAL_REFERENCE(
|
||||||
|
url, node::url::BindingData::RegisterExternalReferences)
|
||||||
|
|
|
@ -3,18 +3,74 @@
|
||||||
|
|
||||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
#include "ada.h"
|
#include "ada.h"
|
||||||
|
#include "aliased_buffer.h"
|
||||||
#include "node.h"
|
#include "node.h"
|
||||||
|
#include "node_snapshotable.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
|
class ExternalReferenceRegistry;
|
||||||
|
|
||||||
namespace url {
|
namespace url {
|
||||||
|
|
||||||
|
enum url_update_action {
|
||||||
|
kProtocol = 0,
|
||||||
|
kHost = 1,
|
||||||
|
kHostname = 2,
|
||||||
|
kPort = 3,
|
||||||
|
kUsername = 4,
|
||||||
|
kPassword = 5,
|
||||||
|
kPathname = 6,
|
||||||
|
kSearch = 7,
|
||||||
|
kHash = 8,
|
||||||
|
kHref = 9,
|
||||||
|
};
|
||||||
|
|
||||||
|
class BindingData : public SnapshotableObject {
|
||||||
|
public:
|
||||||
|
explicit BindingData(Realm* realm, v8::Local<v8::Object> obj);
|
||||||
|
|
||||||
|
using InternalFieldInfo = InternalFieldInfoBase;
|
||||||
|
|
||||||
|
SERIALIZABLE_OBJECT_METHODS()
|
||||||
|
static constexpr FastStringKey type_name{"node::url::BindingData"};
|
||||||
|
static constexpr EmbedderObjectType type_int =
|
||||||
|
EmbedderObjectType::k_url_binding_data;
|
||||||
|
|
||||||
|
void MemoryInfo(MemoryTracker* tracker) const override;
|
||||||
|
SET_SELF_SIZE(BindingData)
|
||||||
|
SET_MEMORY_INFO_NAME(BindingData)
|
||||||
|
|
||||||
|
static void DomainToASCII(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void DomainToUnicode(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
|
||||||
|
static void CanParse(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void Format(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void Parse(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void Update(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
|
||||||
|
static void Initialize(v8::Local<v8::Object> target,
|
||||||
|
v8::Local<v8::Value> unused,
|
||||||
|
v8::Local<v8::Context> context,
|
||||||
|
void* priv);
|
||||||
|
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr size_t kURLComponentsLength = 9;
|
||||||
|
AliasedUint32Array url_components_buffer_;
|
||||||
|
|
||||||
|
void UpdateComponents(const ada::url_components& components,
|
||||||
|
const ada::scheme::type type);
|
||||||
|
};
|
||||||
|
|
||||||
std::string FromFilePath(const std::string_view file_path);
|
std::string FromFilePath(const std::string_view file_path);
|
||||||
|
|
||||||
} // namespace url
|
} // namespace url
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
|
||||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
|
|
@ -47,15 +47,18 @@ assert.strictEqual(
|
||||||
hash: '#hash',
|
hash: '#hash',
|
||||||
[Symbol(context)]: URLContext {
|
[Symbol(context)]: URLContext {
|
||||||
href: 'https://username:password@host.name:8080/path/name/?que=ry#hash',
|
href: 'https://username:password@host.name:8080/path/name/?que=ry#hash',
|
||||||
origin: 'https://host.name:8080',
|
protocol_end: 6,
|
||||||
protocol: 'https:',
|
username_end: 16,
|
||||||
hostname: 'host.name',
|
host_start: 25,
|
||||||
pathname: '/path/name/',
|
host_end: 35,
|
||||||
search: '?que=ry',
|
pathname_start: 40,
|
||||||
username: 'username',
|
search_start: 51,
|
||||||
password: 'password',
|
hash_start: 58,
|
||||||
port: '8080',
|
port: 8080,
|
||||||
hash: '#hash'
|
scheme_type: 2,
|
||||||
|
[hasPort]: [Getter],
|
||||||
|
[hasSearch]: [Getter],
|
||||||
|
[hasHash]: [Getter]
|
||||||
}
|
}
|
||||||
}`);
|
}`);
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
"./typings/internalBinding/symbols.d.ts",
|
"./typings/internalBinding/symbols.d.ts",
|
||||||
"./typings/internalBinding/timers.d.ts",
|
"./typings/internalBinding/timers.d.ts",
|
||||||
"./typings/internalBinding/types.d.ts",
|
"./typings/internalBinding/types.d.ts",
|
||||||
|
"./typings/internalBinding/url.d.ts",
|
||||||
"./typings/internalBinding/util.d.ts",
|
"./typings/internalBinding/util.d.ts",
|
||||||
"./typings/internalBinding/worker.d.ts",
|
"./typings/internalBinding/worker.d.ts",
|
||||||
"./typings/globals.d.ts",
|
"./typings/globals.d.ts",
|
||||||
|
|
12
typings/internalBinding/url.d.ts
vendored
Normal file
12
typings/internalBinding/url.d.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import type { urlUpdateActions } from 'internal/url'
|
||||||
|
|
||||||
|
declare function InternalBinding(binding: 'url'): {
|
||||||
|
urlComponents: Uint32Array;
|
||||||
|
|
||||||
|
domainToASCII(input: string): string;
|
||||||
|
domainToUnicode(input: string): string;
|
||||||
|
canParse(input: string, base?: string): boolean;
|
||||||
|
format(input: string, fragment?: boolean, unicode?: boolean, search?: boolean, auth?: boolean): string;
|
||||||
|
parse(input: string, base?: string): string | false;
|
||||||
|
update(input: string, actionType: typeof urlUpdateActions, value: string): string | false;
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue