Skip to content

Commit 322fc20

Browse files
jasnellitaloacasas
authored andcommitted
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: #10857 Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Timothy Gu <[email protected]> Reviewed-By: Brian White <[email protected]>
1 parent 92ed2b5 commit 322fc20

File tree

3 files changed

+142
-37
lines changed

3 files changed

+142
-37
lines changed

lib/internal/url.js

+30-30
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
'use strict';
22

3-
function getPunycode() {
4-
try {
5-
return process.binding('icu');
6-
} catch (err) {
7-
return require('punycode');
8-
}
9-
}
10-
const punycode = getPunycode();
113
const util = require('util');
124
const binding = process.binding('url');
135
const context = Symbol('context');
@@ -20,6 +12,7 @@ const kScheme = Symbol('scheme');
2012
const kHost = Symbol('host');
2113
const kPort = Symbol('port');
2214
const kDomain = Symbol('domain');
15+
const kFormat = Symbol('format');
2316

2417
// https://tc39.github.io/ecma262/#sec-%iteratorprototype%-object
2518
const IteratorPrototype = Object.getPrototypeOf(
@@ -263,18 +256,19 @@ class URL {
263256
}
264257

265258
Object.defineProperties(URL.prototype, {
266-
toString: {
267-
// https://heycam.github.io/webidl/#es-stringifier
268-
writable: true,
269-
enumerable: true,
270-
configurable: true,
259+
[kFormat]: {
260+
enumerable: false,
261+
configurable: false,
271262
// eslint-disable-next-line func-name-matching
272-
value: function toString(options) {
273-
options = options || {};
274-
const fragment =
275-
options.fragment !== undefined ?
276-
!!options.fragment : true;
277-
const unicode = !!options.unicode;
263+
value: function format(options) {
264+
if (options && typeof options !== 'object')
265+
throw new TypeError('options must be an object');
266+
options = Object.assign({
267+
fragment: true,
268+
unicode: false,
269+
search: true,
270+
auth: true
271+
}, options);
278272
const ctx = this[context];
279273
var ret;
280274
if (this.protocol)
@@ -284,28 +278,23 @@ Object.defineProperties(URL.prototype, {
284278
const has_username = typeof ctx.username === 'string';
285279
const has_password = typeof ctx.password === 'string' &&
286280
ctx.password !== '';
287-
if (has_username || has_password) {
281+
if (options.auth && (has_username || has_password)) {
288282
if (has_username)
289283
ret += ctx.username;
290284
if (has_password)
291285
ret += `:${ctx.password}`;
292286
ret += '@';
293287
}
294-
if (unicode) {
295-
ret += punycode.toUnicode(this.hostname);
296-
if (this.port !== undefined)
297-
ret += `:${this.port}`;
298-
} else {
299-
ret += this.host;
300-
}
288+
ret += options.unicode ?
289+
domainToUnicode(this.host) : this.host;
301290
} else if (ctx.scheme === 'file:') {
302291
ret += '//';
303292
}
304293
if (this.pathname)
305294
ret += this.pathname;
306-
if (typeof ctx.query === 'string')
295+
if (options.search && typeof ctx.query === 'string')
307296
ret += `?${ctx.query}`;
308-
if (fragment & typeof ctx.fragment === 'string')
297+
if (options.fragment && typeof ctx.fragment === 'string')
309298
ret += `#${ctx.fragment}`;
310299
return ret;
311300
}
@@ -314,11 +303,21 @@ Object.defineProperties(URL.prototype, {
314303
configurable: true,
315304
value: 'URL'
316305
},
306+
toString: {
307+
// https://heycam.github.io/webidl/#es-stringifier
308+
writable: true,
309+
enumerable: true,
310+
configurable: true,
311+
// eslint-disable-next-line func-name-matching
312+
value: function toString() {
313+
return this[kFormat]({});
314+
}
315+
},
317316
href: {
318317
enumerable: true,
319318
configurable: true,
320319
get() {
321-
return this.toString();
320+
return this[kFormat]({});
322321
},
323322
set(input) {
324323
parse(this, input);
@@ -1078,3 +1077,4 @@ exports.domainToASCII = domainToASCII;
10781077
exports.domainToUnicode = domainToUnicode;
10791078
exports.encodeAuth = encodeAuth;
10801079
exports.urlToOptions = urlToOptions;
1080+
exports.formatSymbol = kFormat;

lib/url.js

+10-7
Original file line numberDiff line numberDiff line change
@@ -549,19 +549,22 @@ function autoEscapeStr(rest) {
549549
}
550550

551551
// format a parsed object into a url string
552-
function urlFormat(obj) {
552+
function urlFormat(obj, options) {
553553
// ensure it's an object, and not a string url.
554554
// If it's an obj, this is a no-op.
555555
// this way, you can call url_format() on strings
556556
// to clean up potentially wonky urls.
557-
if (typeof obj === 'string') obj = urlParse(obj);
558-
559-
else if (typeof obj !== 'object' || obj === null)
557+
if (typeof obj === 'string') {
558+
obj = urlParse(obj);
559+
} else if (typeof obj !== 'object' || obj === null) {
560560
throw new TypeError('Parameter "urlObj" must be an object, not ' +
561561
obj === null ? 'null' : typeof obj);
562-
563-
else if (!(obj instanceof Url)) return Url.prototype.format.call(obj);
564-
562+
} else if (!(obj instanceof Url)) {
563+
var format = obj[internalUrl.formatSymbol];
564+
return format ?
565+
format.call(obj, options) :
566+
Url.prototype.format.call(obj);
567+
}
565568
return obj.format();
566569
}
567570

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const url = require('url');
6+
const URL = url.URL;
7+
8+
const myURL = new URL('http://xn--lck1c3crb1723bpq4a.com/a?a=b#c');
9+
10+
assert.strictEqual(
11+
url.format(myURL),
12+
'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c'
13+
);
14+
15+
assert.strictEqual(
16+
url.format(myURL, {}),
17+
'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c'
18+
);
19+
20+
const errreg = /^TypeError: options must be an object$/;
21+
assert.throws(() => url.format(myURL, true), errreg);
22+
assert.throws(() => url.format(myURL, 1), errreg);
23+
assert.throws(() => url.format(myURL, 'test'), errreg);
24+
assert.throws(() => url.format(myURL, Infinity), errreg);
25+
26+
// Any falsy value other than undefined will be treated as false.
27+
// Any truthy value will be treated as true.
28+
29+
assert.strictEqual(
30+
url.format(myURL, {fragment: false}),
31+
'http://xn--lck1c3crb1723bpq4a.com/a?a=b'
32+
);
33+
34+
assert.strictEqual(
35+
url.format(myURL, {fragment: ''}),
36+
'http://xn--lck1c3crb1723bpq4a.com/a?a=b'
37+
);
38+
39+
assert.strictEqual(
40+
url.format(myURL, {fragment: 0}),
41+
'http://xn--lck1c3crb1723bpq4a.com/a?a=b'
42+
);
43+
44+
assert.strictEqual(
45+
url.format(myURL, {fragment: 1}),
46+
'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c'
47+
);
48+
49+
assert.strictEqual(
50+
url.format(myURL, {fragment: {}}),
51+
'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c'
52+
);
53+
54+
assert.strictEqual(
55+
url.format(myURL, {search: false}),
56+
'http://xn--lck1c3crb1723bpq4a.com/a#c'
57+
);
58+
59+
assert.strictEqual(
60+
url.format(myURL, {search: ''}),
61+
'http://xn--lck1c3crb1723bpq4a.com/a#c'
62+
);
63+
64+
assert.strictEqual(
65+
url.format(myURL, {search: 0}),
66+
'http://xn--lck1c3crb1723bpq4a.com/a#c'
67+
);
68+
69+
assert.strictEqual(
70+
url.format(myURL, {search: 1}),
71+
'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c'
72+
);
73+
74+
assert.strictEqual(
75+
url.format(myURL, {search: {}}),
76+
'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c'
77+
);
78+
79+
assert.strictEqual(
80+
url.format(myURL, {unicode: true}),
81+
'http://理容ナカムラ.com/a?a=b#c'
82+
);
83+
84+
assert.strictEqual(
85+
url.format(myURL, {unicode: 1}),
86+
'http://理容ナカムラ.com/a?a=b#c'
87+
);
88+
89+
assert.strictEqual(
90+
url.format(myURL, {unicode: {}}),
91+
'http://理容ナカムラ.com/a?a=b#c'
92+
);
93+
94+
assert.strictEqual(
95+
url.format(myURL, {unicode: false}),
96+
'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c'
97+
);
98+
99+
assert.strictEqual(
100+
url.format(myURL, {unicode: 0}),
101+
'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c'
102+
);

0 commit comments

Comments
 (0)