diff --git a/lib/internal/url.js b/lib/internal/url.js index 113746a24d5edc..b5ef83ae451eaf 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -1,13 +1,5 @@ 'use strict'; -function getPunycode() { - try { - return process.binding('icu'); - } catch (err) { - return require('punycode'); - } -} -const punycode = getPunycode(); const util = require('util'); const binding = process.binding('url'); const context = Symbol('context'); @@ -20,6 +12,7 @@ const kScheme = Symbol('scheme'); const kHost = Symbol('host'); const kPort = Symbol('port'); const kDomain = Symbol('domain'); +const kFormat = Symbol('format'); // https://tc39.github.io/ecma262/#sec-%iteratorprototype%-object const IteratorPrototype = Object.getPrototypeOf( @@ -263,18 +256,19 @@ class URL { } Object.defineProperties(URL.prototype, { - toString: { - // https://heycam.github.io/webidl/#es-stringifier - writable: true, - enumerable: true, - configurable: true, + [kFormat]: { + enumerable: false, + configurable: false, // eslint-disable-next-line func-name-matching - value: function toString(options) { - options = options || {}; - const fragment = - options.fragment !== undefined ? - !!options.fragment : true; - const unicode = !!options.unicode; + value: function format(options) { + if (options && typeof options !== 'object') + throw new TypeError('options must be an object'); + options = Object.assign({ + fragment: true, + unicode: false, + search: true, + auth: true + }, options); const ctx = this[context]; var ret; if (this.protocol) @@ -284,28 +278,23 @@ Object.defineProperties(URL.prototype, { const has_username = typeof ctx.username === 'string'; const has_password = typeof ctx.password === 'string' && ctx.password !== ''; - if (has_username || has_password) { + if (options.auth && (has_username || has_password)) { if (has_username) ret += ctx.username; if (has_password) ret += `:${ctx.password}`; ret += '@'; } - if (unicode) { - ret += punycode.toUnicode(this.hostname); - if (this.port !== undefined) - ret += `:${this.port}`; - } else { - ret += this.host; - } + ret += options.unicode ? + domainToUnicode(this.host) : this.host; } else if (ctx.scheme === 'file:') { ret += '//'; } if (this.pathname) ret += this.pathname; - if (typeof ctx.query === 'string') + if (options.search && typeof ctx.query === 'string') ret += `?${ctx.query}`; - if (fragment & typeof ctx.fragment === 'string') + if (options.fragment && typeof ctx.fragment === 'string') ret += `#${ctx.fragment}`; return ret; } @@ -314,11 +303,21 @@ Object.defineProperties(URL.prototype, { configurable: true, value: 'URL' }, + toString: { + // https://heycam.github.io/webidl/#es-stringifier + writable: true, + enumerable: true, + configurable: true, + // eslint-disable-next-line func-name-matching + value: function toString() { + return this[kFormat]({}); + } + }, href: { enumerable: true, configurable: true, get() { - return this.toString(); + return this[kFormat]({}); }, set(input) { parse(this, input); @@ -1078,3 +1077,4 @@ exports.domainToASCII = domainToASCII; exports.domainToUnicode = domainToUnicode; exports.encodeAuth = encodeAuth; exports.urlToOptions = urlToOptions; +exports.formatSymbol = kFormat; diff --git a/lib/url.js b/lib/url.js index 2f05cc6648ab48..9164d5991b7a67 100644 --- a/lib/url.js +++ b/lib/url.js @@ -549,19 +549,22 @@ function autoEscapeStr(rest) { } // format a parsed object into a url string -function urlFormat(obj) { +function urlFormat(obj, options) { // ensure it's an object, and not a string url. // If it's an obj, this is a no-op. // this way, you can call url_format() on strings // to clean up potentially wonky urls. - if (typeof obj === 'string') obj = urlParse(obj); - - else if (typeof obj !== 'object' || obj === null) + if (typeof obj === 'string') { + obj = urlParse(obj); + } else if (typeof obj !== 'object' || obj === null) { throw new TypeError('Parameter "urlObj" must be an object, not ' + obj === null ? 'null' : typeof obj); - - else if (!(obj instanceof Url)) return Url.prototype.format.call(obj); - + } else if (!(obj instanceof Url)) { + var format = obj[internalUrl.formatSymbol]; + return format ? + format.call(obj, options) : + Url.prototype.format.call(obj); + } return obj.format(); } diff --git a/test/parallel/test-url-format-whatwg.js b/test/parallel/test-url-format-whatwg.js new file mode 100644 index 00000000000000..507d3f8419d73e --- /dev/null +++ b/test/parallel/test-url-format-whatwg.js @@ -0,0 +1,102 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const url = require('url'); +const URL = url.URL; + +const myURL = new URL('http://xn--lck1c3crb1723bpq4a.com/a?a=b#c'); + +assert.strictEqual( + url.format(myURL), + 'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, {}), + 'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +const errreg = /^TypeError: options must be an object$/; +assert.throws(() => url.format(myURL, true), errreg); +assert.throws(() => url.format(myURL, 1), errreg); +assert.throws(() => url.format(myURL, 'test'), errreg); +assert.throws(() => url.format(myURL, Infinity), errreg); + +// Any falsy value other than undefined will be treated as false. +// Any truthy value will be treated as true. + +assert.strictEqual( + url.format(myURL, {fragment: false}), + 'http://xn--lck1c3crb1723bpq4a.com/a?a=b' +); + +assert.strictEqual( + url.format(myURL, {fragment: ''}), + 'http://xn--lck1c3crb1723bpq4a.com/a?a=b' +); + +assert.strictEqual( + url.format(myURL, {fragment: 0}), + 'http://xn--lck1c3crb1723bpq4a.com/a?a=b' +); + +assert.strictEqual( + url.format(myURL, {fragment: 1}), + 'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, {fragment: {}}), + 'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, {search: false}), + 'http://xn--lck1c3crb1723bpq4a.com/a#c' +); + +assert.strictEqual( + url.format(myURL, {search: ''}), + 'http://xn--lck1c3crb1723bpq4a.com/a#c' +); + +assert.strictEqual( + url.format(myURL, {search: 0}), + 'http://xn--lck1c3crb1723bpq4a.com/a#c' +); + +assert.strictEqual( + url.format(myURL, {search: 1}), + 'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, {search: {}}), + 'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, {unicode: true}), + 'http://理容ナカムラ.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, {unicode: 1}), + 'http://理容ナカムラ.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, {unicode: {}}), + 'http://理容ナカムラ.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, {unicode: false}), + 'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, {unicode: 0}), + 'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c' +);