Browse Source

url: extend url.format to support WHATWG URL

Removes the non-standard options on WHATWG URL toString
and extends the existing url.format() API to support
customizable serialization of the WHATWG URL object.

This does not yet include the documentation updates
because the documentation for the new WHATWG URL object
has not yet landed.

Example:

```js
const url = require('url');
const URL = url.URL;
const myURL = new URL('http://example.org/?a=b#c');
const str = url.format(myURL, {fragment: false, search: false});
console.log(str);
  // Prints: http://example.org/
```

PR-URL: https://github.com/nodejs/node/pull/10857
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: Brian White <mscdex@mscdex.net>
v6
James M Snell 8 years ago
parent
commit
c5e9654b5b
  1. 60
      lib/internal/url.js
  2. 17
      lib/url.js
  3. 102
      test/parallel/test-url-format-whatwg.js

60
lib/internal/url.js

@ -1,13 +1,5 @@
'use strict'; 'use strict';
function getPunycode() {
try {
return process.binding('icu');
} catch (err) {
return require('punycode');
}
}
const punycode = getPunycode();
const util = require('util'); const util = require('util');
const binding = process.binding('url'); const binding = process.binding('url');
const context = Symbol('context'); const context = Symbol('context');
@ -20,6 +12,7 @@ const kScheme = Symbol('scheme');
const kHost = Symbol('host'); const kHost = Symbol('host');
const kPort = Symbol('port'); const kPort = Symbol('port');
const kDomain = Symbol('domain'); const kDomain = Symbol('domain');
const kFormat = Symbol('format');
// https://tc39.github.io/ecma262/#sec-%iteratorprototype%-object // https://tc39.github.io/ecma262/#sec-%iteratorprototype%-object
const IteratorPrototype = Object.getPrototypeOf( const IteratorPrototype = Object.getPrototypeOf(
@ -263,18 +256,19 @@ class URL {
} }
Object.defineProperties(URL.prototype, { Object.defineProperties(URL.prototype, {
toString: { [kFormat]: {
// https://heycam.github.io/webidl/#es-stringifier enumerable: false,
writable: true, configurable: false,
enumerable: true,
configurable: true,
// eslint-disable-next-line func-name-matching // eslint-disable-next-line func-name-matching
value: function toString(options) { value: function format(options) {
options = options || {}; if (options && typeof options !== 'object')
const fragment = throw new TypeError('options must be an object');
options.fragment !== undefined ? options = Object.assign({
!!options.fragment : true; fragment: true,
const unicode = !!options.unicode; unicode: false,
search: true,
auth: true
}, options);
const ctx = this[context]; const ctx = this[context];
var ret; var ret;
if (this.protocol) if (this.protocol)
@ -284,28 +278,23 @@ Object.defineProperties(URL.prototype, {
const has_username = typeof ctx.username === 'string'; const has_username = typeof ctx.username === 'string';
const has_password = typeof ctx.password === 'string' && const has_password = typeof ctx.password === 'string' &&
ctx.password !== ''; ctx.password !== '';
if (has_username || has_password) { if (options.auth && (has_username || has_password)) {
if (has_username) if (has_username)
ret += ctx.username; ret += ctx.username;
if (has_password) if (has_password)
ret += `:${ctx.password}`; ret += `:${ctx.password}`;
ret += '@'; ret += '@';
} }
if (unicode) { ret += options.unicode ?
ret += punycode.toUnicode(this.hostname); domainToUnicode(this.host) : this.host;
if (this.port !== undefined)
ret += `:${this.port}`;
} else {
ret += this.host;
}
} else if (ctx.scheme === 'file:') { } else if (ctx.scheme === 'file:') {
ret += '//'; ret += '//';
} }
if (this.pathname) if (this.pathname)
ret += this.pathname; ret += this.pathname;
if (typeof ctx.query === 'string') if (options.search && typeof ctx.query === 'string')
ret += `?${ctx.query}`; ret += `?${ctx.query}`;
if (fragment & typeof ctx.fragment === 'string') if (options.fragment && typeof ctx.fragment === 'string')
ret += `#${ctx.fragment}`; ret += `#${ctx.fragment}`;
return ret; return ret;
} }
@ -314,11 +303,21 @@ Object.defineProperties(URL.prototype, {
configurable: true, configurable: true,
value: 'URL' 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: { href: {
enumerable: true, enumerable: true,
configurable: true, configurable: true,
get() { get() {
return this.toString(); return this[kFormat]({});
}, },
set(input) { set(input) {
parse(this, input); parse(this, input);
@ -1120,3 +1119,4 @@ exports.domainToASCII = domainToASCII;
exports.domainToUnicode = domainToUnicode; exports.domainToUnicode = domainToUnicode;
exports.encodeAuth = encodeAuth; exports.encodeAuth = encodeAuth;
exports.urlToOptions = urlToOptions; exports.urlToOptions = urlToOptions;
exports.formatSymbol = kFormat;

17
lib/url.js

@ -538,19 +538,22 @@ function autoEscapeStr(rest) {
} }
// format a parsed object into a url string // 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. // ensure it's an object, and not a string url.
// If it's an obj, this is a no-op. // If it's an obj, this is a no-op.
// this way, you can call url_format() on strings // this way, you can call url_format() on strings
// to clean up potentially wonky urls. // to clean up potentially wonky urls.
if (typeof obj === 'string') obj = urlParse(obj); if (typeof obj === 'string') {
obj = urlParse(obj);
else if (typeof obj !== 'object' || obj === null) } else if (typeof obj !== 'object' || obj === null) {
throw new TypeError('Parameter "urlObj" must be an object, not ' + throw new TypeError('Parameter "urlObj" must be an object, not ' +
obj === null ? 'null' : typeof obj); obj === null ? 'null' : typeof obj);
} else if (!(obj instanceof Url)) {
else if (!(obj instanceof Url)) return Url.prototype.format.call(obj); var format = obj[internalUrl.formatSymbol];
return format ?
format.call(obj, options) :
Url.prototype.format.call(obj);
}
return obj.format(); return obj.format();
} }

102
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'
);
Loading…
Cancel
Save