From ba687f6eb431ce7fd5e324ef495d2e133a3f254b Mon Sep 17 00:00:00 2001 From: Yazhong Liu Date: Thu, 20 Nov 2014 23:46:38 +0800 Subject: [PATCH] url: support `path` for url.format this adds support for a "path" field that overrides "query", "search", and "pathname" if given. Fixes: https://github.com/joyent/node/issues/8722 PR-URL: https://github.com/joyent/node/pull/8755 Reviewed-by: Chris Dickinson --- doc/api/url.markdown | 9 ++--- lib/url.js | 31 ++++++++++++++--- test/simple/test-url.js | 77 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 104 insertions(+), 13 deletions(-) diff --git a/doc/api/url.markdown b/doc/api/url.markdown index 6b8ff9d507..3ace699579 100644 --- a/doc/api/url.markdown +++ b/doc/api/url.markdown @@ -95,11 +95,12 @@ Take a parsed URL object, and return a formatted URL string. * `hostname` will only be used if `host` is absent. * `port` will only be used if `host` is absent. * `host` will be used in place of `hostname` and `port` -* `pathname` is treated the same with or without the leading `/` (slash) -* `search` will be used in place of `query` +* `pathname` is treated the same with or without the leading `/` (slash). +* `path` is treated the same with `pathname` but able to contain `query` as well. +* `search` will be used in place of `query`. * `query` (object; see `querystring`) will only be used if `search` is absent. -* `search` is treated the same with or without the leading `?` (question mark) -* `hash` is treated the same with or without the leading `#` (pound sign, anchor) +* `search` is treated the same with or without the leading `?` (question mark). +* `hash` is treated the same with or without the leading `#` (pound sign, anchor). ## url.resolve(from, to) diff --git a/lib/url.js b/lib/url.js index 6a6a19d81e..56b1be9328 100644 --- a/lib/url.js +++ b/lib/url.js @@ -362,7 +362,7 @@ Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { } // finally, reconstruct the href based on what has been validated. - this.href = this.format(); + this.href = this.format(parseQueryString); return this; }; @@ -377,7 +377,7 @@ function urlFormat(obj) { return obj.format(); } -Url.prototype.format = function() { +Url.prototype.format = function(parseQueryString) { var auth = this.auth || ''; if (auth) { auth = encodeURIComponent(auth); @@ -389,7 +389,26 @@ Url.prototype.format = function() { pathname = this.pathname || '', hash = this.hash || '', host = false, - query = ''; + query = '', + search = ''; + + if (this.path) { + var qm = this.path.indexOf('?'); + if (qm !== -1) { + query = this.path.slice(qm + 1); + search = '?' + query; + pathname = this.path.slice(0, qm); + } else { + if (parseQueryString) { + this.query = {}; + this.search = ''; + } else { + this.query = null; + this.search = null; + } + pathname = this.path; + } + } if (this.host) { host = auth + this.host; @@ -402,13 +421,15 @@ Url.prototype.format = function() { } } - if (this.query && + if (!query && + this.query && util.isObject(this.query) && Object.keys(this.query).length) { query = querystring.stringify(this.query); } - var search = this.search || (query && ('?' + query)) || ''; + if (!search) + search = this.search || (query && ('?' + query)) || ''; if (protocol && protocol.substr(-1) !== ':') protocol += ':'; diff --git a/test/simple/test-url.js b/test/simple/test-url.js index 8bfedcdf3d..df72cc6f4e 100644 --- a/test/simple/test-url.js +++ b/test/simple/test-url.js @@ -1085,7 +1085,7 @@ var formatTests = { // `#`,`?` in path '/path/to/%%23%3F+=&.txt?foo=theA1#bar' : { - href : '/path/to/%%23%3F+=&.txt?foo=theA1#bar', + href: '/path/to/%%23%3F+=&.txt?foo=theA1#bar', pathname: '/path/to/%#?+=&.txt', query: { foo: 'theA1' @@ -1095,7 +1095,7 @@ var formatTests = { // `#`,`?` in path + `#` in query '/path/to/%%23%3F+=&.txt?foo=the%231#bar' : { - href : '/path/to/%%23%3F+=&.txt?foo=the%231#bar', + href: '/path/to/%%23%3F+=&.txt?foo=the%231#bar', pathname: '/path/to/%#?+=&.txt', query: { foo: 'the#1' @@ -1110,7 +1110,7 @@ var formatTests = { hostname: 'ex.com', hash: '#frag', search: '?abc=the#1?&foo=bar', - pathname: '/foo?100%m#r', + pathname: '/foo?100%m#r' }, // `?` and `#` in search only @@ -1120,8 +1120,77 @@ var formatTests = { hostname: 'ex.com', hash: '#frag', search: '?abc=the#1?&foo=bar', - pathname: '/fooA100%mBr', + pathname: '/fooA100%mBr' + }, + + // path + 'http://github.com/joyent/node#js1': { + href: 'http://github.com/joyent/node#js1', + protocol: 'http:', + hostname: 'github.com', + hash: '#js1', + path: '/joyent/node' + }, + + // pathname vs. path, path wins + 'http://github.com/joyent/node2#js1': { + href: 'http://github.com/joyent/node2#js1', + protocol: 'http:', + hostname: 'github.com', + hash: '#js1', + path: '/joyent/node2', + pathname: '/joyent/node' + }, + + // pathname with query/search + 'http://github.com/joyent/node?foo=bar#js2': { + href: 'http://github.com/joyent/node?foo=bar#js2', + protocol: 'http:', + hostname: 'github.com', + hash: '#js2', + path: '/joyent/node?foo=bar' + }, + + // path vs. query, path wins + 'http://github.com/joyent/node?foo=bar2#js3': { + href: 'http://github.com/joyent/node?foo=bar2#js3', + protocol: 'http:', + hostname: 'github.com', + hash: '#js3', + path: '/joyent/node?foo=bar2', + query: {foo: 'bar'} + }, + + // path vs. search, path wins + 'http://github.com/joyent/node?foo=bar3#js4': { + href: 'http://github.com/joyent/node?foo=bar3#js4', + protocol: 'http:', + hostname: 'github.com', + hash: '#js4', + path: '/joyent/node?foo=bar3', + search: '?foo=bar' + }, + + // path is present without ? vs. query given + 'http://github.com/joyent/node#js5': { + href: 'http://github.com/joyent/node#js5', + protocol: 'http:', + hostname: 'github.com', + hash: '#js5', + path: '/joyent/node', + query: {foo: 'bar'} + }, + + // path is present without ? vs. search given + 'http://github.com/joyent/node#js6': { + href: 'http://github.com/joyent/node#js6', + protocol: 'http:', + hostname: 'github.com', + hash: '#js6', + path: '/joyent/node', + search: '?foo=bar' } + }; for (var u in formatTests) { var expect = formatTests[u].href;