From 702997c1f08516fe84f52957cfd830a46d7eceac Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Sun, 3 May 2015 15:05:44 -0700 Subject: Revert "url: significantly improve the performance of the url module" This reverts commit 3fd7fc429c394059113432312ed19decbafd8fc4. It was agreed that this change contained too much potential ecosystem breakage, particularly around the inability to `delete` properties off a `Url` object. It may be re-introduced for a later release, along with better work on ecosystem compatibility. PR-URL: https://github.com/iojs/io.js/pull/1602 Reviewed-By: Mikeal Rogers Reviewed-By: Ben Noordhuis Reviewed-By: Forrest L Norvell Reviewed-By: Chris Dickinson Reviewed-By: Isaac Z. Schlueter Reviewed-By: Jeremiah Senkpiel --- lib/_http_client.js | 2 +- lib/https.js | 2 +- lib/url.js | 1566 ++++++++++++----------------------- lib/util.js | 3 - test/parallel/test-url-accessors.js | 378 --------- test/parallel/test-url.js | 1364 +----------------------------- 6 files changed, 529 insertions(+), 2786 deletions(-) delete mode 100644 test/parallel/test-url-accessors.js diff --git a/lib/_http_client.js b/lib/_http_client.js index 63d414e929..200a08e5d5 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -20,7 +20,7 @@ function ClientRequest(options, cb) { OutgoingMessage.call(self); if (typeof options === 'string') { - options = url.parse(options).toJSON(); + options = url.parse(options); } else { options = util._extend({}, options); } diff --git a/lib/https.js b/lib/https.js index 6f919845aa..21103b71af 100644 --- a/lib/https.js +++ b/lib/https.js @@ -107,7 +107,7 @@ exports.Agent = Agent; exports.request = function(options, cb) { if (typeof options === 'string') { - options = url.parse(options).toJSON(); + options = url.parse(options); } else { options = util._extend({}, options); } diff --git a/lib/url.js b/lib/url.js index f5edd89333..1683f90503 100644 --- a/lib/url.js +++ b/lib/url.js @@ -10,62 +10,60 @@ exports.format = urlFormat; exports.Url = Url; function Url() { - // For more efficient internal representation and laziness. - // The non-underscore versions of these properties are accessor functions - // defined on the prototype. - this._protocol = null; - this._port = -1; - this._query = null; - this._auth = null; - this._hostname = null; - this._host = null; - this._pathname = null; - this._hash = null; - this._search = null; - this._href = ''; - - this._prependSlash = false; - this._parsesQueryStrings = false; - + this.protocol = null; this.slashes = null; + this.auth = null; + this.host = null; + this.port = null; + this.hostname = null; + this.hash = null; + this.search = null; + this.query = null; + this.pathname = null; + this.path = null; + this.href = null; } // Reference: RFC 3986, RFC 1808, RFC 2396 -const _protocolCharacters = makeAsciiTable([ - [0x61, 0x7A] /*a-z*/, - [0x41, 0x5A] /*A-Z*/, - 0x2E /*'.'*/, 0x2B /*'+'*/, 0x2D /*'-'*/ -]); +// define these here so at least they only have to be +// compiled once on the first module load. +const protocolPattern = /^([a-z0-9.+-]+:)/i; +const portPattern = /:[0-9]*$/; + +// Special case for a simple path URL +const simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/; // RFC 2396: characters reserved for delimiting URLs. // We actually just auto-escape these. -// RFC 2396: characters not allowed for various reasons. -// Allowed by RFCs, but cause of XSS attacks. Always escape these. -const _autoEscape = [ - '<', '>', '\'', '`', ' ', '\r', '\n', '\t', '{', '}', '|', '\\', '^', '`', '"' -]; +const delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t']; -const _autoEscapeMap = new Array(128); - -for (let i = 0, len = _autoEscapeMap.length; i < len; ++i) { - _autoEscapeMap[i] = ''; -} - -for (let i = 0, len = _autoEscape.length; i < len; ++i) { - let c = _autoEscape[i]; - let esc = encodeURIComponent(c); - if (esc === c) - esc = escape(c); - _autoEscapeMap[c.charCodeAt(0)] = esc; -} +// RFC 2396: characters not allowed for various reasons. +const unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims); -// Same as autoEscapeMap except \ is not escaped but is turned into /. -const _afterQueryAutoEscapeMap = _autoEscapeMap.slice(); -_autoEscapeMap[0x5C /*'\'*/] = '/'; +// Allowed by RFCs, but cause of XSS attacks. Always escape these. +const autoEscape = ['\''].concat(unwise); -// Protocols that always contain a // bit. -const _slashProtocols = { +// Characters that are never ever allowed in a hostname. +// Note that any invalid chars are also handled, but these +// are the ones that are *expected* to be seen, so we fast-path them. +const nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape); +const hostEndingChars = ['/', '?', '#']; +const hostnameMaxLen = 255; +const hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/; +const hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/; +// protocols that can allow "unsafe" and "unwise" chars. +const unsafeProtocol = { + 'javascript': true, + 'javascript:': true +}; +// protocols that never have a hostname. +const hostlessProtocol = { + 'javascript': true, + 'javascript:': true +}; +// protocols that always contain a // bit. +const slashedProtocol = { 'http': true, 'https': true, 'ftp': true, @@ -77,180 +75,355 @@ const _slashProtocols = { 'gopher:': true, 'file:': true }; - -const _autoEscapeCharacters = makeAsciiTable(_autoEscape.map(function(v) { - return v.charCodeAt(0); -})); - -// Characters that are never ever allowed in a hostname. -// Note that any invalid chars are also handled, but these -// are the ones that are *expected* to be seen, so we fast-path them. -const _hostEndingCharacters = makeAsciiTable( - ['#', '?', '/', '\\'].map(function(v) { - return v.charCodeAt(0); - })); -// If these characters end a host name, the path will not be prepended a /. -const _hostEndingCharactersNoPrependSlash = makeAsciiTable([ - '<', '>', '"', '`', ' ', '\r', '\n', '\t', '{', '}', '|', '^', '`', '\'', '%', - ';' -].map(function(v) { - return v.charCodeAt(0); -})); - const querystring = require('querystring'); -const unserializablePropertyNames = ['_protocol', '_port', '_href', '_query', - '_prependSlash', '_auth', '_hostname', - '_pathname', '_hash', '_search', - '_parsesQueryStrings', '_host']; -const noSerializePattern = new RegExp('^(?:' + - unserializablePropertyNames.join('|') + - ')$'); - function urlParse(url, parseQueryString, slashesDenoteHost) { if (url instanceof Url) return url; - var u = new Url(); + var u = new Url; u.parse(url, parseQueryString, slashesDenoteHost); return u; } -Url.prototype.parse = function(str, parseQueryString, hostDenotesSlash) { - if (typeof str !== 'string') { - throw new TypeError(`Parameter 'url' must be a string, not ` + - typeof str); +Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { + if (typeof url !== 'string') { + throw new TypeError("Parameter 'url' must be a string, not " + typeof url); + } + + // Copy chrome, IE, opera backslash-handling behavior. + // Back slashes before the query string get converted to forward slashes + // See: https://code.google.com/p/chromium/issues/detail?id=25916 + var queryIndex = url.indexOf('?'), + splitter = + (queryIndex !== -1 && queryIndex < url.indexOf('#')) ? '?' : '#', + uSplit = url.split(splitter), + slashRegex = /\\/g; + uSplit[0] = uSplit[0].replace(slashRegex, '/'); + url = uSplit.join(splitter); + + var rest = url; + + // trim before proceeding. + // This is to support parse stuff like " http://foo.com \n" + rest = rest.trim(); + + if (!slashesDenoteHost && url.split('#').length === 1) { + // Try fast path regexp + var simplePath = simplePathPattern.exec(rest); + if (simplePath) { + this.path = rest; + this.href = rest; + this.pathname = simplePath[1]; + if (simplePath[2]) { + this.search = simplePath[2]; + if (parseQueryString) { + this.query = querystring.parse(this.search.substr(1)); + } else { + this.query = this.search.substr(1); + } + } else if (parseQueryString) { + this.search = ''; + this.query = {}; + } + return this; + } } - // The field's value must always be an actual boolean. - this._parsesQueryStrings = !!parseQueryString; - var start = 0; - var end = str.length - 1; - - // Trim leading and trailing ws. - while (str.charCodeAt(start) <= 0x20 /*' '*/) start++; - var trimmedStart = start; - while (str.charCodeAt(end) <= 0x20 /*' '*/) end--; - - start = this._parseProtocol(str, start, end); - - // Javascript doesn't have host. - if (this._protocol !== 'javascript') { - start = this._parseHost(str, trimmedStart, start, end, hostDenotesSlash); - var proto = this._protocol; - if (!this._hostname && - (this.slashes || (proto && !_slashProtocols[proto]))) - this._hostname = this._host = ''; + + var proto = protocolPattern.exec(rest); + if (proto) { + proto = proto[0]; + var lowerProto = proto.toLowerCase(); + this.protocol = lowerProto; + rest = rest.substr(proto.length); } - if (start <= end) { - var ch = str.charCodeAt(start); - if (ch === 0x2F /*'/'*/ || ch === 0x5C /*'\'*/) - this._parsePath(str, start, end); - else if (ch === 0x3F /*'?'*/) - this._parseQuery(str, start, end); - else if (ch === 0x23 /*'#'*/) - this._parseHash(str, start, end); - else if (this._protocol !== 'javascript') - this._parsePath(str, start, end); - else // For javascript the pathname is just the rest of it. - this._pathname = str.slice(start, end + 1); + // figure out if it's got a host + // user@server is *always* interpreted as a hostname, and url + // resolution will treat //foo/bar as host=foo,path=bar because that's + // how the browser resolves relative URLs. + if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { + var slashes = rest.substr(0, 2) === '//'; + if (slashes && !(proto && hostlessProtocol[proto])) { + rest = rest.substr(2); + this.slashes = true; + } + } + + if (!hostlessProtocol[proto] && + (slashes || (proto && !slashedProtocol[proto]))) { + + // there's a hostname. + // the first instance of /, ?, ;, or # ends the host. + // + // If there is an @ in the hostname, then non-host chars *are* allowed + // to the left of the last @ sign, unless some host-ending character + // comes *before* the @-sign. + // URLs are obnoxious. + // + // ex: + // http://a@b@c/ => user:a@b host:c + // http://a@b?@c => user:a host:c path:/?@c + + // v0.12 TODO(isaacs): This is not quite how Chrome does things. + // Review our test case against browsers more comprehensively. + + // find the first instance of any hostEndingChars + var hostEnd = -1; + for (var i = 0; i < hostEndingChars.length; i++) { + var hec = rest.indexOf(hostEndingChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) + hostEnd = hec; + } + + // at this point, either we have an explicit point where the + // auth portion cannot go past, or the last @ char is the decider. + var auth, atSign; + if (hostEnd === -1) { + // atSign can be anywhere. + atSign = rest.lastIndexOf('@'); + } else { + // atSign must be in auth portion. + // http://a@b/c@d => host:b auth:a path:/c@d + atSign = rest.lastIndexOf('@', hostEnd); + } + + // Now we have a portion which is definitely the auth. + // Pull that off. + if (atSign !== -1) { + auth = rest.slice(0, atSign); + rest = rest.slice(atSign + 1); + this.auth = decodeURIComponent(auth); + } + + // the host is the remaining to the left of the first non-host char + hostEnd = -1; + for (var i = 0; i < nonHostChars.length; i++) { + var hec = rest.indexOf(nonHostChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) + hostEnd = hec; + } + // if we still have not hit it, then the entire thing is a host. + if (hostEnd === -1) + hostEnd = rest.length; + + this.host = rest.slice(0, hostEnd); + rest = rest.slice(hostEnd); + + // pull out port. + this.parseHost(); + + // we've indicated that there is a hostname, + // so even if it's empty, it has to be present. + this.hostname = this.hostname || ''; + + // if hostname begins with [ and ends with ] + // assume that it's an IPv6 address. + var ipv6Hostname = this.hostname[0] === '[' && + this.hostname[this.hostname.length - 1] === ']'; + + // validate a little. + if (!ipv6Hostname) { + var hostparts = this.hostname.split(/\./); + for (var i = 0, l = hostparts.length; i < l; i++) { + var part = hostparts[i]; + if (!part) continue; + if (!part.match(hostnamePartPattern)) { + var newpart = ''; + for (var j = 0, k = part.length; j < k; j++) { + if (part.charCodeAt(j) > 127) { + // we replace non-ASCII char with a temporary placeholder + // we need this to make sure size of hostname is not + // broken by replacing non-ASCII by nothing + newpart += 'x'; + } else { + newpart += part[j]; + } + } + // we test again with ASCII char only + if (!newpart.match(hostnamePartPattern)) { + var validParts = hostparts.slice(0, i); + var notHost = hostparts.slice(i + 1); + var bit = part.match(hostnamePartStart); + if (bit) { + validParts.push(bit[1]); + notHost.unshift(bit[2]); + } + if (notHost.length) { + rest = '/' + notHost.join('.') + rest; + } + this.hostname = validParts.join('.'); + break; + } + } + } + } + + if (this.hostname.length > hostnameMaxLen) { + this.hostname = ''; + } else { + // hostnames are always lower case. + this.hostname = this.hostname.toLowerCase(); + } + + if (!ipv6Hostname) { + // IDNA Support: Returns a punycoded representation of "domain". + // It only converts parts of the domain name that + // have non-ASCII characters, i.e. it doesn't matter if + // you call it with a domain that already is ASCII-only. + this.hostname = punycode.toASCII(this.hostname); + } + + var p = this.port ? ':' + this.port : ''; + var h = this.hostname || ''; + this.host = h + p; + + // strip [ and ] from the hostname + // the host field still retains them, though + if (ipv6Hostname) { + this.hostname = this.hostname.substr(1, this.hostname.length - 2); + if (rest[0] !== '/') { + rest = '/' + rest; + } + } } - if (!this._pathname && this._hostname && _slashProtocols[this._protocol]) - this._pathname = '/'; - - if (parseQueryString) { - var search = this._search; - if (search == null) - search = this._search = ''; - if (search.charCodeAt(0) === 0x3F /*'?'*/) - search = search.slice(1); - // This calls a setter function, there is no .query data property. - this.query = querystring.parse(search); + // now rest is set to the post-host stuff. + // chop off any delim chars. + if (!unsafeProtocol[lowerProto]) { + + // First, make 100% sure that any "autoEscape" chars get + // escaped, even if encodeURIComponent doesn't think they + // need to be. + for (var i = 0, l = autoEscape.length; i < l; i++) { + var ae = autoEscape[i]; + if (rest.indexOf(ae) === -1) + continue; + var esc = encodeURIComponent(ae); + if (esc === ae) { + esc = escape(ae); + } + rest = rest.split(ae).join(esc); + } } -}; -function urlResolve(source, relative) { - return urlParse(source, false, true).resolve(relative); -} -Url.prototype.resolve = function(relative) { - return this.resolveObject(urlParse(relative, false, true)).format(); + // chop off from the tail first. + var hash = rest.indexOf('#'); + if (hash !== -1) { + // got a fragment string. + this.hash = rest.substr(hash); + rest = rest.slice(0, hash); + } + var qm = rest.indexOf('?'); + if (qm !== -1) { + this.search = rest.substr(qm); + this.query = rest.substr(qm + 1); + if (parseQueryString) { + this.query = querystring.parse(this.query); + } + rest = rest.slice(0, qm); + } else if (parseQueryString) { + // no query string, but parseQueryString still requested + this.search = ''; + this.query = {}; + } + if (rest) this.pathname = rest; + if (slashedProtocol[lowerProto] && + this.hostname && !this.pathname) { + this.pathname = '/'; + } + + //to support http.request + if (this.pathname || this.search) { + var p = this.pathname || ''; + var s = this.search || ''; + this.path = p + s; + } + + // finally, reconstruct the href based on what has been validated. + this.href = this.format(); + return this; }; -// Format a parsed object into a url string. +// format a parsed object into a url string function urlFormat(obj) { - // 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. // 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) { - throw new TypeError('Parameter \'urlObj\' must be an object, not ' + + 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)) return Url.prototype.format.call(obj); return obj.format(); } Url.prototype.format = function() { var auth = this.auth || ''; - if (auth) { auth = encodeURIComponent(auth); auth = auth.replace(/%3A/i, ':'); auth += '@'; } - var protocol = this.protocol || ''; - var pathname = this.pathname || ''; - var hash = this.hash || ''; - var search = this.search || ''; - var query = ''; - var hostname = this.hostname || ''; - var port = this.port || ''; - var host = false; - var scheme = ''; - - // Cache the result of the getter function. - var q = this.query; - if (q !== null && typeof q === 'object') { - query = querystring.stringify(q); + var protocol = this.protocol || '', + pathname = this.pathname || '', + hash = this.hash || '', + host = false, + query = ''; + + if (this.host) { + host = auth + this.host; + } else if (this.hostname) { + host = auth + (this.hostname.indexOf(':') === -1 ? + this.hostname : + '[' + this.hostname + ']'); + if (this.port) { + host += ':' + this.port; + } } - if (!search) { - search = query ? '?' + query : ''; + if (this.query !== null && + typeof this.query === 'object' && + Object.keys(this.query).length) { + query = querystring.stringify(this.query); } - if (protocol && protocol.charCodeAt(protocol.length - 1) !== 0x3A /*':'*/) - protocol += ':'; + var search = this.search || (query && ('?' + query)) || ''; - if (this.host) { - host = auth + this.host; - } else if (hostname) { - host = auth + hostname + (port ? ':' + port : ''); + if (protocol && protocol.substr(-1) !== ':') protocol += ':'; + + // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. + // unless they had them to begin with. + if (this.slashes || + (!protocol || slashedProtocol[protocol]) && host !== false) { + host = '//' + (host || ''); + if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname; + } else if (!host) { + host = ''; } - var slashes = this.slashes || - ((!protocol || _slashProtocols[protocol]) && host !== false); + if (hash && hash.charAt(0) !== '#') hash = '#' + hash; + if (search && search.charAt(0) !== '?') search = '?' + search; - if (protocol) scheme = protocol + (slashes ? '//' : ''); - else if (slashes) scheme = '//'; + pathname = pathname.replace(/[?#]/g, function(match) { + return encodeURIComponent(match); + }); + search = search.replace('#', '%23'); - if (slashes && pathname && pathname.charCodeAt(0) !== 0x2F /*'/'*/) { - pathname = '/' + pathname; - } - if (search && search.charCodeAt(0) !== 0x3F /*'?'*/) - search = '?' + search; - if (hash && hash.charCodeAt(0) !== 0x23 /*'#'*/) - hash = '#' + hash; + return protocol + host + pathname + search + hash; +}; - pathname = escapePathName(pathname); - search = escapeSearch(search); +function urlResolve(source, relative) { + return urlParse(source, false, true).resolve(relative); +} - return scheme + (host === false ? '' : host) + pathname + search + hash; +Url.prototype.resolve = function(relative) { + return this.resolveObject(urlParse(relative, false, true)).format(); }; function urlResolveObject(source, relative) { @@ -259,35 +432,51 @@ function urlResolveObject(source, relative) { } Url.prototype.resolveObject = function(relative) { - if (typeof relative === 'string') - relative = urlParse(relative, false, true); + if (typeof relative === 'string') { + var rel = new Url(); + rel.parse(relative, false, true); + relative = rel; + } - var result = this._clone(); + var result = new Url(); + var tkeys = Object.keys(this); + for (var tk = 0; tk < tkeys.length; tk++) { + var tkey = tkeys[tk]; + result[tkey] = this[tkey]; + } - // Hash is always overridden, no matter what. - // even href='' will remove it. - result._hash = relative._hash; + // hash is always overridden, no matter what. + // even href="" will remove it. + result.hash = relative.hash; - // If the relative url is empty, then there's nothing left to do here. - if (!relative.href) { - result._href = ''; + // if the relative url is empty, then there's nothing left to do here. + if (relative.href === '') { + result.href = result.format(); return result; } - // Hrefs like //foo/bar always cut to the protocol. - if (relative.slashes && !relative._protocol) { - relative._copyPropsTo(result, true); + // hrefs like //foo/bar always cut to the protocol. + if (relative.slashes && !relative.protocol) { + // take everything except the protocol from relative + var rkeys = Object.keys(relative); + for (var rk = 0; rk < rkeys.length; rk++) { + var rkey = rkeys[rk]; + if (rkey !== 'protocol') + result[rkey] = relative[rkey]; + } - if (_slashProtocols[result._protocol] && - result._hostname && !result._pathname) { - result._pathname = '/'; + //urlParse appends trailing / to urls like http://www.example.com + if (slashedProtocol[result.protocol] && + result.hostname && !result.pathname) { + result.path = result.pathname = '/'; } - result._href = ''; + + result.href = result.format(); return result; } - if (relative._protocol && relative._protocol !== result._protocol) { - // If it's a known url protocol, then changing + if (relative.protocol && relative.protocol !== result.protocol) { + // if it's a known url protocol, then changing // the protocol does weird things // first, if it's not file:, then we MUST have a host, // and if there was a path @@ -295,147 +484,168 @@ Url.prototype.resolveObject = function(relative) { // if it is file:, then the host is dropped, // because that's known to be hostless. // anything else is assumed to be absolute. - if (!_slashProtocols[relative._protocol]) { - relative._copyPropsTo(result, false); - result._href = ''; + if (!slashedProtocol[relative.protocol]) { + var keys = Object.keys(relative); + for (var v = 0; v < keys.length; v++) { + var k = keys[v]; + result[k] = relative[k]; + } + result.href = result.format(); return result; } - result._protocol = relative._protocol; - if (!relative._host && - !/^file:?$/.test(relative._protocol) && - relative._protocol !== 'javascript') { - var relPath = (relative._pathname || '').split('/'); - while (relPath.length && !(relative._host = relPath.shift())); - if (!relative._host) relative._host = ''; - if (!relative._hostname) relative._hostname = ''; + result.protocol = relative.protocol; + if (!relative.host && + !/^file:?$/.test(relative.protocol) && + !hostlessProtocol[relative.protocol]) { + var relPath = (relative.pathname || '').split('/'); + while (relPath.length && !(relative.host = relPath.shift())); + if (!relative.host) relative.host = ''; + if (!relative.hostname) relative.hostname = ''; if (relPath[0] !== '') relPath.unshift(''); if (relPath.length < 2) relPath.unshift(''); - result._pathname = relPath.join('/'); + result.pathname = relPath.join('/'); } else { - result._pathname = relative._pathname; + result.pathname = relative.pathname; + } + result.search = relative.search; + result.query = relative.query; + result.host = relative.host || ''; + result.auth = relative.auth; + result.hostname = relative.hostname || relative.host; + result.port = relative.port; + // to support http.request + if (result.pathname || result.search) { + var p = result.pathname || ''; + var s = result.search || ''; + result.path = p + s; } - - result._search = relative._search; - result._host = relative._host || ''; - result._auth = relative._auth; - result._hostname = relative._hostname || relative._host; - result._port = relative._port; result.slashes = result.slashes || relative.slashes; - result._href = ''; + result.href = result.format(); return result; } - var isSourceAbs = - (result._pathname && result._pathname.charCodeAt(0) === 0x2F /*'/'*/); - var isRelAbs = ( - relative._host || - (relative._pathname && - relative._pathname.charCodeAt(0) === 0x2F /*'/'*/)); - var mustEndAbs = (isRelAbs || - isSourceAbs || - (result._host && relative._pathname)); + var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'), + isRelAbs = ( + relative.host || + relative.pathname && relative.pathname.charAt(0) === '/' + ), + mustEndAbs = (isRelAbs || isSourceAbs || + (result.host && relative.pathname)), + removeAllDots = mustEndAbs, + srcPath = result.pathname && result.pathname.split('/') || [], + relPath = relative.pathname && relative.pathname.split('/') || [], + psychotic = result.protocol && !slashedProtocol[result.protocol]; - var removeAllDots = mustEndAbs; - - var srcPath = result._pathname && result._pathname.split('/') || []; - var relPath = relative._pathname && relative._pathname.split('/') || []; - var psychotic = result._protocol && !_slashProtocols[result._protocol]; - - // If the url is a non-slashed url, then relative + // if the url is a non-slashed url, then relative // links like ../.. should be able // to crawl up to the hostname, as well. This is strange. // result.protocol has already been set by now. // Later on, put the first path part into the host field. if (psychotic) { - result._hostname = ''; - result._port = -1; - if (result._host) { - if (srcPath[0] === '') srcPath[0] = result._host; - else srcPath.unshift(result._host); + result.hostname = ''; + result.port = null; + if (result.host) { + if (srcPath[0] === '') srcPath[0] = result.host; + else srcPath.unshift(result.host); } - result._host = ''; - if (relative._protocol) { - relative._hostname = ''; - relative._port = -1; - if (relative._host) { - if (relPath[0] === '') relPath[0] = relative._host; - else relPath.unshift(relative._host); + result.host = ''; + if (relative.protocol) { + relative.hostname = null; + relative.port = null; + if (relative.host) { + if (relPath[0] === '') relPath[0] = relative.host; + else relPath.unshift(relative.host); } - relative._host = ''; + relative.host = null; } mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); } if (isRelAbs) { // it's absolute. - result._host = relative._host ? relative._host : result._host; - result._hostname = - relative._hostname ? relative._hostname : result._hostname; - result._search = relative._search; + result.host = (relative.host || relative.host === '') ? + relative.host : result.host; + result.hostname = (relative.hostname || relative.hostname === '') ? + relative.hostname : result.hostname; + result.search = relative.search; + result.query = relative.query; srcPath = relPath; - // Fall through to the dot-handling below. + // fall through to the dot-handling below. } else if (relPath.length) { - // It's relative + // it's relative // throw away the existing file, and take the new path instead. if (!srcPath) srcPath = []; srcPath.pop(); srcPath = srcPath.concat(relPath); - result._search = relative._search; - } else if (relative._search) { - // Just pull out the search. + result.search = relative.search; + result.query = relative.query; + } else if (relative.search !== null && relative.search !== undefined) { + // just pull out the search. // like href='?foo'. // Put this after the other two cases because it simplifies the booleans if (psychotic) { - result._hostname = result._host = srcPath.shift(); - // Occasionally the auth can get stuck only in host - // this especialy happens in cases like - // url.resolveObject('mailto:local1@domain1', 'local2@domain2'). - var authInHost = result._host && result._host.indexOf('@') > 0 ? - result._host.split('@') : false; + result.hostname = result.host = srcPath.shift(); + //occationaly the auth can get stuck only in host + //this especialy happens in cases like + //url.resolveObject('mailto:local1@domain1', 'local2@domain2') + var authInHost = result.host && result.host.indexOf('@') > 0 ? + result.host.split('@') : false; if (authInHost) { - result._auth = authInHost.shift(); - result._host = result._hostname = authInHost.shift(); + result.auth = authInHost.shift(); + result.host = result.hostname = authInHost.shift(); } } - result._search = relative._search; - result._href = ''; + result.search = relative.search; + result.query = relative.query; + //to support http.request + if (result.pathname !== null || result.search !== null) { + result.path = (result.pathname ? result.pathname : '') + + (result.search ? result.search : ''); + } + result.href = result.format(); return result; } if (!srcPath.length) { - // No path at all. easy. + // no path at all. easy. // we've already handled the other stuff above. - result._pathname = null; - result._href = ''; + result.pathname = null; + //to support http.request + if (result.search) { + result.path = '/' + result.search; + } else { + result.path = null; + } + result.href = result.format(); return result; } - // If a url ENDs in . or .., then it must get a trailing slash. + // if a url ENDs in . or .., then it must get a trailing slash. // however, if it ends in anything else non-slashy, // then it must NOT get a trailing slash. var last = srcPath.slice(-1)[0]; var hasTrailingSlash = ( - (result._host || relative._host || srcPath.length > 1) && + (result.host || relative.host || srcPath.length > 1) && (last === '.' || last === '..') || last === ''); - // Strip single dots, resolve double dots to parent dir - // if the path tries to go above the root, `up` ends up > 0. + // strip single dots, resolve double dots to parent dir + // if the path tries to go above the root, `up` ends up > 0 var up = 0; for (var i = srcPath.length; i >= 0; i--) { last = srcPath[i]; if (last === '.') { - srcPath.splice(i, 1); + spliceOne(srcPath, i); } else if (last === '..') { - srcPath.splice(i, 1); + spliceOne(srcPath, i); up++; } else if (up) { - srcPath.splice(i, 1); + spliceOne(srcPath, i); up--; } } - // If the path is allowed to go above the root, restore leading ..s. + // if the path is allowed to go above the root, restore leading ..s if (!mustEndAbs && !removeAllDots) { for (; up--; up) { srcPath.unshift('..'); @@ -443,7 +653,7 @@ Url.prototype.resolveObject = function(relative) { } if (mustEndAbs && srcPath[0] !== '' && - (!srcPath[0] || srcPath[0].charCodeAt(0) !== 0x2F /*'/'*/)) { + (!srcPath[0] || srcPath[0].charAt(0) !== '/')) { srcPath.unshift(''); } @@ -452,803 +662,63 @@ Url.prototype.resolveObject = function(relative) { } var isAbsolute = srcPath[0] === '' || - (srcPath[0] && srcPath[0].charCodeAt(0) === 0x2F /*'/'*/); + (srcPath[0] && srcPath[0].charAt(0) === '/'); // put the host back if (psychotic) { - result._hostname = result._host = isAbsolute ? '' : - srcPath.length ? srcPath.shift() : ''; - // Occasionally the auth can get stuck only in host. - // This especialy happens in cases like - // url.resolveObject('mailto:local1@domain1', 'local2@domain2'). - var authInHost = result._host && result._host.indexOf('@') > 0 ? - result._host.split('@') : false; + result.hostname = result.host = isAbsolute ? '' : + srcPath.length ? srcPath.shift() : ''; + //occationaly the auth can get stuck only in host + //this especialy happens in cases like + //url.resolveObject('mailto:local1@domain1', 'local2@domain2') + var authInHost = result.host && result.host.indexOf('@') > 0 ? + result.host.split('@') : false; if (authInHost) { - result._auth = authInHost.shift(); - result._host = result._hostname = authInHost.shift(); + result.auth = authInHost.shift(); + result.host = result.hostname = authInHost.shift(); } } - mustEndAbs = mustEndAbs || (result._host && srcPath.length); + mustEndAbs = mustEndAbs || (result.host && srcPath.length); if (mustEndAbs && !isAbsolute) { srcPath.unshift(''); } - result._pathname = srcPath.length === 0 ? null : srcPath.join('/'); - result._auth = relative._auth || result._auth; - result.slashes = result.slashes || relative.slashes; - result._href = ''; - return result; -}; - - -Url.prototype._parseProtocol = function(str, start, end) { - var needsLowerCasing = false; - var protocolCharacters = _protocolCharacters; - - for (var i = start; i <= end; ++i) { - var ch = str.charCodeAt(i); - - if (ch === 0x3A /*':'*/) { - if (i - start === 0) - return start; - var protocol = str.slice(start, i); - if (needsLowerCasing) protocol = protocol.toLowerCase(); - this._protocol = protocol; - return i + 1; - } else if (protocolCharacters[ch] === 1) { - if (ch < 0x61 /*'a'*/) - needsLowerCasing = true; - } else { - return start; - } - - } - return start; -}; - -Url.prototype._parseAuth = function(str, start, end, decode) { - var auth = str.slice(start, end + 1); - if (decode) { - auth = decodeURIComponent(auth); - } - this._auth = auth; -}; - -Url.prototype._parsePort = function(str, start, end) { - // Distinguish between :0 and : (no port number at all). - var hadChars = false; - var hadValidPortTerminator = true; - var checkLeadingZeroes = false; - - for (var i = start; i <= end; ++i) { - var ch = str.charCodeAt(i); - - if (0x30 /*'0'*/ <= ch && ch <= 0x39 /*'9'*/) { - if (i === start && ch === 0x30 /*'0'*/) - checkLeadingZeroes = true; - hadChars = true; - } else { - hadValidPortTerminator = false; - if (_hostEndingCharacters[ch] === 1 || - _hostEndingCharactersNoPrependSlash[ch] === 1) { - hadValidPortTerminator = true; - } else { - this._port = -2; - } - break; - } - - } - - if (!hadChars || !hadValidPortTerminator) - return 0; - - var portEnd = i; - var port = str.slice(start, portEnd); - - if (checkLeadingZeroes) { - var hadNonZero = false; - for (var i = 0; i < port.length; ++i) { - if (port.charCodeAt(i) !== 0x30 /*'0'*/) { - hadNonZero = true; - break; - } - } - - if (hadNonZero) - port = -1; - else - port = '0'; - } - this._port = port; - return portEnd - start; -}; - -Url.prototype._hasValidPort = function() { - return typeof this._port === 'string'; -}; - -Url.prototype._parseHost = function(str, - trimmedStart, - start, - end, - slashesDenoteHost) { - var hostEndingCharacters = _hostEndingCharacters; - var first = str.charCodeAt(start); - var second = str.charCodeAt(start + 1); - if ((first === 0x2F /*'/'*/ || first === 0x5C /*'\'*/) && - (second === 0x2F /*'/'*/ || second === 0x5C /*'\'*/)) { - this.slashes = true; - - // The string starts with // or \\. - if (start === trimmedStart) { - // The string is just '//' or '\\'. - if (end - start === 1) return start; - // If slashes do not denote host and there is no auth, - // there is no host when the string starts with // or \\. - var hasAuth = - containsCharacter(str, - 0x40 /*'@'*/, - trimmedStart + 2, - hostEndingCharacters); - if (!hasAuth && !slashesDenoteHost) { - this.slashes = null; - return start; - } - } - // There is a host that starts after the //. - start += 2; - } - // If there is no slashes, there is no hostname if - // 1. there was no protocol at all. - else if (!this._protocol || - // 2. there was a protocol that requires slashes - // e.g. in 'http:asd' 'asd' is not a hostname. - _slashProtocols[this._protocol] - ) { - return start; - } - - var needsLowerCasing = false; - var idna = false; - var hostNameStart = start; - var hostNameEnd = end; - var lastCh = -1; - var portLength = 0; - var charsAfterDot = 0; - var authNeedsDecoding = false; - - var j = -1; - - // Find the last occurrence of an @-sign until hostending character is met - // also mark if decoding is needed for the auth portion. - for (var i = start; i <= end; ++i) { - var ch = str.charCodeAt(i); - if (ch === 0x40 /*'@'*/) - j = i; - else if (ch === 0x25 /*'%'*/) - authNeedsDecoding = true; - else if (hostEndingCharacters[ch] === 1) - break; - } - - // @-sign was found at index j, everything to the left from it - // is auth part. - if (j > -1) { - this._parseAuth(str, start, j - 1, authNeedsDecoding); - // Hostname starts after the last @-sign- - start = hostNameStart = j + 1; - } - - // Host name is starting with a [. - if (str.charCodeAt(start) === 0x5B /*'['*/) { - for (var i = start + 1; i <= end; ++i) { - var ch = str.charCodeAt(i); - - // Assume valid IP6 is between the brackets. - if (ch === 0x5D /*']'*/) { - if (str.charCodeAt(i + 1) === 0x3A /*':'*/) - portLength = this._parsePort(str, i + 2, end) + 1; - - var hostname = '[' + str.slice(start + 1, i).toLowerCase() + ']'; - this._hostname = hostname; - this._host = - this._hasValidPort() ? hostname + ':' + this._port : hostname; - this._pathname = '/'; - return i + portLength + 1; - } - } - // Empty hostname, [ starts a path. - return start; - } - var hostEndingCharactersNoPrependSlash = _hostEndingCharactersNoPrependSlash; - for (var i = start; i <= end; ++i) { - if (charsAfterDot > 62) { - this._hostname = this._host = str.slice(start, i); - return i; - } - var ch = str.charCodeAt(i); - - if (ch === 0x3A /*':'*/) { - portLength = this._parsePort(str, i + 1, end) + 1; - hostNameEnd = i - 1; - break; - } else if (ch < 0x61 /*'a'*/) { - if (ch === 0x2E /*'.'*/) { - // TODO(petkaantonov) This error is originally ignored: - // if (lastCh === 0x2E /*'.'*/ || lastCh === -1) { - // this.hostname = this.host = ''; - // return start; - // } - charsAfterDot = -1; - } else if (0x41 /*'A'*/ <= ch && ch <= 0x5A /*'Z'*/) { - needsLowerCasing = true; - } - // Valid characters other than ASCII letters -, _, +, 0-9. - else if (!(ch === 0x2D /*'-'*/ || - ch === 0x5F /*'_'*/ || - ch === 0x2B /*'+'*/ || - (0x30 /*'0'*/ <= ch && ch <= 0x39 /*'9'*/))) { - if (hostEndingCharacters[ch] === 0 && - hostEndingCharactersNoPrependSlash[ch] === 0) - this._prependSlash = true; - hostNameEnd = i - 1; - break; - } - } else if (ch >= 0x7B /*'{'*/) { - if (ch <= 0x7E /*'~'*/) { - if (hostEndingCharactersNoPrependSlash[ch] === 0) { - this._prependSlash = true; - } - hostNameEnd = i - 1; - break; - } - idna = true; - needsLowerCasing = true; - } - lastCh = ch; - charsAfterDot++; - } - - // TODO(petkaantonov) This error is originally ignored: - // if (lastCh === 0x2E /*'.'*/) - // hostNameEnd-- - - if (hostNameEnd + 1 !== start && - hostNameEnd - hostNameStart <= 256) { - var hostname = str.slice(hostNameStart, hostNameEnd + 1); - if (needsLowerCasing) - hostname = hostname.toLowerCase(); - if (idna) - hostname = punycode.toASCII(hostname); - - this._hostname = hostname; - this._host = - this._hasValidPort() ? hostname + ':' + this._port : hostname; - } - - return hostNameEnd + 1 + portLength; - -}; - -Url.prototype._copyPropsTo = function(target, noProtocol) { - if (!noProtocol) { - target._protocol = this._protocol; - } - // Forces getter recalculation. - target._href = null; - target._host = this._host; - target._hostname = this._hostname; - target._pathname = this._pathname; - target._search = this._search; - target._parsesQueryStrings = this._parsesQueryStrings; - target._prependSlash = this._prependSlash; - target._port = this._port; - target._auth = this._auth; - target.slashes = this.slashes; - target._query = this._query; - target._hash = this._hash; -}; - -Url.prototype._clone = function() { - var ret = new Url(); - ret._protocol = this._protocol; - ret._href = this._href; - ret._port = this._port; - ret._prependSlash = this._prependSlash; - ret._auth = this._auth; - ret._hostname = this._hostname; - ret._host = this._host; - ret._hash = this._hash; - ret._search = this._search; - ret._pathname = this._pathname; - ret._parsesQueryStrings = this._parsesQueryStrings; - ret._query = this._query; - ret.slashes = this.slashes; - return ret; -}; - -Url.prototype.toJSON = function() { - var ret = { - href: this.href, - pathname: this.pathname, - path: this.path, - protocol: this.protocol, - query: this.query, - port: this.port, - auth: this.auth, - hash: this.hash, - host: this.host, - hostname: this.hostname, - search: this.search, - slashes: this.slashes - }; - var keys = Object.keys(this); - for (var i = 0; i < keys.length; ++i) { - var key = keys[i]; - if (noSerializePattern.test(key)) continue; - ret[key] = this[key]; - - } - return ret; -}; - -Url.prototype._parsePath = function(str, start, end) { - var pathStart = start; - var pathEnd = end; - var escape = false; - var autoEscapeCharacters = _autoEscapeCharacters; - var prePath = this._port === -2 ? '/:' : ''; - - for (var i = start; i <= end; ++i) { - var ch = str.charCodeAt(i); - - if (ch === 0x23 /*'#'*/) { - this._parseHash(str, i, end); - pathEnd = i - 1; - break; - } else if (ch === 0x3F /*'?'*/) { - this._parseQuery(str, i, end); - pathEnd = i - 1; - break; - } else if (!escape && autoEscapeCharacters[ch] === 1) { - escape = true; - } - } - - if (pathStart > pathEnd) { - this._pathname = prePath === '' ? '/' : prePath; - return; - } - - var path; - if (escape) { - path = getComponentEscaped(str, pathStart, pathEnd, false); + if (!srcPath.length) { + result.pathname = null; + result.path = null; } else { - path = str.slice(pathStart, pathEnd + 1); - } - - this._pathname = prePath === '' ? - (this._prependSlash ? '/' + path : path) : prePath + path; -}; - -Url.prototype._parseQuery = function(str, start, end) { - var queryStart = start; - var queryEnd = end; - var escape = false; - var autoEscapeCharacters = _autoEscapeCharacters; - - for (var i = start; i <= end; ++i) { - var ch = str.charCodeAt(i); - - if (ch === 0x23 /*'#'*/) { - this._parseHash(str, i, end); - queryEnd = i - 1; - break; - } else if (!escape && autoEscapeCharacters[ch] === 1) - escape = true; + result.pathname = srcPath.join('/'); } - if (queryStart > queryEnd) { - this._search = ''; - return; + //to support request.http + if (result.pathname !== null || result.search !== null) { + result.path = (result.pathname ? result.pathname : '') + + (result.search ? result.search : ''); } - - var query = escape ? - getComponentEscaped(str, queryStart, queryEnd, true) : - str.slice(queryStart, queryEnd + 1); - - this._search = query; -}; - -Url.prototype._parseHash = function(str, start, end) { - if (start > end) { - this._hash = ''; - return; - } - this._hash = getComponentEscaped(str, start, end, true); + result.auth = relative.auth || result.auth; + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; }; -Object.defineProperty(Url.prototype, 'port', { - get: function() { - if (this._hasValidPort()) - return this._port; - - return null; - }, - set: function(v) { - if (v === null) { - this._port = -1; - if (this._host) - this._host = null; - } else { - this._port = toPortNumber(v); - - if (this._hostname) - this._host = this._hostname + ':' + this._port; - else - this._host = ':' + this._port; - } - this._href = ''; - }, - enumerable: true, - configurable: true -}); - -Object.defineProperty(Url.prototype, 'query', { - get: function() { - var query = this._query; - if (query != null) - return query; - - var search = this._search; - - if (search) { - if (search.charCodeAt(0) === 0x3F /*'?'*/) - search = search.slice(1); - if (search !== '') { - this._query = search; - return search; - } - } - return search; - }, - set: function(v) { - if (typeof v === 'string') { - if (v !== '') this.search = '?' + v; - this._query = v; - } else if (v !== null && typeof v === 'object') { - var string = querystring.stringify(v); - this._search = string !== '' ? '?' + querystring.stringify(v) : ''; - this._query = v; - } else { - this._query = this._search = null; - } - this._href = ''; - }, - enumerable: true, - configurable: true -}); - -Object.defineProperty(Url.prototype, 'path', { - get: function() { - var p = this.pathname || ''; - var s = this.search || ''; - if (p || s) - return p + s; - return (p == null && s) ? ('/' + s) : null; - }, - set: function(v) { - if (v === null) { - this._pathname = this._search = null; - return; - } - var path = '' + v; - var matches = path.match(/([^\?]*)(\?.*)$/); - - if (matches) { - this.pathname = matches[1]; - this.search = matches[2]; - } else { - this.pathname = path; - this.search = null; - } - this._href = ''; - }, - enumerable: true, - configurable: true -}); - -Object.defineProperty(Url.prototype, 'protocol', { - get: function() { - var proto = this._protocol; - - if (typeof proto === 'string' && proto.length > 0) { - if (proto.charCodeAt(proto.length - 1) !== 0x3A/*':'*/) - return proto + ':'; - return proto; - } - return proto; - }, - set: function(v) { - if (v === null) { - this._protocol = null; - } else { - var proto = '' + v; - if (proto.length > 0) { - if (proto.charCodeAt(proto.length - 1) !== 0x3A /*':'*/) - proto = proto + ':'; - this._parseProtocol(proto, 0, proto.length - 1); - this._href = ''; - } - } - }, - enumerable: true, - configurable: true -}); - -Object.defineProperty(Url.prototype, 'href', { - get: function() { - var href = this._href; - if (!href) - href = this._href = this.format(); - return href; - }, - set: function(v) { - this._href = ''; - var parsesQueryStrings = this._parsesQueryStrings; - // Reset properties. - Url.call(this); - if (v !== null && v !== '') - this.parse('' + v, parsesQueryStrings, false); - }, - enumerable: true, - configurable: true -}); - -Object.defineProperty(Url.prototype, 'auth', { - get: function() { - return this._auth; - }, - set: function(v) { - this._auth = v === null ? null : '' + v; - this._href = ''; - }, - enumerable: true, - configurable: true -}); - -// host = hostname + port. -Object.defineProperty(Url.prototype, 'host', { - get: function() { - return this._host; - }, - set: function(v) { - if (v === null) { - this._port = -1; - this._hostname = this._host = null; - } else { - var host = '' + v; - var matches = host.match(/(.*):(.+)$/); - - if (matches) { - this._hostname = encodeURIComponent(matches[1]); - this._port = toPortNumber(matches[2]); - this._host = this._hostname + ':' + this._port; - } else { - this._port = -1; - this._hostname = this._host = encodeURIComponent(host); - } - this._href = ''; - } - }, - enumerable: true, - configurable: true -}); - -Object.defineProperty(Url.prototype, 'hostname', { - get: function() { - return this._hostname; - }, - set: function(v) { - if (v === null) { - this._hostname = null; - - if (this._hasValidPort()) - this._host = ':' + this._port; - else - this._host = null; - } else { - var hostname = encodeURIComponent('' + v); - this._hostname = hostname; - - if (this._hasValidPort()) - this._host = hostname + ':' + this._port; - else - this._host = hostname; - - this._href = ''; - } - }, - enumerable: true, - configurable: true -}); - -Object.defineProperty(Url.prototype, 'hash', { - get: function() { - return this._hash; - }, - set: function(v) { - if (v === null) { - this._hash = null; - } else { - var hash = '' + v; - if (hash.charCodeAt(0) !== 0x23 /*'#'*/) { - hash = '#' + hash; - } - this._hash = hash; - this._href = ''; - } - }, - enumerable: true, - configurable: true -}); - -Object.defineProperty(Url.prototype, 'search', { - get: function() { - return this._search; - }, - set: function(v) { - if (v === null) { - this._search = this._query = null; - } else { - var search = escapeSearch('' + v); - - if (search.charCodeAt(0) !== 0x3F /*'?'*/) - search = '?' + search; - - this._search = search; - - if (this._parsesQueryStrings) { - this.query = querystring.parse(search.slice(1)); - } - this._href = ''; - } - }, - enumerable: true, - configurable: true -}); - -Object.defineProperty(Url.prototype, 'pathname', { - get: function() { - return this._pathname; - }, - set: function(v) { - if (v === null) { - this._pathname = null; - } else { - var pathname = escapePathName('' + v).replace(/\\/g, '/'); - - if (this.hostname && - _slashProtocols[this._protocol] && - pathname.charCodeAt(0) !== 0x2F /*'/'*/) { - pathname = '/' + pathname; - } - - this._pathname = pathname; - this._href = ''; +Url.prototype.parseHost = function() { + var host = this.host; + var port = portPattern.exec(host); + if (port) { + port = port[0]; + if (port !== ':') { + this.port = port.substr(1); } - }, - enumerable: true, - configurable: true -}); - -// Search `char1` (integer code for a character) in `string` -// starting from `fromIndex` and ending at `string.length - 1` -// or when a stop character is found. -function containsCharacter(string, char1, fromIndex, stopCharacterTable) { - var len = string.length; - for (var i = fromIndex; i < len; ++i) { - var ch = string.charCodeAt(i); - - if (ch === char1) - return true; - else if (stopCharacterTable[ch] === 1) - return false; - } - return false; -} - -// See if `char1` or `char2` (integer codes for characters) -// is contained in `string`. -function containsCharacter2(string, char1, char2) { - for (var i = 0, len = string.length; i < len; ++i) { - var ch = string.charCodeAt(i); - if (ch === char1 || ch === char2) - return true; + host = host.substr(0, host.length - port.length); } - return false; -} - -// Makes an array of 128 uint8's which represent boolean values. -// Spec is an array of ascii code points or ascii code point ranges -// ranges are expressed as [start, end]. - -// For example, to create a table with the characters -// 0x30-0x39 (decimals '0' - '9') and -// 0x7A (lowercaseletter 'z') as `true`: - -// var a = makeAsciiTable([[0x30, 0x39], 0x7A]); -// a[0x30]; //1 -// a[0x15]; //0 -// a[0x35]; //1 -function makeAsciiTable(spec) { - var ret = new Uint8Array(128); - spec.forEach(function(item) { - if (typeof item === 'number') { - ret[item] = 1; - } else { - var start = item[0]; - var end = item[1]; - for (var j = start; j <= end; ++j) { - ret[j] = 1; - } - } - }); - - return ret; -} - -function escapePathName(pathname) { - if (!containsCharacter2(pathname, 0x23 /*'#'*/, 0x3F /*'?'*/)) - return pathname; - - return pathname.replace(/[?#]/g, function(match) { - return encodeURIComponent(match); - }); -} - -function escapeSearch(search) { - if (!containsCharacter2(search, 0x23 /*'#'*/, -1)) - return search; - - return search.replace(/#/g, function(match) { - return encodeURIComponent(match); - }); -} - -function getComponentEscaped(str, start, end, isAfterQuery) { - var cur = start; - var i = start; - var ret = ''; - var autoEscapeMap = isAfterQuery ? _afterQueryAutoEscapeMap : _autoEscapeMap; - for (; i <= end; ++i) { - var ch = str.charCodeAt(i); - var escaped = autoEscapeMap[ch]; - - if (escaped !== '' && escaped !== undefined) { - if (cur < i) ret += str.slice(cur, i); - ret += escaped; - cur = i + 1; - } - } - if (cur < i + 1) ret += str.slice(cur, i); - return ret; -} - -function toPortNumber(v) { - v = parseInt(v); - if (!Number.isFinite(v)) - v = 0; + if (host) this.hostname = host; +}; - v = Math.max(0, v) % 0x10000; - return '' + v; +// About 1.5x faster than the two-arg version of Array#splice(). +function spliceOne(list, index) { + for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) + list[i] = list[k]; + list.pop(); } - -// Optimize back from normalized object caused by non-identifier keys. -function FakeConstructor() {} -FakeConstructor.prototype = _slashProtocols; -/*jshint nonew: false */ -new FakeConstructor(); diff --git a/lib/util.js b/lib/util.js index c4a0adc41f..9293587d58 100644 --- a/lib/util.js +++ b/lib/util.js @@ -2,7 +2,6 @@ const uv = process.binding('uv'); const Debug = require('vm').runInDebugContext('Debug'); -const Url = require('url').Url; const formatRegExp = /%[sdj%]/g; exports.format = function(f) { @@ -744,8 +743,6 @@ exports.inherits = function(ctor, superCtor) { exports._extend = function(origin, add) { // Don't do anything if add isn't an object if (add === null || typeof add !== 'object') return origin; - // For compatibility with Url objects - if (add instanceof Url) add = add.toJSON(); var keys = Object.keys(add); var i = keys.length; diff --git a/test/parallel/test-url-accessors.js b/test/parallel/test-url-accessors.js deleted file mode 100644 index 387c149d23..0000000000 --- a/test/parallel/test-url-accessors.js +++ /dev/null @@ -1,378 +0,0 @@ -'use strict'; - -const common = require('../common'); -const assert = require('assert'); -const util = require('util'); -const url = require('url'); - -// url: The url object to test (if string, it's parsed to url) -// set: Object where the fields are the property values to set in the url object -// test: Object where the fields are the property values to test in the url object -// Example: -// -// Tests that the host property reflects the new port that was set on the url object -// { -// url: 'http://www.google.com', -// set: {port: 443}, -// test: {host: 'www.google.com:443'} -// } - -const accessorTests = [{ - // Setting href to null nullifies all properties. - url: 'http://user:password@www.google.com:80/path?query#hash', - set: {href: null}, - test: { - protocol: null, - port: null, - query: null, - auth: null, - hostname: null, - host: null, - pathname: null, - hash: null, - search: null, - href: '' - } -}, { - // Resetting href doesn't forget query string parsing preference - url: url.parse('http://www.google.com?key=value', true), - set: {href: 'http://www.yahoo.com?key2=value2'}, - test: { - query: {key2: 'value2'} - } -}, { - // Setting href to non-null non-string coerces to string - url: 'google', - set: {href: undefined}, - test: { - path: 'undefined', - href: 'undefined' - } -}, { - // Setting port is reflected in host - url: 'http://www.google.com', - set: {port: 443}, - test: { - host: 'www.google.com:443', - hostname: 'www.google.com', - port: '443' - } -}, { - // Port is treated as an unsigned 16-bit integer - url: 'http://www.google.com', - set: {port: 100000}, - test: { - host: 'www.google.com:34464', - hostname: 'www.google.com', - port: '34464' - } -}, { - // Port is parsed numerically - url: 'http://www.google.com', - set: {port: '100000'}, - test: { - host: 'www.google.com:34464', - hostname: 'www.google.com', - port: '34464' - } -}, { - // Invalid port values become 0 - url: 'http://www.google.com', - set: {port: NaN}, - test: { - host: 'www.google.com:0', - hostname: 'www.google.com', - port: '0' - } -}, { - // Auth special characters are urlencoded - url: 'http://www.google.com', - set: {auth: 'weird@'}, - test: { - auth: 'weird@', - href: 'http://weird%40@www.google.com/' - } -}, { - // Auth password separator is not urlencoded - url: 'http://www.google.com', - set: {auth: 'weird@:password:'}, - test: { - auth: 'weird@:password:', - href: 'http://weird%40:password%3A@www.google.com/' - } -}, { - - // Host is reflected in hostname and port - url: 'http://www.google.com', - set: {host: 'www.yahoo.com:443'}, - test: { - hostname: 'www.yahoo.com', - host: 'www.yahoo.com:443', - port: '443', - } -}, { - - // Port in host has port setter behavior - url: 'http://www.google.com', - set: {host: 'www.yahoo.com:100000'}, - test: { - hostname: 'www.yahoo.com', - host: 'www.yahoo.com:34464', - port: '34464', - } -}, { - - // Hostname in host has hostname setter behavior - url: 'http://www.google.com', - set: {host: 'www.yahoo .com'}, - test: { - hostname: 'www.yahoo%20.com', - host: 'www.yahoo%20.com', - port: null, - href: 'http://www.yahoo%20.com/' - } -}, { - - // Nullifying host nullifies port and hostname - url: 'http://www.google.com:443', - set: {host: null}, - test: { - hostname: null, - host: null, - port: null - } -}, { - - // Question mark is added to search - url: 'http://www.google.com', - set: {search: 'key=value'}, - test: { - search: '?key=value' - } -}, { - - // It's impossible to start a hash in search - url: 'http://www.google.com', - set: {search: 'key=value#hash'}, - test: { - search: '?key=value%23hash' - } -}, { - - // Query is reflected in urls that parse query strings - url: url.parse('http://www.google.com', true), - set: {search: 'key=value'}, - test: { - search: '?key=value', - query: {key: 'value'} - } -}, { - - // Search is reflected in path - url: 'http://www.google.com', - set: {search: 'key=value'}, - test: { - path: '/?key=value' - } -}, { - - // Empty search is ok - url: 'http://www.google.com', - set: {search: ''}, - test: { - search: '?', - path: '/?' - } -}, { - - // Nullifying search is ok - url: 'http://www.google.com?key=value', - set: {search: null}, - test: { - search: null, - path: '/' - } -}, { - - // # is added to hash if it's missing - url: 'http://www.google.com', - set: {hash: 'myhash'}, - test: { - hash: '#myhash' - } -}, { - - // Empty hash is ok - url: 'http://www.google.com', - set: {hash: ''}, - test: { - hash: '#' - } -}, { - - // Nullifying hash is ok - url: 'http://www.google.com#hash', - set: {hash: null}, - test: { - hash: null - } -}, { - - // / is added to pathname - url: 'http://www.google.com', - set: {pathname: 'path'}, - test: { - pathname: '/path' - } -}, { - - // It's impossible to start query in pathname - url: 'http://www.google.com', - set: {pathname: 'path?key=value'}, - test: { - pathname: '/path%3Fkey=value' - } -}, { - - // It's impossible to start hash in pathname - url: 'http://www.google.com', - set: {pathname: 'path#hash'}, - test: { - pathname: '/path%23hash' - } -}, { - // Backslashes are treated as slashes in pathnames - url: 'http://www.google.com', - set: {pathname: '\\path\\name'}, - test: { - pathname: '/path/name' - } -}, { - // Empty path is ok - url: 'http://www.google.com', - set: {pathname: ''}, - test: { - pathname: '/' - } -}, { - // Null path is ok - url: 'http://www.google.com/path', - set: {pathname: null}, - test: { - pathname: null - } -}, { - // Pathname is reflected in path - url: 'http://www.google.com/path', - set: {pathname: '/path2'}, - test: { - path: '/path2' - } -}, { - // Protocol doesn't require colon - url: 'http://www.google.com/path', - set: {protocol: 'mailto'}, - test: { - protocol: 'mailto:' - } -}, { - // Invalid protocol doesn't change protocol - url: 'http://www.google.com/path', - set: {protocol: 'mailto '}, - test: { - protocol: 'http:' - } -}, { - // Nullifying protocol is ok - url: 'http://www.google.com/path', - set: {protocol: null}, - test: { - protocol: null - } -}, { - // Empty protocol is invalid - url: 'http://www.google.com/path', - set: {protocol: ''}, - test: { - protocol: 'http:' - } -}, { - // Set query to an object - url: 'http://www.google.com/path', - set: {query: {key: 'value'}}, - test: { - search: '?key=value', - href: 'http://www.google.com/path?key=value' - } -}, { - // Set query to null is ok - url: 'http://www.google.com/path?key=value', - set: {query: null}, - test: { - search: null, - query: null, - href: 'http://www.google.com/path' - } -}, { - // path is reflected in search and pathname - url: 'http://www.google.com/path?key=value', - set: {path: 'path2?key2=value2'}, - test: { - pathname: '/path2', - search: '?key2=value2', - href: 'http://www.google.com/path2?key2=value2' - } -}, { - // path is reflected in search and pathname 2 - url: 'http://www.google.com/path?key=value', - set: {path: '?key2=value2'}, - test: { - pathname: '/', - search: '?key2=value2', - href: 'http://www.google.com/?key2=value2' - } -}, { - // path is reflected in search and pathname 3 - url: 'http://www.google.com/path?key=value', - set: {path: 'path2'}, - test: { - pathname: '/path2', - search: null, - href: 'http://www.google.com/path2' - } -}, { - // path is reflected in search and pathname 4 - url: 'http://www.google.com/path?key=value', - set: {path: null}, - test: { - pathname: null, - search: null, - href: 'http://www.google.com' - } -} - -]; - - -accessorTests.forEach(function(test) { - var urlObject = test.url; - - if (typeof urlObject === 'string') - urlObject = url.parse(urlObject); - - Object.keys(test.set).forEach(function(key) { - urlObject[key] = test.set[key]; - }); - - Object.keys(test.test).forEach(function(key) { - const actual = urlObject[key]; - const expected = test.test[key]; - - const message = `Expecting ${key}=${util.inspect(expected)} ` + - `but got ${key}=${util.inspect(actual)}. ` + - `URL object: ${JSON.stringify(urlObject, null, 2)} ` + - `\nTest object: ${JSON.stringify(test, null, 2)}` - - assert.deepStrictEqual(actual, expected, message); - }); -}); diff --git a/test/parallel/test-url.js b/test/parallel/test-url.js index acbdfa79eb..121d6caaf6 100644 --- a/test/parallel/test-url.js +++ b/test/parallel/test-url.js @@ -600,7 +600,7 @@ var parseTests = { 'protocol': 'coap:', 'slashes': true, 'host': '[fedc:ba98:7654:3210:fedc:ba98:7654:3210]', - 'hostname': '[fedc:ba98:7654:3210:fedc:ba98:7654:3210]', + 'hostname': 'fedc:ba98:7654:3210:fedc:ba98:7654:3210', 'href': 'coap://[fedc:ba98:7654:3210:fedc:ba98:7654:3210]/', 'pathname': '/', 'path': '/' @@ -611,7 +611,7 @@ var parseTests = { 'slashes': true, 'host': '[1080:0:0:0:8:800:200c:417a]:61616', 'port': '61616', - 'hostname': '[1080:0:0:0:8:800:200c:417a]', + 'hostname': '1080:0:0:0:8:800:200c:417a', 'href': 'coap://[1080:0:0:0:8:800:200c:417a]:61616/', 'pathname': '/', 'path': '/' @@ -623,7 +623,7 @@ var parseTests = { 'auth': 'user:password', 'host': '[3ffe:2a00:100:7031::1]:8080', 'port': '8080', - 'hostname': '[3ffe:2a00:100:7031::1]', + 'hostname': '3ffe:2a00:100:7031::1', 'href': 'http://user:password@[3ffe:2a00:100:7031::1]:8080/', 'pathname': '/', 'path': '/' @@ -635,7 +635,7 @@ var parseTests = { 'auth': 'u:p', 'host': '[::192.9.5.5]:61616', 'port': '61616', - 'hostname': '[::192.9.5.5]', + 'hostname': '::192.9.5.5', 'href': 'coap://u:p@[::192.9.5.5]:61616/.well-known/r?n=Temperature', 'search': '?n=Temperature', 'query': 'n=Temperature', @@ -691,7 +691,7 @@ var parseTests = { 'protocol': 'http:', 'slashes': true, 'host': '[fe80::1]', - 'hostname': '[fe80::1]', + 'hostname': 'fe80::1', 'href': 'http://[fe80::1]/a/b?a=b#abc', 'search': '?a=b', 'query': 'a=b', @@ -857,8 +857,8 @@ var parseTests = { }; for (var u in parseTests) { - var actual = url.parse(u).toJSON(), - spaced = url.parse(' \t ' + u + '\n\t').toJSON(); + var actual = url.parse(u), + spaced = url.parse(' \t ' + u + '\n\t'); expected = parseTests[u]; Object.keys(actual).forEach(function (i) { @@ -929,7 +929,7 @@ var parseTestsWithQueryString = { } }; for (var u in parseTestsWithQueryString) { - var actual = url.parse(u, true).toJSON(); + var actual = url.parse(u, true); var expected = parseTestsWithQueryString[u]; for (var i in actual) { if (actual[i] === null && expected[i] === undefined) { @@ -1074,7 +1074,7 @@ var formatTests = { 'href': 'coap:u:p@[::1]:61616/.well-known/r?n=Temperature', 'protocol': 'coap:', 'auth': 'u:p', - 'hostname': '[::1]', + 'hostname': '::1', 'port': '61616', 'pathname': '/.well-known/r', 'search': 'n=Temperature' @@ -1576,1349 +1576,3 @@ for (var i = 0; i < throws.length; i++) { }; assert(url.format('') === ''); assert(url.format({}) === ''); - -// base = base url to use. -// input = relative url that will be resolved in relation to base -// nodejs = result given by node.js 0.12, usually incorrect so not optimal -// override = custom override that differs from node.js but is more correct -var whatwgTests = [ - { - "base": "http://example.org/foo/bar", - "input": "http://example\t.\norg", - "nodejs": "http://example/%09.%0Aorg" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://user:pass@foo:21/bar;par?b#c", - "nodejs": "http://user:pass@foo:21/bar;par?b#c" - }, - { - "base": "http://example.org/foo/bar", - "input": "http:foo.com", - "nodejs": "http://example.org/foo/foo.com" - }, - { - "base": "http://example.org/foo/bar", - "input": "\t :foo.com \n", - "nodejs": "http://example.org/foo/:foo.com" - }, - { - "base": "http://example.org/foo/bar", - "input": " foo.com ", - "nodejs": "http://example.org/foo/foo.com" - }, - { - "base": "http://example.org/foo/bar", - "input": "a:\t foo.com", - "nodejs": "a:%09%20foo.com" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://f:21/ b ? d # e ", - "nodejs": "http://f:21/%20b%20?%20d%20#%20e" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://f:/c", - "nodejs": "http://f/c" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://f:0/c", - "nodejs": "http://f:0/c" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://f:00000000000000/c", - "nodejs": "http://f:00000000000000/c", - "override": "http://f:0/c" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://f:00000000000000000000080/c", - "nodejs": "http://f:00000000000000000000080/c", - "override": "http://f/c" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://f:b/c", - "nodejs": "http://f/:b/c" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://f: /c", - "nodejs": "http://f/%20/c" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://f:\n/c", - "nodejs": "http://f/%0A/c" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://f:fifty-two/c", - "nodejs": "http://f/:fifty-two/c" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://f:999999/c", - "nodejs": "http://f:999999/c" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://f: 21 / b ? d # e ", - "nodejs": "http://f/%2021%20/%20b%20?%20d%20#%20e" - }, - { - "base": "http://example.org/foo/bar", - "input": "", - "nodejs": "http://example.org/foo/bar" - }, - { - "base": "http://example.org/foo/bar", - "input": " \t", - "nodejs": "http://example.org/foo/bar" - }, - { - "base": "http://example.org/foo/bar", - "input": ":foo.com/", - "nodejs": "http://example.org/foo/:foo.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": ":foo.com\\", - "nodejs": "http://example.org/foo/:foo.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": ":", - "nodejs": "http://example.org/foo/:" - }, - { - "base": "http://example.org/foo/bar", - "input": ":a", - "nodejs": "http://example.org/foo/:a" - }, - { - "base": "http://example.org/foo/bar", - "input": ":/", - "nodejs": "http://example.org/foo/:/" - }, - { - "base": "http://example.org/foo/bar", - "input": ":\\", - "nodejs": "http://example.org/foo/:/" - }, - { - "base": "http://example.org/foo/bar", - "input": ":#", - "nodejs": "http://example.org/foo/:#" - }, - { - "base": "http://example.org/foo/bar", - "input": "#", - "nodejs": "http://example.org/foo/bar#" - }, - { - "base": "http://example.org/foo/bar", - "input": "#/", - "nodejs": "http://example.org/foo/bar#/" - }, - { - "base": "http://example.org/foo/bar", - "input": "#\\", - "nodejs": "http://example.org/foo/bar#%5C" - }, - { - "base": "http://example.org/foo/bar", - "input": "#;?", - "nodejs": "http://example.org/foo/bar#;?" - }, - { - "base": "http://example.org/foo/bar", - "input": "?", - "nodejs": "http://example.org/foo/bar?" - }, - { - "base": "http://example.org/foo/bar", - "input": "/", - "nodejs": "http://example.org/" - }, - { - "base": "http://example.org/foo/bar", - "input": ":23", - "nodejs": "http://example.org/foo/:23" - }, - { - "base": "http://example.org/foo/bar", - "input": "/:23", - "nodejs": "http://example.org/:23" - }, - { - "base": "http://example.org/foo/bar", - "input": "::", - "nodejs": "http://example.org/foo/::" - }, - { - "base": "http://example.org/foo/bar", - "input": "::23", - "nodejs": "http://example.org/foo/::23" - }, - { - "base": "http://example.org/foo/bar", - "input": "foo://", - "nodejs": "foo://" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://a:b@c:29/d", - "nodejs": "http://a:b@c:29/d" - }, - { - "base": "http://example.org/foo/bar", - "input": "http::@c:29", - "nodejs": "http://example.org/foo/:@c:29" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://&a:foo(b]c@d:2/", - "nodejs": "http://%26a:foo(b%5Dc@d:2/" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://::@c@d:2", - "nodejs": "http://:%3A%40c@d:2/" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://foo.com:b@d/", - "nodejs": "http://foo.com:b@d/" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://foo.com/\\@", - "nodejs": "http://foo.com//@" - }, - { - "base": "http://example.org/foo/bar", - "input": "http:\\\\foo.com\\", - "nodejs": "http://foo.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "http:\\\\a\\b:c\\d@foo.com\\", - "nodejs": "http://a/b:c/d@foo.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "foo:/", - "nodejs": "foo:/" - }, - { - "base": "http://example.org/foo/bar", - "input": "foo:/bar.com/", - "nodejs": "foo:/bar.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "foo://///////", - "nodejs": "foo://///////" - }, - { - "base": "http://example.org/foo/bar", - "input": "foo://///////bar.com/", - "nodejs": "foo://///////bar.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "foo:////://///", - "nodejs": "foo:////://///" - }, - { - "base": "http://example.org/foo/bar", - "input": "c:/foo", - "nodejs": "c:/foo" - }, - { - "base": "http://example.org/foo/bar", - "input": "//foo/bar", - "nodejs": "http://foo/bar" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://foo/path;a??e#f#g", - "nodejs": "http://foo/path;a??e#f#g" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://foo/abcd?efgh?ijkl", - "nodejs": "http://foo/abcd?efgh?ijkl" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://foo/abcd#foo?bar", - "nodejs": "http://foo/abcd#foo?bar" - }, - { - "base": "http://example.org/foo/bar", - "input": "[61:24:74]:98", - "nodejs": "http://example.org/foo/[61:24:74]:98" - }, - { - "base": "http://example.org/foo/bar", - "input": "http:[61:27]/:foo", - "nodejs": "http://example.org/foo/[61:27]/:foo" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://[1::2]:3:4", - "nodejs": "http://:4/[1::2]:3", - "override": "http://[1::2]/:3:4" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://2001::1", - "nodejs": "http://2001:1/:", - "override": "http://2001/::1" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://2001::1]", - "nodejs": "http://2001/::1]" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://2001::1]:80", - "nodejs": "http://2001:80/::1]", - "override": "http://2001/::1]:80" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://[2001::1]", - "nodejs": "http://[2001::1]/" - }, - { - "base": "http://example.org/foo/bar", - "input": "http://[2001::1]:80", - "nodejs": "http://[2001::1]:80/" - }, - { - "base": "http://example.org/foo/bar", - "input": "http:/example.com/", - "nodejs": "http://example.org/example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "ftp:/example.com/", - "nodejs": "ftp://example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "https:/example.com/", - "nodejs": "https://example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "madeupscheme:/example.com/", - "nodejs": "madeupscheme:/example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "file:/example.com/", - "nodejs": "file://example.com/", - "override": "file:///example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "ftps:/example.com/", - "nodejs": "ftps:/example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "gopher:/example.com/", - "nodejs": "gopher://example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "ws:/example.com/", - "nodejs": "ws:/example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "wss:/example.com/", - "nodejs": "wss:/example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "data:/example.com/", - "nodejs": "data:/example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "javascript:/example.com/", - "nodejs": "javascript:/example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "mailto:/example.com/", - "nodejs": "mailto:/example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "http:example.com/", - "nodejs": "http://example.org/foo/example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "ftp:example.com/", - "nodejs": "ftp://example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "https:example.com/", - "nodejs": "https://example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "madeupscheme:example.com/", - "nodejs": "madeupscheme:example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "ftps:example.com/", - "nodejs": "ftps:example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "gopher:example.com/", - "nodejs": "gopher://example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "ws:example.com/", - "nodejs": "ws:example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "wss:example.com/", - "nodejs": "wss:example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "data:example.com/", - "nodejs": "data:example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "javascript:example.com/", - "nodejs": "javascript:example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "mailto:example.com/", - "nodejs": "mailto:example.com/" - }, - { - "base": "http://example.org/foo/bar", - "input": "/a/b/c", - "nodejs": "http://example.org/a/b/c" - }, - { - "base": "http://example.org/foo/bar", - "input": "/a/ /c", - "nodejs": "http://example.org/a/%20/c" - }, - { - "base": "http://example.org/foo/bar", - "input": "/a%2fc", - "nodejs": "http://example.org/a%2fc" - }, - { - "base": "http://example.org/foo/bar", - "input": "/a/%2f/c", - "nodejs": "http://example.org/a/%2f/c" - }, - { - "base": "http://example.org/foo/bar", - "input": "#\u03b2", - "nodejs": "http://example.org/foo/bar#\u03b2" - }, - { - "base": "http://example.org/foo/bar", - "input": "data:text/html,test#test", - "nodejs": "data:text/html,test#test" - }, - { - "base": "file:///tmp/mock/path", - "input": "file:c:\\foo\\bar.html", - "nodejs": "file:///tmp/mock/c:/foo/bar.html" - }, - { - "base": "file:///tmp/mock/path", - "input": " File:c|////foo\\bar.html", - "nodejs": "file://c/%7C////foo/bar.html", - "override": "file:///tmp/mock/c%7C////foo/bar.html" - }, - { - "base": "file:///tmp/mock/path", - "input": "C|/foo/bar", - "nodejs": "file:///tmp/mock/C%7C/foo/bar" - }, - { - "base": "file:///tmp/mock/path", - "input": "/C|\\foo\\bar", - "nodejs": "file:///C%7C/foo/bar" - }, - { - "base": "file:///tmp/mock/path", - "input": "//C|/foo/bar", - "nodejs": "file://c/%7C/foo/bar" - }, - { - "base": "file:///tmp/mock/path", - "input": "//server/file", - "nodejs": "file://server/file" - }, - { - "base": "file:///tmp/mock/path", - "input": "\\\\server\\file", - "nodejs": "file://server/file" - }, - { - "base": "file:///tmp/mock/path", - "input": "/\\server/file", - "nodejs": "file://server/file" - }, - { - "base": "file:///tmp/mock/path", - "input": "file:///foo/bar.txt", - "nodejs": "file:///foo/bar.txt" - }, - { - "base": "file:///tmp/mock/path", - "input": "file:///home/me", - "nodejs": "file:///home/me" - }, - { - "base": "file:///tmp/mock/path", - "input": "//", - "nodejs": "file://", - "override": "file:////" - }, - { - "base": "file:///tmp/mock/path", - "input": "///", - "nodejs": "file:///" - }, - { - "base": "file:///tmp/mock/path", - "input": "///test", - "nodejs": "file:///test" - }, - { - "base": "file:///tmp/mock/path", - "input": "file://test", - "nodejs": "file://test/" - }, - { - "base": "file:///tmp/mock/path", - "input": "file://localhost", - "nodejs": "file://localhost/" - }, - { - "base": "file:///tmp/mock/path", - "input": "file://localhost/", - "nodejs": "file://localhost/" - }, - { - "base": "file:///tmp/mock/path", - "input": "file://localhost/test", - "nodejs": "file://localhost/test" - }, - { - "base": "file:///tmp/mock/path", - "input": "test", - "nodejs": "file:///tmp/mock/test" - }, - { - "base": "file:///tmp/mock/path", - "input": "file:test", - "nodejs": "file:///tmp/mock/test" - }, - { - "base": "about:blank", - "input": "http://example.com/././foo", - "nodejs": "http://example.com/././foo" - }, - { - "base": "about:blank", - "input": "http://example.com/./.foo", - "nodejs": "http://example.com/./.foo" - }, - { - "base": "about:blank", - "input": "http://example.com/foo/.", - "nodejs": "http://example.com/foo/." - }, - { - "base": "about:blank", - "input": "http://example.com/foo/./", - "nodejs": "http://example.com/foo/./" - }, - { - "base": "about:blank", - "input": "http://example.com/foo/bar/..", - "nodejs": "http://example.com/foo/bar/.." - }, - { - "base": "about:blank", - "input": "http://example.com/foo/bar/../", - "nodejs": "http://example.com/foo/bar/../" - }, - { - "base": "about:blank", - "input": "http://example.com/foo/..bar", - "nodejs": "http://example.com/foo/..bar" - }, - { - "base": "about:blank", - "input": "http://example.com/foo/bar/../ton", - "nodejs": "http://example.com/foo/bar/../ton" - }, - { - "base": "about:blank", - "input": "http://example.com/foo/bar/../ton/../../a", - "nodejs": "http://example.com/foo/bar/../ton/../../a" - }, - { - "base": "about:blank", - "input": "http://example.com/foo/../../..", - "nodejs": "http://example.com/foo/../../.." - }, - { - "base": "about:blank", - "input": "http://example.com/foo/../../../ton", - "nodejs": "http://example.com/foo/../../../ton" - }, - { - "base": "about:blank", - "input": "http://example.com/foo/%2e", - "nodejs": "http://example.com/foo/%2e" - }, - { - "base": "about:blank", - "input": "http://example.com/foo/%2e%2", - "nodejs": "http://example.com/foo/%2e%2" - }, - { - "base": "about:blank", - "input": "http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar", - "nodejs": "http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar" - }, - { - "base": "about:blank", - "input": "http://example.com////../..", - "nodejs": "http://example.com////../.." - }, - { - "base": "about:blank", - "input": "http://example.com/foo/bar//../..", - "nodejs": "http://example.com/foo/bar//../.." - }, - { - "base": "about:blank", - "input": "http://example.com/foo/bar//..", - "nodejs": "http://example.com/foo/bar//.." - }, - { - "base": "about:blank", - "input": "http://example.com/foo", - "nodejs": "http://example.com/foo" - }, - { - "base": "about:blank", - "input": "http://example.com/%20foo", - "nodejs": "http://example.com/%20foo" - }, - { - "base": "about:blank", - "input": "http://example.com/foo%", - "nodejs": "http://example.com/foo%" - }, - { - "base": "about:blank", - "input": "http://example.com/foo%2", - "nodejs": "http://example.com/foo%2" - }, - { - "base": "about:blank", - "input": "http://example.com/foo%2zbar", - "nodejs": "http://example.com/foo%2zbar" - }, - { - "base": "about:blank", - "input": "http://example.com/foo%2\u00c2\u00a9zbar", - "nodejs": "http://example.com/foo%2\u00c2\u00a9zbar" - }, - { - "base": "about:blank", - "input": "http://example.com/foo%41%7a", - "nodejs": "http://example.com/foo%41%7a" - }, - { - "base": "about:blank", - "input": "http://example.com/foo\t\u0091%91", - "nodejs": "http://example.com/foo%09\u0091%91" - }, - { - "base": "about:blank", - "input": "http://example.com/foo%00%51", - "nodejs": "http://example.com/foo%00%51" - }, - { - "base": "about:blank", - "input": "http://example.com/(%28:%3A%29)", - "nodejs": "http://example.com/(%28:%3A%29)" - }, - { - "base": "about:blank", - "input": "http://example.com/%3A%3a%3C%3c", - "nodejs": "http://example.com/%3A%3a%3C%3c" - }, - { - "base": "about:blank", - "input": "http://example.com/foo\tbar", - "nodejs": "http://example.com/foo%09bar" - }, - { - "base": "about:blank", - "input": "http://example.com\\\\foo\\\\bar", - "nodejs": "http://example.com//foo//bar" - }, - { - "base": "about:blank", - "input": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd", - "nodejs": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd" - }, - { - "base": "about:blank", - "input": "http://example.com/@asdf%40", - "nodejs": "http://example.com/@asdf%40" - }, - { - "base": "about:blank", - "input": "http://example.com/\u4f60\u597d\u4f60\u597d", - "nodejs": "http://example.com/\u4f60\u597d\u4f60\u597d" - }, - { - "base": "about:blank", - "input": "http://example.com/\u2025/foo", - "nodejs": "http://example.com/\u2025/foo" - }, - { - "base": "about:blank", - "input": "http://example.com/\ufeff/foo", - "nodejs": "http://example.com/\ufeff/foo" - }, - { - "base": "about:blank", - "input": "http://example.com/\u202e/foo/\u202d/bar", - "nodejs": "http://example.com/\u202e/foo/\u202d/bar" - }, - { - "base": "about:blank", - "input": "http://www.google.com/foo?bar=baz#", - "nodejs": "http://www.google.com/foo?bar=baz#" - }, - { - "base": "about:blank", - "input": "http://www.google.com/foo?bar=baz# \u00bb", - "nodejs": "http://www.google.com/foo?bar=baz#%20\u00bb" - }, - { - "base": "about:blank", - "input": "data:test# \u00bb", - "nodejs": "data:test#%20\u00bb" - }, - { - "base": "about:blank", - "input": "http://[www.google.com]/", - "nodejs": "http://[www.google.com]/" - }, - { - "base": "about:blank", - "input": "http://www.google.com", - "nodejs": "http://www.google.com/" - }, - { - "base": "about:blank", - "input": "http://192.0x00A80001", - "nodejs": "http://192.0x00a80001/" - }, - { - "base": "about:blank", - "input": "http://www/foo%2Ehtml", - "nodejs": "http://www/foo%2Ehtml" - }, - { - "base": "about:blank", - "input": "http://www/foo/%2E/html", - "nodejs": "http://www/foo/%2E/html" - }, - { - "base": "about:blank", - "input": "http://user:pass@/", - "nodejs": "http:///" - }, - { - "base": "about:blank", - "input": "http://%25DOMAIN:foobar@foodomain.com/", - "nodejs": "http://%25DOMAIN:foobar@foodomain.com/" - }, - { - "base": "about:blank", - "input": "http:\\\\www.google.com\\foo", - "nodejs": "http://www.google.com/foo" - }, - { - "base": "about:blank", - "input": "http://foo:80/", - "nodejs": "http://foo:80/" - }, - { - "base": "about:blank", - "input": "http://foo:81/", - "nodejs": "http://foo:81/" - }, - { - "base": "about:blank", - "input": "httpa://foo:80/", - "nodejs": "httpa://foo:80/" - }, - { - "base": "about:blank", - "input": "http://foo:-80/", - "nodejs": "http://foo/:-80/" - }, - { - "base": "about:blank", - "input": "https://foo:443/", - "nodejs": "https://foo:443/" - }, - { - "base": "about:blank", - "input": "https://foo:80/", - "nodejs": "https://foo:80/" - }, - { - "base": "about:blank", - "input": "ftp://foo:21/", - "nodejs": "ftp://foo:21/" - }, - { - "base": "about:blank", - "input": "ftp://foo:80/", - "nodejs": "ftp://foo:80/" - }, - { - "base": "about:blank", - "input": "gopher://foo:70/", - "nodejs": "gopher://foo:70/" - }, - { - "base": "about:blank", - "input": "gopher://foo:443/", - "nodejs": "gopher://foo:443/" - }, - { - "base": "about:blank", - "input": "ws://foo:80/", - "nodejs": "ws://foo:80/" - }, - { - "base": "about:blank", - "input": "ws://foo:81/", - "nodejs": "ws://foo:81/" - }, - { - "base": "about:blank", - "input": "ws://foo:443/", - "nodejs": "ws://foo:443/" - }, - { - "base": "about:blank", - "input": "ws://foo:815/", - "nodejs": "ws://foo:815/" - }, - { - "base": "about:blank", - "input": "wss://foo:80/", - "nodejs": "wss://foo:80/" - }, - { - "base": "about:blank", - "input": "wss://foo:81/", - "nodejs": "wss://foo:81/" - }, - { - "base": "about:blank", - "input": "wss://foo:443/", - "nodejs": "wss://foo:443/" - }, - { - "base": "about:blank", - "input": "wss://foo:815/", - "nodejs": "wss://foo:815/" - }, - { - "base": "about:blank", - "input": "http:/example.com/", - "nodejs": "http://example.com/" - }, - { - "base": "about:blank", - "input": "ftp:/example.com/", - "nodejs": "ftp://example.com/" - }, - { - "base": "about:blank", - "input": "https:/example.com/", - "nodejs": "https://example.com/" - }, - { - "base": "about:blank", - "input": "madeupscheme:/example.com/", - "nodejs": "madeupscheme:/example.com/" - }, - { - "base": "about:blank", - "input": "file:/example.com/", - "nodejs": "file://example.com/", - "override": "file:/example.com/" - }, - { - "base": "about:blank", - "input": "ftps:/example.com/", - "nodejs": "ftps:/example.com/" - }, - { - "base": "about:blank", - "input": "gopher:/example.com/", - "nodejs": "gopher://example.com/" - }, - { - "base": "about:blank", - "input": "ws:/example.com/", - "nodejs": "ws:/example.com/" - }, - { - "base": "about:blank", - "input": "wss:/example.com/", - "nodejs": "wss:/example.com/" - }, - { - "base": "about:blank", - "input": "data:/example.com/", - "nodejs": "data:/example.com/" - }, - { - "base": "about:blank", - "input": "javascript:/example.com/", - "nodejs": "javascript:/example.com/" - }, - { - "base": "about:blank", - "input": "mailto:/example.com/", - "nodejs": "mailto:/example.com/" - }, - { - "base": "about:blank", - "input": "http:example.com/", - "nodejs": "http://example.com/" - }, - { - "base": "about:blank", - "input": "ftp:example.com/", - "nodejs": "ftp://example.com/" - }, - { - "base": "about:blank", - "input": "https:example.com/", - "nodejs": "https://example.com/" - }, - { - "base": "about:blank", - "input": "madeupscheme:example.com/", - "nodejs": "madeupscheme:example.com/" - }, - { - "base": "about:blank", - "input": "ftps:example.com/", - "nodejs": "ftps:example.com/" - }, - { - "base": "about:blank", - "input": "gopher:example.com/", - "nodejs": "gopher://example.com/" - }, - { - "base": "about:blank", - "input": "ws:example.com/", - "nodejs": "ws:example.com/" - }, - { - "base": "about:blank", - "input": "wss:example.com/", - "nodejs": "wss:example.com/" - }, - { - "base": "about:blank", - "input": "data:example.com/", - "nodejs": "data:example.com/" - }, - { - "base": "about:blank", - "input": "javascript:example.com/", - "nodejs": "javascript:example.com/" - }, - { - "base": "about:blank", - "input": "mailto:example.com/", - "nodejs": "mailto:example.com/" - }, - { - "base": "about:blank", - "input": "http:@www.example.com", - "nodejs": "http://@www.example.com/" - }, - { - "base": "about:blank", - "input": "http:/@www.example.com", - "nodejs": "http://@www.example.com/" - }, - { - "base": "about:blank", - "input": "http://@www.example.com", - "nodejs": "http://www.example.com/" - }, - { - "base": "about:blank", - "input": "http:a:b@www.example.com", - "nodejs": "http://a:b@www.example.com/" - }, - { - "base": "about:blank", - "input": "http:/a:b@www.example.com", - "nodejs": "http://a:b@www.example.com/" - }, - { - "base": "about:blank", - "input": "http://a:b@www.example.com", - "nodejs": "http://a:b@www.example.com/" - }, - { - "base": "about:blank", - "input": "http://@pple.com", - "nodejs": "http://pple.com/" - }, - { - "base": "about:blank", - "input": "http::b@www.example.com", - "nodejs": "http://:b@www.example.com/" - }, - { - "base": "about:blank", - "input": "http:/:b@www.example.com", - "nodejs": "http://:b@www.example.com/" - }, - { - "base": "about:blank", - "input": "http://:b@www.example.com", - "nodejs": "http://:b@www.example.com/" - }, - { - "base": "about:blank", - "input": "http:/:@/www.example.com", - "nodejs": "http://:@/www.example.com" - }, - { - "base": "about:blank", - "input": "http://user@/www.example.com", - "nodejs": "http://user@www.example.com/" - }, - { - "base": "about:blank", - "input": "http:@/www.example.com", - "nodejs": "http://@/www.example.com" - }, - { - "base": "about:blank", - "input": "http:/@/www.example.com", - "nodejs": "http://@/www.example.com" - }, - { - "base": "about:blank", - "input": "http://@/www.example.com", - "nodejs": "http://www.example.com/" - }, - { - "base": "about:blank", - "input": "https:@/www.example.com", - "nodejs": "https://@/www.example.com" - }, - { - "base": "about:blank", - "input": "http:a:b@/www.example.com", - "nodejs": "http://a:b@/www.example.com" - }, - { - "base": "about:blank", - "input": "http:/a:b@/www.example.com", - "nodejs": "http://a:b@/www.example.com" - }, - { - "base": "about:blank", - "input": "http://a:b@/www.example.com", - "nodejs": "http://a:b@www.example.com/" - }, - { - "base": "about:blank", - "input": "http::@/www.example.com", - "nodejs": "http://:@/www.example.com" - }, - { - "base": "about:blank", - "input": "http:a:@www.example.com", - "nodejs": "http://a:@www.example.com/" - }, - { - "base": "about:blank", - "input": "http:/a:@www.example.com", - "nodejs": "http://a:@www.example.com/" - }, - { - "base": "about:blank", - "input": "http://a:@www.example.com", - "nodejs": "http://a:@www.example.com/" - }, - { - "base": "about:blank", - "input": "http://www.@pple.com", - "nodejs": "http://www.@pple.com/" - }, - { - "base": "about:blank", - "input": "http:@:www.example.com", - "nodejs": "http://@:www.example.com/" - }, - { - "base": "about:blank", - "input": "http:/@:www.example.com", - "nodejs": "http://@:www.example.com/" - }, - { - "base": "about:blank", - "input": "http://@:www.example.com", - "nodejs": "http://:www.example.com/" - }, - { - "base": "about:blank", - "input": "http://:@www.example.com", - "nodejs": "http://:@www.example.com/" - }, - { - "base": "http://www.example.com/test", - "input": "/", - "nodejs": "http://www.example.com/" - }, - { - "base": "http://www.example.com/test", - "input": "/test.txt", - "nodejs": "http://www.example.com/test.txt" - }, - { - "base": "http://www.example.com/test", - "input": ".", - "nodejs": "http://www.example.com/" - }, - { - "base": "http://www.example.com/test", - "input": "..", - "nodejs": "http://www.example.com/" - }, - { - "base": "http://www.example.com/test", - "input": "test.txt", - "nodejs": "http://www.example.com/test.txt" - }, - { - "base": "http://www.example.com/test", - "input": "./test.txt", - "nodejs": "http://www.example.com/test.txt" - }, - { - "base": "http://www.example.com/test", - "input": "../test.txt", - "nodejs": "http://www.example.com/test.txt" - }, - { - "base": "http://www.example.com/test", - "input": "../aaa/test.txt", - "nodejs": "http://www.example.com/aaa/test.txt" - }, - { - "base": "http://www.example.com/test", - "input": "../../test.txt", - "nodejs": "http://www.example.com/test.txt" - }, - { - "base": "http://www.example.com/test", - "input": "\u4e2d/test.txt", - "nodejs": "http://www.example.com/\u4e2d/test.txt" - }, - { - "base": "http://www.example.com/test", - "input": "http://www.example2.com", - "nodejs": "http://www.example2.com/" - }, - { - "base": "http://www.example.com/test", - "input": "//www.example2.com", - "nodejs": "http://www.example2.com/" - }, - { - "base": "http://other.com/", - "input": "http://ExAmPlE.CoM", - "nodejs": "http://example.com/" - }, - { - "base": "http://other.com/", - "input": "http://example example.com", - "nodejs": "http://example/%20example.com" - }, - { - "base": "http://other.com/", - "input": "http://Goo%20 goo%7C|.com", - "nodejs": "http://goo/%20%20goo%7C%7C.com" - }, - { - "base": "http://other.com/", - "input": "http://GOO\u00a0\u3000goo.com", - "nodejs": "http://xn--googoo-rga8318g.com/" - }, - { - "base": "http://other.com/", - "input": "http://GOO\u200b\u2060\ufeffgoo.com", - "nodejs": "http://xn--googoo-df0c40ax241n.com/" - }, - { - "base": "http://other.com/", - "input": "http://www.foo\u3002bar.com", - "nodejs": "http://www.foo.bar.com/" - }, - { - "base": "http://other.com/", - "input": "http://\ufdd0zyx.com", - "nodejs": "http://xn--zyx-h05s.com/" - }, - { - "base": "http://other.com/", - "input": "http://%ef%b7%90zyx.com", - "nodejs": "http://other.com/%ef%b7%90zyx.com" - }, - { - "base": "http://other.com/", - "input": "http://\uff27\uff4f.com", - "nodejs": "http://xn--si7cqa.com/" - }, - { - "base": "http://other.com/", - "input": "http://\uff05\uff14\uff11.com", - "nodejs": "http://xn--wg7cyai.com/" - }, - { - "base": "http://other.com/", - "input": "http://%ef%bc%85%ef%bc%94%ef%bc%91.com", - "nodejs": "http://other.com/%ef%bc%85%ef%bc%94%ef%bc%91.com" - }, - { - "base": "http://other.com/", - "input": "http://\uff05\uff10\uff10.com", - "nodejs": "http://xn--wg7cwaa.com/" - }, - { - "base": "http://other.com/", - "input": "http://%ef%bc%85%ef%bc%90%ef%bc%90.com", - "nodejs": "http://other.com/%ef%bc%85%ef%bc%90%ef%bc%90.com" - }, - { - "base": "http://other.com/", - "input": "http://\u4f60\u597d\u4f60\u597d", - "nodejs": "http://xn--6qqa088eba/" - }, - { - "base": "http://other.com/", - "input": "http://%zz%66%a.com", - "nodejs": "http://other.com/%zz%66%a.com" - }, - { - "base": "http://other.com/", - "input": "http://%25", - "nodejs": "http://other.com/%25" - }, - { - "base": "http://other.com/", - "input": "http://hello%00", - "nodejs": "http://hello/%00" - }, - { - "base": "http://other.com/", - "input": "http://%30%78%63%30%2e%30%32%35%30.01", - "nodejs": "http://other.com/%30%78%63%30%2e%30%32%35%30.01" - }, - { - "base": "http://other.com/", - "input": "http://%30%78%63%30%2e%30%32%35%30.01%2e", - "nodejs": "http://other.com/%30%78%63%30%2e%30%32%35%30.01%2e" - }, - { - "base": "http://other.com/", - "input": "http://192.168.0.257", - "nodejs": "http://192.168.0.257/" - }, - { - "base": "http://other.com/", - "input": "http://%3g%78%63%30%2e%30%32%35%30%2E.01", - "nodejs": "http://other.com/%3g%78%63%30%2e%30%32%35%30%2E.01" - }, - { - "base": "http://other.com/", - "input": "http://192.168.0.1 hello", - "nodejs": "http://192.168.0.1/%20hello" - }, - { - "base": "http://other.com/", - "input": "http://\uff10\uff38\uff43\uff10\uff0e\uff10\uff12\uff15\uff10\uff0e\uff10\uff11", - "nodejs": "http://xn--7g7ca6m5c.xn--7g7cafm.xn--7g7cc/" - }, - { - "base": "http://other.com/", - "input": "http://[google.com]", - "nodejs": "http://[google.com]/" - }, - { - "base": "http://other.com/", - "input": "http://foo:\ud83d\udca9@example.com/bar", - "nodejs": "http://foo:%F0%9F%92%A9@example.com/bar" - }, - { - "base": "test:test", - "input": "x", - "nodejs": "test:x" - } -]; - -whatwgTests.forEach(function(test) { - var expected = test.override !== undefined ? test.override : test.nodejs; - assert.equal(url.resolve(test.base, test.input), expected); -}); -- cgit v1.2.3