diff --git a/lib/url.js b/lib/url.js index 6f36b36354ce00..e0965c13791e8f 100644 --- a/lib/url.js +++ b/lib/url.js @@ -300,16 +300,25 @@ Url.prototype.parse = function parse(url, parseQueryString, slashesDenoteHost) { // how the browser resolves relative URLs. http:@example.com is treated // the same as http://example.com. let removedCharsCount = 0; + let isExtraSlashForFileSchemes = false; if (slashesDenoteHost || proto || hostPattern.test(rest)) { - if (rest.charCodeAt(0) === CHAR_FORWARD_SLASH &&rest.charCodeAt(1) === CHAR_FORWARD_SLASH) { + if (rest.charCodeAt(0) === CHAR_FORWARD_SLASH && rest.charCodeAt(1) === CHAR_FORWARD_SLASH) { this.slashes = true; } if (!(proto && hostlessProtocol.has(lowerProto))) { if (this.slashes) { rest = rest.slice(2); - } else if (slashedProtocol.has(lowerProto)) { - const newRest = rest.replace(/^[/\\]*@?/, ''); - removedCharsCount = rest.length - newRest; + } + + 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 1ef4665f2082bc..f8363159348d5d 100644 --- a/test/parallel/test-url-parse-format.js +++ b/test/parallel/test-url-parse-format.js @@ -1011,6 +1011,51 @@ const parseTests = { 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) {