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 isWindows = platform === 'win32';
|
||||
|
||||
const {
|
||||
domainToASCII: _domainToASCII,
|
||||
domainToUnicode: _domainToUnicode,
|
||||
parse,
|
||||
canParse: _canParse,
|
||||
updateUrl,
|
||||
} = internalBinding('url');
|
||||
const bindingUrl = internalBinding('url');
|
||||
|
||||
const {
|
||||
storeDataObject,
|
||||
|
@ -142,16 +136,46 @@ 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.
|
||||
static #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 = 0;
|
||||
hash_start = 0;
|
||||
port = 0;
|
||||
/**
|
||||
* 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) {
|
||||
|
@ -581,13 +605,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) {
|
||||
|
@ -622,23 +646,40 @@ class URL {
|
|||
return `${constructor.name} ${inspect(obj, opts)}`;
|
||||
}
|
||||
|
||||
#onParseComplete = (href, origin, protocol, hostname, pathname,
|
||||
search, username, password, port, hash) => {
|
||||
#updateContext(href) {
|
||||
const ctx = this[context];
|
||||
ctx.href = href;
|
||||
ctx.origin = origin;
|
||||
ctx.protocol = protocol;
|
||||
ctx.hostname = hostname;
|
||||
ctx.pathname = pathname;
|
||||
ctx.search = search;
|
||||
ctx.username = username;
|
||||
ctx.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,
|
||||
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.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]) {
|
||||
this[searchParams][searchParams] = parseParams(search);
|
||||
if (ctx.hasSearch) {
|
||||
this[searchParams][searchParams] = parseParams(this.search);
|
||||
} else {
|
||||
this[searchParams][searchParams] = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
toString() {
|
||||
if (!isURL(this))
|
||||
|
@ -655,122 +696,210 @@ class URL {
|
|||
set href(value) {
|
||||
if (!isURL(this))
|
||||
throw new ERR_INVALID_THIS('URL');
|
||||
const valid = updateUrl(this[context].href, updateActions.kHref, `${value}`, this.#onParseComplete);
|
||||
if (!valid) { throw ERR_INVALID_URL(`${value}`); }
|
||||
value = `${value}`;
|
||||
const href = bindingUrl.update(this[context].href, updateActions.kHref, value);
|
||||
if (!href) { throw ERR_INVALID_URL(value); }
|
||||
this.#updateContext(href);
|
||||
}
|
||||
|
||||
// readonly
|
||||
get origin() {
|
||||
if (!isURL(this))
|
||||
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() {
|
||||
if (!isURL(this))
|
||||
throw new ERR_INVALID_THIS('URL');
|
||||
return this[context].protocol;
|
||||
return StringPrototypeSlice(this[context].href, 0, this[context].protocol_end);
|
||||
}
|
||||
|
||||
set protocol(value) {
|
||||
if (!isURL(this))
|
||||
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() {
|
||||
if (!isURL(this))
|
||||
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) {
|
||||
if (!isURL(this))
|
||||
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() {
|
||||
if (!isURL(this))
|
||||
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) {
|
||||
if (!isURL(this))
|
||||
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() {
|
||||
if (!isURL(this))
|
||||
throw new ERR_INVALID_THIS('URL');
|
||||
const port = this[context].port;
|
||||
const suffix = port.length > 0 ? `:${port}` : '';
|
||||
return this[context].hostname + suffix;
|
||||
const ctx = this[context];
|
||||
let startsAt = ctx.host_start;
|
||||
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) {
|
||||
if (!isURL(this))
|
||||
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() {
|
||||
if (!isURL(this))
|
||||
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) {
|
||||
if (!isURL(this))
|
||||
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() {
|
||||
if (!isURL(this))
|
||||
throw new ERR_INVALID_THIS('URL');
|
||||
return this[context].port;
|
||||
if (this[context].hasPort) {
|
||||
return `${this[context].port}`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
set port(value) {
|
||||
if (!isURL(this))
|
||||
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() {
|
||||
if (!isURL(this))
|
||||
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) {
|
||||
if (!isURL(this))
|
||||
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() {
|
||||
if (!isURL(this))
|
||||
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) {
|
||||
if (!isURL(this))
|
||||
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
|
||||
get searchParams() {
|
||||
if (!isURL(this))
|
||||
throw new ERR_INVALID_THIS('URL');
|
||||
// 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];
|
||||
|
@ -779,13 +908,20 @@ class URL {
|
|||
get hash() {
|
||||
if (!isURL(this))
|
||||
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) {
|
||||
if (!isURL(this))
|
||||
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() {
|
||||
|
@ -801,7 +937,7 @@ class URL {
|
|||
base = `${base}`;
|
||||
}
|
||||
|
||||
return _canParse(url, base);
|
||||
return bindingUrl.canParse(url, base);
|
||||
}
|
||||
|
||||
static createObjectURL(obj) {
|
||||
|
@ -1164,7 +1300,7 @@ function domainToASCII(domain) {
|
|||
throw new ERR_MISSING_ARGS('domain');
|
||||
|
||||
// toUSVString is not needed.
|
||||
return _domainToASCII(`${domain}`);
|
||||
return bindingUrl.domainToASCII(`${domain}`);
|
||||
}
|
||||
|
||||
function domainToUnicode(domain) {
|
||||
|
@ -1172,7 +1308,7 @@ function domainToUnicode(domain) {
|
|||
throw new ERR_MISSING_ARGS('domain');
|
||||
|
||||
// toUSVString is not needed.
|
||||
return _domainToUnicode(`${domain}`);
|
||||
return bindingUrl.domainToUnicode(`${domain}`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1355,4 +1491,6 @@ module.exports = {
|
|||
urlToHttpOptions,
|
||||
encodeStr,
|
||||
isURL,
|
||||
|
||||
urlUpdateActions: updateActions,
|
||||
};
|
||||
|
|
|
@ -59,9 +59,7 @@ const {
|
|||
urlToHttpOptions,
|
||||
} = require('internal/url');
|
||||
|
||||
const {
|
||||
formatUrl,
|
||||
} = internalBinding('url');
|
||||
const bindingUrl = internalBinding('url');
|
||||
|
||||
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);
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "node_metadata.h"
|
||||
#include "node_process.h"
|
||||
#include "node_snapshot_builder.h"
|
||||
#include "node_url.h"
|
||||
#include "node_util.h"
|
||||
#include "node_v8.h"
|
||||
#include "node_v8_platform-inl.h"
|
||||
|
|
|
@ -27,6 +27,7 @@ struct PropInfo {
|
|||
V(v8_binding_data, v8_utils::BindingData) \
|
||||
V(blob_binding_data, BlobBindingData) \
|
||||
V(process_binding_data, process::BindingData) \
|
||||
V(url_binding_data, url::BindingData) \
|
||||
V(util_weak_reference, util::WeakReference)
|
||||
|
||||
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_i18n.h"
|
||||
#include "util-inl.h"
|
||||
#include "v8.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <numeric>
|
||||
|
||||
namespace node {
|
||||
namespace url {
|
||||
|
||||
using v8::Context;
|
||||
using v8::Function;
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::HandleScope;
|
||||
using v8::Isolate;
|
||||
|
@ -22,102 +24,49 @@ using v8::Object;
|
|||
using v8::String;
|
||||
using v8::Value;
|
||||
|
||||
namespace url {
|
||||
namespace {
|
||||
|
||||
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 BindingData::MemoryInfo(MemoryTracker* tracker) const {
|
||||
tracker->TrackField("url_components_buffer", url_components_buffer_);
|
||||
}
|
||||
|
||||
void Parse(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK_GE(args.Length(), 3);
|
||||
CHECK(args[0]->IsString()); // input
|
||||
// args[1] // base url
|
||||
CHECK(args[2]->IsFunction()); // complete callback
|
||||
|
||||
Local<Function> success_callback_ = args[2].As<Function>();
|
||||
|
||||
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);
|
||||
BindingData::BindingData(Realm* realm, v8::Local<v8::Object> object)
|
||||
: SnapshotableObject(realm, object, type_int),
|
||||
url_components_buffer_(realm->isolate(), kURLComponentsLength) {
|
||||
object
|
||||
->Set(realm->context(),
|
||||
FIXED_ONE_BYTE_STRING(realm->isolate(), "urlComponents"),
|
||||
url_components_buffer_.GetJSArray())
|
||||
.Check();
|
||||
}
|
||||
|
||||
void 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 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());
|
||||
bool BindingData::PrepareForSerialization(v8::Local<v8::Context> context,
|
||||
v8::SnapshotCreator* creator) {
|
||||
// We'll just re-initialize the buffers in the constructor since their
|
||||
// contents can be thrown away once consumed in the previous call.
|
||||
url_components_buffer_.Release();
|
||||
// Return true because we need to maintain the reference to the binding from
|
||||
// JS land.
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
CHECK_GE(args.Length(), 1);
|
||||
CHECK(args[0]->IsString());
|
||||
|
@ -127,11 +76,10 @@ void DomainToASCII(const FunctionCallbackInfo<Value>& args) {
|
|||
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.
|
||||
// Since it will change the implementation of `set_hostname` according to URL
|
||||
// spec.
|
||||
ada::result out = ada::parse("ws://x");
|
||||
auto out = ada::parse<ada::url>("ws://x");
|
||||
DCHECK(out);
|
||||
if (!out->set_hostname(input)) {
|
||||
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();
|
||||
args.GetReturnValue().Set(
|
||||
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);
|
||||
CHECK_GE(args.Length(), 1);
|
||||
CHECK(args[0]->IsString());
|
||||
|
||||
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.
|
||||
// Since it will change the implementation of `set_hostname` according to URL
|
||||
// spec.
|
||||
ada::result out = ada::parse("ws://x");
|
||||
auto out = ada::parse<ada::url>("ws://x");
|
||||
DCHECK(out);
|
||||
if (!out->set_hostname(input)) {
|
||||
return args.GetReturnValue().Set(
|
||||
String::NewFromUtf8(env->isolate(), "").ToLocalChecked());
|
||||
}
|
||||
std::string host = out->get_hostname();
|
||||
std::string result = ada::unicode::to_unicode(out->get_hostname());
|
||||
|
||||
MaybeStackBuffer<char> buf;
|
||||
int32_t len = i18n::ToUnicode(&buf, host.data(), host.length());
|
||||
|
||||
if (len < 0) {
|
||||
return args.GetReturnValue().Set(
|
||||
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
|
||||
args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(),
|
||||
result.c_str(),
|
||||
NewStringType::kNormal,
|
||||
result.length())
|
||||
.ToLocalChecked());
|
||||
}
|
||||
|
||||
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[1]->IsNumber()); // action type
|
||||
CHECK(args[2]->IsString()); // new value
|
||||
CHECK(args[3]->IsFunction()); // success callback
|
||||
|
||||
BindingData* binding_data = Realm::GetBindingData<BindingData>(args);
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
Isolate* isolate = env->isolate();
|
||||
|
||||
|
@ -193,10 +232,9 @@ void UpdateUrl(const FunctionCallbackInfo<Value>& args) {
|
|||
args[1]->Uint32Value(env->context()).FromJust());
|
||||
Utf8Value input(isolate, args[0].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();
|
||||
ada::result out = ada::parse(input.ToStringView());
|
||||
auto out = ada::parse<ada::url_aggregator>(input.ToStringView());
|
||||
CHECK(out);
|
||||
|
||||
bool result{true};
|
||||
|
@ -242,89 +280,60 @@ void UpdateUrl(const FunctionCallbackInfo<Value>& args) {
|
|||
result = out->set_username(new_value_view);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE("Unsupported URL update action");
|
||||
}
|
||||
|
||||
auto argv = GetCallbackArgs(env, out);
|
||||
USE(success_callback_->Call(
|
||||
env->context(), args.This(), argv.size(), argv.data()));
|
||||
args.GetReturnValue().Set(result);
|
||||
if (!result) {
|
||||
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 FormatUrl(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::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 BindingData::UpdateComponents(const ada::url_components& components,
|
||||
const ada::scheme::type type) {
|
||||
url_components_buffer_[0] = components.protocol_end;
|
||||
url_components_buffer_[1] = components.username_end;
|
||||
url_components_buffer_[2] = components.host_start;
|
||||
url_components_buffer_[3] = components.host_end;
|
||||
url_components_buffer_[4] = components.port;
|
||||
url_components_buffer_[5] = components.pathname_start;
|
||||
url_components_buffer_[6] = components.search_start;
|
||||
url_components_buffer_[7] = components.hash_start;
|
||||
url_components_buffer_[8] = type;
|
||||
static_assert(kURLComponentsLength == 9,
|
||||
"kURLComponentsLength should be up-to-date");
|
||||
}
|
||||
|
||||
void Initialize(Local<Object> target,
|
||||
Local<Value> unused,
|
||||
Local<Context> context,
|
||||
void* priv) {
|
||||
SetMethod(context, target, "parse", Parse);
|
||||
SetMethod(context, target, "updateUrl", UpdateUrl);
|
||||
SetMethodNoSideEffect(context, target, "canParse", CanParse);
|
||||
SetMethodNoSideEffect(context, target, "formatUrl", FormatUrl);
|
||||
void BindingData::Initialize(Local<Object> target,
|
||||
Local<Value> unused,
|
||||
Local<Context> context,
|
||||
void* priv) {
|
||||
Realm* realm = Realm::GetCurrent(context);
|
||||
BindingData* const binding_data =
|
||||
realm->AddBindingData<BindingData>(context, target);
|
||||
if (binding_data == nullptr) return;
|
||||
|
||||
SetMethodNoSideEffect(context, target, "domainToASCII", DomainToASCII);
|
||||
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(DomainToUnicode);
|
||||
registry->Register(CanParse);
|
||||
registry->Register(Format);
|
||||
registry->Register(Parse);
|
||||
registry->Register(Update);
|
||||
}
|
||||
|
||||
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 node
|
||||
|
||||
NODE_BINDING_CONTEXT_AWARE_INTERNAL(url, node::url::Initialize)
|
||||
NODE_BINDING_EXTERNAL_REFERENCE(url, node::url::RegisterExternalReferences)
|
||||
NODE_BINDING_CONTEXT_AWARE_INTERNAL(url, node::url::BindingData::Initialize)
|
||||
NODE_BINDING_EXTERNAL_REFERENCE(
|
||||
url, node::url::BindingData::RegisterExternalReferences)
|
||||
|
|
|
@ -3,18 +3,74 @@
|
|||
|
||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
||||
#include <cinttypes>
|
||||
#include "ada.h"
|
||||
#include "aliased_buffer.h"
|
||||
#include "node.h"
|
||||
#include "node_snapshotable.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace node {
|
||||
class ExternalReferenceRegistry;
|
||||
|
||||
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);
|
||||
|
||||
} // namespace url
|
||||
|
||||
} // namespace node
|
||||
|
||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
|
|
@ -47,15 +47,18 @@ assert.strictEqual(
|
|||
hash: '#hash',
|
||||
[Symbol(context)]: URLContext {
|
||||
href: 'https://username:password@host.name:8080/path/name/?que=ry#hash',
|
||||
origin: 'https://host.name:8080',
|
||||
protocol: 'https:',
|
||||
hostname: 'host.name',
|
||||
pathname: '/path/name/',
|
||||
search: '?que=ry',
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
port: '8080',
|
||||
hash: '#hash'
|
||||
protocol_end: 6,
|
||||
username_end: 16,
|
||||
host_start: 25,
|
||||
host_end: 35,
|
||||
pathname_start: 40,
|
||||
search_start: 51,
|
||||
hash_start: 58,
|
||||
port: 8080,
|
||||
scheme_type: 2,
|
||||
[hasPort]: [Getter],
|
||||
[hasSearch]: [Getter],
|
||||
[hasHash]: [Getter]
|
||||
}
|
||||
}`);
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"./typings/internalBinding/symbols.d.ts",
|
||||
"./typings/internalBinding/timers.d.ts",
|
||||
"./typings/internalBinding/types.d.ts",
|
||||
"./typings/internalBinding/url.d.ts",
|
||||
"./typings/internalBinding/util.d.ts",
|
||||
"./typings/internalBinding/worker.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