GeezerVison is showing github.com — url: simplify and improve url formatting · nodejs/node@a39dd37
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/url.js +13-85Lines changed: 13 additions & 85 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -79,7 +79,6 @@ const path = require('path'); | 79 | 79 |
| 80 | 80 | const { | 81 | 81 | validateFunction, | 82 | | - validateObject, | 83 | 82 | } = require('internal/validators'); | 84 | 83 |
| 85 | 84 | const querystring = require('querystring'); | @@ -152,8 +151,6 @@ class URLContext { | 152 | 151 | password = ''; | 153 | 152 | port = ''; | 154 | 153 | hash = ''; | 155 | | - hasHost = false; | 156 | | - hasOpaquePath = false; | 157 | 154 | } | 158 | 155 |
| 159 | 156 | function isURLSearchParams(self) { | @@ -290,7 +287,9 @@ class URLSearchParams { | 290 | 287 | name = toUSVString(name); | 291 | 288 | value = toUSVString(value); | 292 | 289 | ArrayPrototypePush(this[searchParams], name, value); | 293 | | - update(this[context], this); | | 290 | + if (this[context]) { | | 291 | + this[context].search = this.toString(); | | 292 | + } | 294 | 293 | } | 295 | 294 |
| 296 | 295 | delete(name) { | @@ -311,7 +310,9 @@ class URLSearchParams { | 311 | 310 | i += 2; | 312 | 311 | } | 313 | 312 | } | 314 | | - update(this[context], this); | | 313 | + if (this[context]) { | | 314 | + this[context].search = this.toString(); | | 315 | + } | 315 | 316 | } | 316 | 317 |
| 317 | 318 | get(name) { | @@ -406,7 +407,9 @@ class URLSearchParams { | 406 | 407 | ArrayPrototypePush(list, name, value); | 407 | 408 | } | 408 | 409 |
| 409 | | - update(this[context], this); | | 410 | + if (this[context]) { | | 411 | + this[context].search = this.toString(); | | 412 | + } | 410 | 413 | } | 411 | 414 |
| 412 | 415 | sort() { | @@ -450,7 +453,9 @@ class URLSearchParams { | 450 | 453 | } | 451 | 454 | } | 452 | 455 |
| 453 | | - update(this[context], this); | | 456 | + if (this[context]) { | | 457 | + this[context].search = this.toString(); | | 458 | + } | 454 | 459 | } | 455 | 460 |
| 456 | 461 | // https://heycam.github.io/webidl/#es-iterators | @@ -536,46 +541,6 @@ function isURLThis(self) { | 536 | 541 | return self != null && ObjectPrototypeHasOwnProperty(self, context); | 537 | 542 | } | 538 | 543 |
| 539 | | -function constructHref(ctx, options) { | 540 | | - if (options) | 541 | | - validateObject(options, 'options'); | 542 | | - | 543 | | - options = { | 544 | | - fragment: true, | 545 | | - unicode: false, | 546 | | - search: true, | 547 | | - auth: true, | 548 | | - ...options, | 549 | | - }; | 550 | | - | 551 | | - // https://url.spec.whatwg.org/#url-serializing | 552 | | - let ret = ctx.protocol; | 553 | | - if (ctx.hasHost) { | 554 | | - ret += '//'; | 555 | | - const hasUsername = ctx.username !== ''; | 556 | | - const hasPassword = ctx.password !== ''; | 557 | | - if (options.auth && (hasUsername || hasPassword)) { | 558 | | - if (hasUsername) | 559 | | - ret += ctx.username; | 560 | | - if (hasPassword) | 561 | | - ret += `:${ctx.password}`; | 562 | | - ret += '@'; | 563 | | - } | 564 | | - ret += options.unicode ? | 565 | | - domainToUnicode(ctx.hostname) : ctx.hostname; | 566 | | - if (ctx.port !== '') | 567 | | - ret += `:${ctx.port}`; | 568 | | - } else if (!ctx.hasOpaquePath && ctx.pathname.lastIndexOf('/') !== 0 && ctx.pathname.startsWith('//')) { | 569 | | - ret += '/.'; | 570 | | - } | 571 | | - ret += ctx.pathname; | 572 | | - if (options.search) | 573 | | - ret += ctx.search; | 574 | | - if (options.fragment) | 575 | | - ret += ctx.hash; | 576 | | - return ret; | 577 | | -} | 578 | | - | 579 | 544 | class URL { | 580 | 545 | constructor(input, base = undefined) { | 581 | 546 | // toUSVString is not needed. | @@ -628,14 +593,8 @@ class URL { | 628 | 593 | return `${constructor.name} ${inspect(obj, opts)}`; | 629 | 594 | } | 630 | 595 |
| 631 | | - [kFormat](options) { | 632 | | - // TODO(@anonrig): Replace kFormat with actually calling setters. | 633 | | - return constructHref(this[context], options); | 634 | | - } | 635 | | - | 636 | 596 | #onParseComplete = (href, origin, protocol, hostname, pathname, | 637 | | - search, username, password, port, hash, hasHost, | 638 | | - hasOpaquePath) => { | | 597 | + search, username, password, port, hash) => { | 639 | 598 | const ctx = this[context]; | 640 | 599 | ctx.href = href; | 641 | 600 | ctx.origin = origin; | @@ -647,9 +606,6 @@ class URL { | 647 | 606 | ctx.password = password; | 648 | 607 | ctx.port = port; | 649 | 608 | ctx.hash = hash; | 650 | | - // TODO(@anonrig): Remove hasHost and hasOpaquePath when kFormat is removed. | 651 | | - ctx.hasHost = hasHost; | 652 | | - ctx.hasOpaquePath = hasOpaquePath; | 653 | 609 | if (!this[searchParams]) { // Invoked from URL constructor | 654 | 610 | this[searchParams] = new URLSearchParams(); | 655 | 611 | this[searchParams][context] = this; | @@ -862,33 +818,6 @@ ObjectDefineProperties(URL, { | 862 | 818 | revokeObjectURL: kEnumerableProperty, | 863 | 819 | }); | 864 | 820 |
| 865 | | -function update(url, params) { | 866 | | - if (!url) | 867 | | - return; | 868 | | - | 869 | | - const ctx = url[context]; | 870 | | - const serializedParams = params.toString(); | 871 | | - if (serializedParams.length > 0) { | 872 | | - ctx.search = '?' + serializedParams; | 873 | | - } else { | 874 | | - ctx.search = ''; | 875 | | - | 876 | | - // Potentially strip trailing spaces from an opaque path | 877 | | - if (ctx.hasOpaquePath && ctx.hash.length === 0) { | 878 | | - let length = ctx.pathname.length; | 879 | | - while (length > 0 && ctx.pathname.charCodeAt(length - 1) === 32) { | 880 | | - length--; | 881 | | - } | 882 | | - | 883 | | - // No need to copy the whole string if there is no space at the end | 884 | | - if (length !== ctx.pathname.length) { | 885 | | - ctx.pathname = ctx.pathname.slice(0, length); | 886 | | - } | 887 | | - } | 888 | | - } | 889 | | - ctx.href = constructHref(ctx); | 890 | | -} | 891 | | - | 892 | 821 | function initSearchParams(url, init) { | 893 | 822 | if (!init) { | 894 | 823 | url[searchParams] = []; | @@ -1387,7 +1316,6 @@ module.exports = { | 1387 | 1316 | domainToASCII, | 1388 | 1317 | domainToUnicode, | 1389 | 1318 | urlToHttpOptions, | 1390 | | - formatSymbol: kFormat, | 1391 | 1319 | searchParamsSymbol: searchParams, | 1392 | 1320 | encodeStr, | 1393 | 1321 | }; |
Collapse file Copy file name to clipboardExpand all lines: lib/url.js +38-8Lines changed: 38 additions & 8 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 22 | 22 | 'use strict'; | 23 | 23 |
| 24 | 24 | const { | | 25 | + Boolean, | 25 | 26 | Int8Array, | 26 | 27 | ObjectCreate, | 27 | 28 | ObjectKeys, | @@ -38,7 +39,10 @@ const { | 38 | 39 | ERR_INVALID_ARG_TYPE, | 39 | 40 | ERR_INVALID_URL, | 40 | 41 | } = require('internal/errors').codes; | 41 | | -const { validateString } = require('internal/validators'); | | 42 | +const { | | 43 | + validateString, | | 44 | + validateObject, | | 45 | +} = require('internal/validators'); | 42 | 46 |
| 43 | 47 | // This ensures setURLConstructor() is called before the native | 44 | 48 | // URL::ToObject() method is used. | @@ -51,11 +55,14 @@ const { | 51 | 55 | domainToASCII, | 52 | 56 | domainToUnicode, | 53 | 57 | fileURLToPath, | 54 | | - formatSymbol, | 55 | 58 | pathToFileURL, | 56 | 59 | urlToHttpOptions, | 57 | 60 | } = require('internal/url'); | 58 | 61 |
| | 62 | +const { | | 63 | + formatUrl, | | 64 | +} = internalBinding('url'); | | 65 | + | 59 | 66 | // Original url.parse() API | 60 | 67 |
| 61 | 68 | function Url() { | @@ -579,13 +586,36 @@ function urlFormat(urlObject, options) { | 579 | 586 | } else if (typeof urlObject !== 'object' || urlObject === null) { | 580 | 587 | throw new ERR_INVALID_ARG_TYPE('urlObject', | 581 | 588 | ['Object', 'string'], urlObject); | 582 | | - } else if (!(urlObject instanceof Url)) { | 583 | | - const format = urlObject[formatSymbol]; | 584 | | - return format ? | 585 | | - format.call(urlObject, options) : | 586 | | - Url.prototype.format.call(urlObject); | | 589 | + } else if (urlObject instanceof URL) { | | 590 | + let fragment = true; | | 591 | + let unicode = false; | | 592 | + let search = true; | | 593 | + let auth = true; | | 594 | + | | 595 | + if (options) { | | 596 | + validateObject(options, 'options'); | | 597 | + | | 598 | + if (options.fragment != null) { | | 599 | + fragment = Boolean(options.fragment); | | 600 | + } | | 601 | + | | 602 | + if (options.unicode != null) { | | 603 | + unicode = Boolean(options.unicode); | | 604 | + } | | 605 | + | | 606 | + if (options.search != null) { | | 607 | + search = Boolean(options.search); | | 608 | + } | | 609 | + | | 610 | + if (options.auth != null) { | | 611 | + auth = Boolean(options.auth); | | 612 | + } | | 613 | + } | | 614 | + | | 615 | + return formatUrl(urlObject.href, fragment, unicode, search, auth); | 587 | 616 | } | 588 | | - return urlObject.format(); | | 617 | + | | 618 | + return Url.prototype.format.call(urlObject); | 589 | 619 | } | 590 | 620 |
| 591 | 621 | // These characters do not need escaping: | |
Collapse file Copy file name to clipboardExpand all lines: src/node_url.cc +59-14Lines changed: 59 additions & 14 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 11 | 11 |
| 12 | 12 | namespace node { | 13 | 13 |
| 14 | | -using v8::Boolean; | 15 | 14 | using v8::Context; | 16 | 15 | using v8::Function; | 17 | 16 | using v8::FunctionCallbackInfo; | @@ -47,7 +46,7 @@ enum url_update_action { | 47 | 46 | kHref = 9, | 48 | 47 | }; | 49 | 48 |
| 50 | | -void SetArgs(Environment* env, Local<Value> argv[12], const ada::result& url) { | | 49 | +void SetArgs(Environment* env, Local<Value> argv[10], const ada::result& url) { | 51 | 50 | Isolate* isolate = env->isolate(); | 52 | 51 | argv[0] = Utf8String(isolate, url->get_href()); | 53 | 52 | argv[1] = Utf8String(isolate, url->get_origin()); | @@ -59,8 +58,6 @@ void SetArgs(Environment* env, Local<Value> argv[12], const ada::result& url) { | 59 | 58 | argv[7] = Utf8String(isolate, url->get_password()); | 60 | 59 | argv[8] = Utf8String(isolate, url->get_port()); | 61 | 60 | argv[9] = Utf8String(isolate, url->get_hash()); | 62 | | - argv[10] = Boolean::New(isolate, url->host.has_value()); | 63 | | - argv[11] = Boolean::New(isolate, url->has_opaque_path); | 64 | 61 | } | 65 | 62 |
| 66 | 63 | void Parse(const FunctionCallbackInfo<Value>& args) { | @@ -86,8 +83,7 @@ void Parse(const FunctionCallbackInfo<Value>& args) { | 86 | 83 | } | 87 | 84 | base_pointer = &base.value(); | 88 | 85 | } | 89 | | - ada::result out = | 90 | | - ada::parse(std::string_view(input.out(), input.length()), base_pointer); | | 86 | + ada::result out = ada::parse(input.ToStringView(), base_pointer); | 91 | 87 |
| 92 | 88 | if (!out) { | 93 | 89 | return args.GetReturnValue().Set(false); | @@ -105,8 +101,6 @@ void Parse(const FunctionCallbackInfo<Value>& args) { | 105 | 101 | undef, | 106 | 102 | undef, | 107 | 103 | undef, | 108 | | - undef, | 109 | | - undef, | 110 | 104 | }; | 111 | 105 | SetArgs(env, argv, out); | 112 | 106 | USE(success_callback_->Call( | @@ -192,10 +186,8 @@ void UpdateUrl(const FunctionCallbackInfo<Value>& args) { | 192 | 186 | Utf8Value new_value(isolate, args[2].As<String>()); | 193 | 187 | Local<Function> success_callback_ = args[3].As<Function>(); | 194 | 188 |
| 195 | | - std::string_view new_value_view = | 196 | | - std::string_view(new_value.out(), new_value.length()); | 197 | | - std::string_view input_view = std::string_view(input.out(), input.length()); | 198 | | - ada::result out = ada::parse(input_view); | | 189 | + std::string_view new_value_view = new_value.ToStringView(); | | 190 | + ada::result out = ada::parse(input.ToStringView()); | 199 | 191 | CHECK(out); | 200 | 192 |
| 201 | 193 | bool result{true}; | @@ -255,21 +247,73 @@ void UpdateUrl(const FunctionCallbackInfo<Value>& args) { | 255 | 247 | undef, | 256 | 248 | undef, | 257 | 249 | undef, | 258 | | - undef, | 259 | | - undef, | 260 | 250 | }; | 261 | 251 | SetArgs(env, argv, out); | 262 | 252 | USE(success_callback_->Call( | 263 | 253 | env->context(), args.This(), arraysize(argv), argv)); | 264 | 254 | args.GetReturnValue().Set(result); | 265 | 255 | } | 266 | 256 |
| | 257 | +void FormatUrl(const FunctionCallbackInfo<Value>& args) { | | 258 | + CHECK_GT(args.Length(), 4); | | 259 | + CHECK(args[0]->IsString()); // url href | | 260 | + | | 261 | + Environment* env = Environment::GetCurrent(args); | | 262 | + Isolate* isolate = env->isolate(); | | 263 | + | | 264 | + Utf8Value href(isolate, args[0].As<String>()); | | 265 | + const bool fragment = args[1]->IsTrue(); | | 266 | + const bool unicode = args[2]->IsTrue(); | | 267 | + const bool search = args[3]->IsTrue(); | | 268 | + const bool auth = args[4]->IsTrue(); | | 269 | + | | 270 | + ada::result out = ada::parse(href.ToStringView()); | | 271 | + CHECK(out); | | 272 | + | | 273 | + if (!fragment) { | | 274 | + out->fragment = std::nullopt; | | 275 | + } | | 276 | + | | 277 | + if (unicode) { | | 278 | +#if defined(NODE_HAVE_I18N_SUPPORT) | | 279 | + std::string hostname = out->get_hostname(); | | 280 | + MaybeStackBuffer<char> buf; | | 281 | + int32_t len = i18n::ToUnicode(&buf, hostname.data(), hostname.length()); | | 282 | + | | 283 | + if (len < 0) { | | 284 | + out->host = ""; | | 285 | + } else { | | 286 | + out->host = buf.ToString(); | | 287 | + } | | 288 | +#else | | 289 | + out->host = ""; | | 290 | +#endif | | 291 | + } | | 292 | + | | 293 | + if (!search) { | | 294 | + out->query = std::nullopt; | | 295 | + } | | 296 | + | | 297 | + if (!auth) { | | 298 | + out->username = ""; | | 299 | + out->password = ""; | | 300 | + } | | 301 | + | | 302 | + std::string result = out->get_href(); | | 303 | + args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(), | | 304 | + result.data(), | | 305 | + NewStringType::kNormal, | | 306 | + result.length()) | | 307 | + .ToLocalChecked()); | | 308 | +} | | 309 | + | 267 | 310 | void Initialize(Local<Object> target, | 268 | 311 | Local<Value> unused, | 269 | 312 | Local<Context> context, | 270 | 313 | void* priv) { | 271 | 314 | SetMethod(context, target, "parse", Parse); | 272 | 315 | SetMethod(context, target, "updateUrl", UpdateUrl); | | 316 | + SetMethod(context, target, "formatUrl", FormatUrl); | 273 | 317 |
| 274 | 318 | SetMethodNoSideEffect(context, target, "domainToASCII", DomainToASCII); | 275 | 319 | SetMethodNoSideEffect(context, target, "domainToUnicode", DomainToUnicode); | @@ -279,6 +323,7 @@ void Initialize(Local<Object> target, | 279 | 323 | void RegisterExternalReferences(ExternalReferenceRegistry* registry) { | 280 | 324 | registry->Register(Parse); | 281 | 325 | registry->Register(UpdateUrl); | | 326 | + registry->Register(FormatUrl); | 282 | 327 |
| 283 | 328 | registry->Register(DomainToASCII); | 284 | 329 | registry->Register(DomainToUnicode); | |
|