diff --git a/lib/url.js b/lib/url.js index 63d24bef7bf0bd..6fb039b324436a 100644 --- a/lib/url.js +++ b/lib/url.js @@ -299,18 +299,27 @@ Url.prototype.parse = function parse(url, parseQueryString, slashesDenoteHost) { // resolution will treat //foo/bar as host=foo,path=bar because that's // how the browser resolves relative URLs. http:@example.com is treated // the same as http://example.com. - let slashes; - let at; + let removedCharsCount = 0; + let isExtraSlashForFileSchemes = false; if (slashesDenoteHost || proto || hostPattern.test(rest)) { - slashes = rest.charCodeAt(0) === CHAR_FORWARD_SLASH && - rest.charCodeAt(1) === CHAR_FORWARD_SLASH; - at = rest.charCodeAt(0) === CHAR_AT; + if (rest.charCodeAt(0) === CHAR_FORWARD_SLASH && rest.charCodeAt(1) === CHAR_FORWARD_SLASH) { + this.slashes = true; + } if (!(proto && hostlessProtocol.has(lowerProto))) { if (slashes) { rest = rest.slice(2); - this.slashes = true; - } else if (at) { - rest = rest.slice(1); + } + + if (slashedProtocol.has(lowerProto)) { + let newRest = rest.replace(/^[/\\]*@?/, ''); + removedCharsCount = rest.length - newRest.length; + if (lowerProto === 'file' || lowerProto === 'file:') { + if (rest[removedCharsCount - 1] === '/') { + newRest = '/' + rest; + isExtraSlashForFileSchemes = true; + } + } + rest = newRest; } } } @@ -510,6 +519,12 @@ Url.prototype.parse = function parse(url, parseQueryString, slashesDenoteHost) { this.path = p + s; } + // Remove extra `/` previously inserted for file: URLs. + if (isExtraSlashForFileSchemes) { + this.path = this.path.slice(1); + this.pathname = this.pathname.slice(1); + } + // Finally, reconstruct the href based on what has been validated. this.href = this.format(); return this; @@ -763,13 +778,11 @@ Url.prototype.resolveObject = function resolveObject(relative) { 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, + // the protocol does weird things. + // If it's not file:, then we MUST have a host, // and if there was a path // to begin with, then we MUST have a path. - // if it is file:, then the host is dropped, - // because that's known to be hostless. - // anything else is assumed to be absolute. + // Anything else is assumed to be absolute. if (!slashedProtocol.has(relative.protocol)) { const keys = ObjectKeys(relative); for (let v = 0; v < keys.length; v++) { diff --git a/test/parallel/test-url-parse-format.js b/test/parallel/test-url-parse-format.js index a4bb141b49bfc7..b317bc0d4101ca 100644 --- a/test/parallel/test-url-parse-format.js +++ b/test/parallel/test-url-parse-format.js @@ -992,6 +992,66 @@ const parseTests = { path: '/', href: 'http://localhost/', }, + + 'http:example.com': { + protocol: 'http:', + slashes: null, + auth: null, + host: 'example.com', + port: null, + hostname: 'example.com', + hash: null, + search: null, + query: null, + pathname: '/', + path: '/', + href: 'http://example.com/', + }, + + 'http:/example.com': { + protocol: 'http:', + slashes: null, + auth: null, + host: 'example.com', + port: null, + hostname: 'example.com', + hash: null, + search: null, + query: null, + pathname: '/', + path: '/', + href: 'http://example.com/', + }, + + 'http:\\example.com': { + protocol: 'http:', + slashes: null, + auth: null, + host: 'example.com', + port: null, + hostname: 'example.com', + hash: null, + search: null, + query: null, + pathname: '/', + path: '/', + href: 'http://example.com/', + }, + + 'http://////////example.com': { + protocol: 'http:', + slashes: true, + auth: null, + host: 'example.com', + port: null, + hostname: 'example.com', + hash: null, + search: null, + query: null, + pathname: '/', + path: '/', + href: 'http://example.com/', + }, }; for (const u in parseTests) {