Skip to content

Commit

Permalink
url: use ada::url_aggregator for parsing urls
Browse files Browse the repository at this point in the history
  • Loading branch information
anonrig committed Mar 31, 2023
1 parent 7eb29a6 commit 4ba4ef0
Show file tree
Hide file tree
Showing 9 changed files with 442 additions and 257 deletions.
232 changes: 173 additions & 59 deletions lib/internal/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const {
ReflectGetOwnPropertyDescriptor,
ReflectOwnKeys,
RegExpPrototypeSymbolReplace,
SafeSet,
StringPrototypeCharAt,
StringPrototypeCharCodeAt,
StringPrototypeCodePointAt,
Expand Down Expand Up @@ -84,13 +85,7 @@ const querystring = require('querystring');
const { platform } = process;
const isWindows = platform === 'win32';

const {
domainToASCII: _domainToASCII,
domainToUnicode: _domainToUnicode,
parse,
canParse: _canParse,
updateUrl,
} = internalBinding('url');
const bindingUrl = internalBinding('url');

const FORWARD_SLASH = /\//g;

Expand Down Expand Up @@ -134,16 +129,32 @@ function lazyCryptoRandom() {
// the C++ binding.
// Refs: https://url.spec.whatwg.org/#concept-url
class URLContext {
// This is the maximum value uint32_t can get.
// Ada uses uint32_t(-1) for declaring omitted values.
#omitted = 4294967295;
href = '';
origin = '';
protocol = '';
hostname = '';
pathname = '';
search = '';
username = '';
password = '';
port = '';
hash = '';
protocol_end = 0;
username_end = 0;
host_start = 0;
host_end = 0;
pathname_start = 0;
search_start = this.#omitted;
hash_start = this.#omitted;
port = this.#omitted;

get hasPort() {
return this.port !== this.#omitted;
}

get hasSearch() {
return this.search_start !== this.#omitted;
}

get hasHash() {
return this.hash_start !== this.#omitted;
}

static SpecialProtocols = new SafeSet(['http:', 'https:', 'ws:', 'wss:', 'ftp:', 'file:']);
}

function isURLSearchParams(self) {
Expand Down Expand Up @@ -556,13 +567,13 @@ class URL {
base = `${base}`;
}

const isValid = parse(input,
base,
this.#onParseComplete);
const href = bindingUrl.parse(input, base);

if (!isValid) {
if (!href) {
throw new ERR_INVALID_URL(input);
}

this.#updateContext(href);
}

[inspect.custom](depth, opts) {
Expand Down Expand Up @@ -592,22 +603,37 @@ class URL {
return `${constructor.name} ${inspect(obj, opts)}`;
}

#onParseComplete = (href, origin, protocol, hostname, pathname,
search, username, password, port, hash) => {
#updateContext(href) {
this.#context.href = href;
this.#context.origin = origin;
this.#context.protocol = protocol;
this.#context.hostname = hostname;
this.#context.pathname = pathname;
this.#context.search = search;
this.#context.username = username;
this.#context.password = password;

const {
0: protocol_end,
1: username_end,
2: host_start,
3: host_end,
4: port,
5: pathname_start,
6: search_start,
7: hash_start,
} = bindingUrl.urlComponents;

this.#context.protocol_end = protocol_end;
this.#context.username_end = username_end;
this.#context.host_start = host_start;
this.#context.host_end = host_end;
this.#context.port = port;
this.#context.hash = hash;
this.#context.pathname_start = pathname_start;
this.#context.search_start = search_start;
this.#context.hash_start = hash_start;

if (this.#searchParams) {
this.#searchParams[searchParams] = parseParams(search);
if (this.#context.hasSearch) {
this.#searchParams[searchParams] = parseParams(this.search);
} else {
this.#searchParams[searchParams] = [];
}
}
};
}

toString() {
return this.#context.href;
Expand All @@ -618,97 +644,183 @@ class URL {
}

set href(value) {
const valid = updateUrl(this.#context.href, updateActions.kHref, `${value}`, this.#onParseComplete);
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
get origin() {
return this.#context.origin;
const protocol = StringPrototypeSlice(this.#context.href, 0, this.#context.protocol_end);

if (URLContext.SpecialProtocols.has(protocol)) {
if (protocol === 'file:') {
return 'null';
}
return `${protocol}//${this.host}`;
}

if (protocol === 'blob:') {
const path = this.pathname;
if (path.length > 0) {
try {
const out = new URL(path);
const blobProtocol = out.protocol;
if (URLContext.SpecialProtocols.has(blobProtocol)) {
return `${blobProtocol}//${out.host}`;
}
} catch {
// Do nothing.
}
}
}

return 'null';
}

get protocol() {
return this.#context.protocol;
return StringPrototypeSlice(this.#context.href, 0, this.#context.protocol_end);
}

set protocol(value) {
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() {
return this.#context.username;
if (this.#context.protocol_end + 2 < this.#context.username_end) {
return StringPrototypeSlice(this.#context.href, this.#context.protocol_end + 2, this.#context.username_end);
}
return '';
}

set username(value) {
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() {
return this.#context.password;
if (this.#context.host_start - this.#context.username_end > 0) {
return StringPrototypeSlice(this.#context.href, this.#context.username_end + 1, this.#context.host_start);
}
return '';
}

set password(value) {
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() {
const port = this.#context.port;
const suffix = port.length > 0 ? `:${port}` : '';
return this.#context.hostname + suffix;
let startsAt = this.#context.host_start;
if (this.#context.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 === this.#context.host_end) {
return '';
}
return StringPrototypeSlice(this.#context.href, startsAt, this.#context.pathname_start);
}

set host(value) {
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() {
return this.#context.hostname;
let startsAt = this.#context.host_start;
// host_start might be "@" if the URL has credentials
if (this.#context.href[startsAt] === '@') {
startsAt++;
}
return StringPrototypeSlice(this.#context.href, startsAt, this.#context.host_end);
}

set hostname(value) {
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() {
return this.#context.port;
if (this.#context.hasPort) {
return `${this.#context.port}`;
}
return '';
}

set port(value) {
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() {
return this.#context.pathname;
let endsAt;
if (this.#context.hasSearch) {
endsAt = this.#context.search_start;
} else if (this.#context.hasHash) {
endsAt = this.#context.hash_start;
}
return StringPrototypeSlice(this.#context.href, this.#context.pathname_start, endsAt);
}

set pathname(value) {
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() {
return this.#context.search;
if (!this.#context.hasSearch) { return ''; }
let endsAt = this.#context.href.length;
if (this.#context.hasHash) { endsAt = this.#context.hash_start; }
if (endsAt - this.#context.search_start <= 1) { return ''; }
return StringPrototypeSlice(this.#context.href, this.#context.search_start, endsAt);
}

set search(value) {
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
get searchParams() {
// Create URLSearchParams on demand to greatly improve the URL performance.
if (this.#searchParams == null) {
this.#searchParams = new URLSearchParams(this.#context.search);
this.#searchParams = new URLSearchParams(this.search);
this.#searchParams[context] = this;
}
return this.#searchParams;
}

get hash() {
return this.#context.hash;
if (!this.#context.hasHash || (this.#context.href.length - this.#context.hash_start <= 1)) {
return '';
}
return StringPrototypeSlice(this.#context.href, this.#context.hash_start);
}

set hash(value) {
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() {
Expand All @@ -722,7 +834,7 @@ class URL {
base = `${base}`;
}

return _canParse(url, base);
return bindingUrl.canParse(url, base);
}
}

Expand Down Expand Up @@ -1107,15 +1219,15 @@ function domainToASCII(domain) {
throw new ERR_MISSING_ARGS('domain');

// toUSVString is not needed.
return _domainToASCII(`${domain}`);
return bindingUrl.domainToASCII(`${domain}`);
}

function domainToUnicode(domain) {
if (arguments.length < 1)
throw new ERR_MISSING_ARGS('domain');

// toUSVString is not needed.
return _domainToUnicode(`${domain}`);
return bindingUrl.domainToUnicode(`${domain}`);
}

/**
Expand Down Expand Up @@ -1299,4 +1411,6 @@ module.exports = {
urlToHttpOptions,
encodeStr,
isURL,

urlUpdateActions: updateActions,
};
6 changes: 2 additions & 4 deletions lib/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ const {
urlToHttpOptions,
} = require('internal/url');

const {
formatUrl,
} = internalBinding('url');
const bindingUrl = internalBinding('url');

const { getOptionValue } = require('internal/options');

Expand Down Expand Up @@ -635,7 +633,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);
Expand Down
Loading

0 comments on commit 4ba4ef0

Please sign in to comment.