Package: release.debian.org Severity: normal User: release.debian....@packages.debian.org Usertags: unblock
Please unblock package node-url-parse [ Reason ] node-url-parse 1.5.1 is vulnerable to URL redirection to untrusted sites. [ Impact ] Medium security issue [ Tests ] Test passed (both build & autopkgtest) [ Risks ] Low risk: node-url-parse is a reverse dependency of: * node-miragejs (Build only) * node-original * node-eventsource I tested rebuild & autopkgtest with success: rebuild node-miragejs ... PASS autopkgtest node-original ... PASS rebuild node-original ... PASS [ Checklist ] [X] all changes are documented in the d/changelog [X] I reviewed all changes and I approve them [X] attach debdiff against the package in testing [ Other info ] I prefered to update node-url-parse instead of backporting changes since all changes are related to this vulnerabilities (including test updates) You will find 2 debdiff: * full debdiff * relevant debdiff (only index.js changes) Cheers, Yadd unblock node-url-parse/1.5.3-1
diff --git a/index.js b/index.js index 72b27c0..c6052d5 100644 --- a/index.js +++ b/index.js @@ -2,8 +2,9 @@ var required = require('requires-port') , qs = require('querystringify') - , slashes = /^[A-Za-z][A-Za-z0-9+-.]*:[\\/]+/ - , protocolre = /^([a-z][a-z0-9.+-]*:)?([\\/]{1,})?([\S\s]*)/i + , slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\/\// + , protocolre = /^([a-z][a-z0-9.+-]*:)?(\/\/)?([\\/]+)?([\S\s]*)/i + , windowsDriveLetter = /^[a-zA-Z]:/ , whitespace = '[\\x09\\x0A\\x0B\\x0C\\x0D\\x20\\xA0\\u1680\\u180E\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200A\\u202F\\u205F\\u3000\\u2028\\u2029\\uFEFF]' , left = new RegExp('^'+ whitespace +'+'); @@ -32,8 +33,8 @@ function trimLeft(str) { var rules = [ ['#', 'hash'], // Extract from the back. ['?', 'query'], // Extract from the back. - function sanitize(address) { // Sanitize what is left of the address - return address.replace('\\', '/'); + function sanitize(address, url) { // Sanitize what is left of the address + return isSpecial(url.protocol) ? address.replace(/\\/g, '/') : address; }, ['/', 'pathname'], // Extract from the back. ['@', 'auth', 1], // Extract from the front. @@ -98,6 +99,24 @@ function lolcation(loc) { return finaldestination; } +/** + * Check whether a protocol scheme is special. + * + * @param {String} The protocol scheme of the URL + * @return {Boolean} `true` if the protocol scheme is special, else `false` + * @private + */ +function isSpecial(scheme) { + return ( + scheme === 'file:' || + scheme === 'ftp:' || + scheme === 'http:' || + scheme === 'https:' || + scheme === 'ws:' || + scheme === 'wss:' + ); +} + /** * @typedef ProtocolExtract * @type Object @@ -110,20 +129,56 @@ function lolcation(loc) { * Extract protocol information from a URL with/without double slash ("//"). * * @param {String} address URL we want to extract from. + * @param {Object} location * @return {ProtocolExtract} Extracted information. * @private */ -function extractProtocol(address) { +function extractProtocol(address, location) { address = trimLeft(address); + location = location || {}; + + var match = protocolre.exec(address); + var protocol = match[1] ? match[1].toLowerCase() : ''; + var forwardSlashes = !!match[2]; + var otherSlashes = !!match[3]; + var slashesCount = 0; + var rest; + + if (forwardSlashes) { + if (otherSlashes) { + rest = match[2] + match[3] + match[4]; + slashesCount = match[2].length + match[3].length; + } else { + rest = match[2] + match[4]; + slashesCount = match[2].length; + } + } else { + if (otherSlashes) { + rest = match[3] + match[4]; + slashesCount = match[3].length; + } else { + rest = match[4] + } + } - var match = protocolre.exec(address) - , protocol = match[1] ? match[1].toLowerCase() : '' - , slashes = !!(match[2] && match[2].length >= 2) - , rest = match[2] && match[2].length === 1 ? '/' + match[3] : match[3]; + if (protocol === 'file:') { + if (slashesCount >= 2) { + rest = rest.slice(2); + } + } else if (isSpecial(protocol)) { + rest = match[4]; + } else if (protocol) { + if (forwardSlashes) { + rest = rest.slice(2); + } + } else if (slashesCount >= 2 && isSpecial(location.protocol)) { + rest = match[4]; + } return { protocol: protocol, - slashes: slashes, + slashes: forwardSlashes || isSpecial(protocol), + slashesCount: slashesCount, rest: rest }; } @@ -214,7 +269,7 @@ function Url(address, location, parser) { // // Extract protocol information before running the instructions. // - extracted = extractProtocol(address || ''); + extracted = extractProtocol(address || '', location); relative = !extracted.protocol && !extracted.slashes; url.slashes = extracted.slashes || relative && location.slashes; url.protocol = extracted.protocol || location.protocol || ''; @@ -224,13 +279,22 @@ function Url(address, location, parser) { // When the authority component is absent the URL starts with a path // component. // - if (!extracted.slashes) instructions[3] = [/(.*)/, 'pathname']; + if ( + extracted.protocol === 'file:' && ( + extracted.slashesCount !== 2 || windowsDriveLetter.test(address)) || + (!extracted.slashes && + (extracted.protocol || + extracted.slashesCount < 2 || + !isSpecial(url.protocol))) + ) { + instructions[3] = [/(.*)/, 'pathname']; + } for (; i < instructions.length; i++) { instruction = instructions[i]; if (typeof instruction === 'function') { - address = instruction(address); + address = instruction(address, url); continue; } @@ -288,7 +352,7 @@ function Url(address, location, parser) { // Default to a / for pathname if none exists. This normalizes the URL // to always have a / // - if (url.pathname.charAt(0) !== '/' && url.hostname) { + if (url.pathname.charAt(0) !== '/' && isSpecial(url.protocol)) { url.pathname = '/' + url.pathname; } @@ -312,7 +376,7 @@ function Url(address, location, parser) { url.password = instruction[1] || ''; } - url.origin = url.protocol && url.host && url.protocol !== 'file:' + url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host ? url.protocol +'//'+ url.host : 'null'; @@ -405,7 +469,7 @@ function set(part, value, fn) { if (ins[4]) url[ins[1]] = url[ins[1]].toLowerCase(); } - url.origin = url.protocol && url.host && url.protocol !== 'file:' + url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host ? url.protocol +'//'+ url.host : 'null'; @@ -430,7 +494,7 @@ function toString(stringify) { if (protocol && protocol.charAt(protocol.length - 1) !== ':') protocol += ':'; - var result = protocol + (url.slashes ? '//' : ''); + var result = protocol + (url.slashes || isSpecial(url.protocol) ? '//' : ''); if (url.username) { result += url.username;
diff --git a/README.md b/README.md index f81f919..4540e4b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # url-parse -[](http://unshift.io)[](https://www.npmjs.com/package/url-parse)[](https://travis-ci.org/unshiftio/url-parse)[](https://david-dm.org/unshiftio/url-parse)[](https://coveralls.io/r/unshiftio/url-parse?branch=master)[](https://webchat.freenode.net/?channels=unshift) +[](http://unshift.io)[](https://www.npmjs.com/package/url-parse)[](https://github.com/unshiftio/url-parse/actions?query=workflow%3ACI+branch%3Amaster)[](https://david-dm.org/unshiftio/url-parse)[](https://coveralls.io/r/unshiftio/url-parse?branch=master)[](https://webchat.freenode.net/?channels=unshift) [](https://saucelabs.com/u/url-parse) diff --git a/SECURITY.md b/SECURITY.md index 31ef5b4..3a97067 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -33,6 +33,19 @@ acknowledge your responsible disclosure, if you wish. ## History +> url-parse mishandles certain use a single of (back) slash such as https:\ & +> https:/ and > interprets the URI as a relative path. Browsers accept a single +> backslash after the protocol, and treat it as a normal slash, while url-parse +> sees it as a relative path. + +- **Reporter credits** + - Ready-Research + - GitHub: [@Ready-Reserach](https://github.com/ready-research) +- Huntr report: https://www.huntr.dev/bounties/1625557993985-unshiftio/url-parse/ +- Fixed in: 1.5.2 + +--- + > Using backslash in the protocol is valid in the browser, while url-parse > thinks it’s a relative path. An application that validates a url using > url-parse might pass a malicious link. @@ -42,6 +55,8 @@ acknowledge your responsible disclosure, if you wish. - Twitter: [Yaniv Nizry](https://twitter.com/ynizry) - Fixed in: 1.5.0 +--- + > The `extractProtocol` method does not return the correct protocol when > provided with unsanitized content which could lead to false positives. diff --git a/debian/changelog b/debian/changelog index ef5bb31..175b525 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +node-url-parse (1.5.3-1) unstable; urgency=medium + + * Team upload + * Fix GitHub tags regex + * New upstream version 1.5.3 (Closes: #991577) + + -- Yadd <y...@debian.org> Sat, 31 Jul 2021 13:13:02 +0200 + node-url-parse (1.5.1-1) unstable; urgency=medium * Team upload diff --git a/debian/watch b/debian/watch index 5636554..b2635d2 100644 --- a/debian/watch +++ b/debian/watch @@ -2,7 +2,7 @@ version=4 opts=\ dversionmangle=auto,\ filenamemangle=s/.*\/v?([\d\.-]+)\.tar\.gz/node-url-parse-$1.tar.gz/ \ - https://github.com/unshiftio/url-parse/tags .*/archive/v?([\d\.]+).tar.gz + https://github.com/unshiftio/url-parse/tags .*/archive/.*/v?([\d\.]+).tar.gz # It is not recommended use npmregistry. Please investigate more. # Take a look at https://wiki.debian.org/debian/watch/ diff --git a/index.js b/index.js index 72b27c0..c6052d5 100644 --- a/index.js +++ b/index.js @@ -2,8 +2,9 @@ var required = require('requires-port') , qs = require('querystringify') - , slashes = /^[A-Za-z][A-Za-z0-9+-.]*:[\\/]+/ - , protocolre = /^([a-z][a-z0-9.+-]*:)?([\\/]{1,})?([\S\s]*)/i + , slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\/\// + , protocolre = /^([a-z][a-z0-9.+-]*:)?(\/\/)?([\\/]+)?([\S\s]*)/i + , windowsDriveLetter = /^[a-zA-Z]:/ , whitespace = '[\\x09\\x0A\\x0B\\x0C\\x0D\\x20\\xA0\\u1680\\u180E\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200A\\u202F\\u205F\\u3000\\u2028\\u2029\\uFEFF]' , left = new RegExp('^'+ whitespace +'+'); @@ -32,8 +33,8 @@ function trimLeft(str) { var rules = [ ['#', 'hash'], // Extract from the back. ['?', 'query'], // Extract from the back. - function sanitize(address) { // Sanitize what is left of the address - return address.replace('\\', '/'); + function sanitize(address, url) { // Sanitize what is left of the address + return isSpecial(url.protocol) ? address.replace(/\\/g, '/') : address; }, ['/', 'pathname'], // Extract from the back. ['@', 'auth', 1], // Extract from the front. @@ -98,6 +99,24 @@ function lolcation(loc) { return finaldestination; } +/** + * Check whether a protocol scheme is special. + * + * @param {String} The protocol scheme of the URL + * @return {Boolean} `true` if the protocol scheme is special, else `false` + * @private + */ +function isSpecial(scheme) { + return ( + scheme === 'file:' || + scheme === 'ftp:' || + scheme === 'http:' || + scheme === 'https:' || + scheme === 'ws:' || + scheme === 'wss:' + ); +} + /** * @typedef ProtocolExtract * @type Object @@ -110,20 +129,56 @@ function lolcation(loc) { * Extract protocol information from a URL with/without double slash ("//"). * * @param {String} address URL we want to extract from. + * @param {Object} location * @return {ProtocolExtract} Extracted information. * @private */ -function extractProtocol(address) { +function extractProtocol(address, location) { address = trimLeft(address); + location = location || {}; + + var match = protocolre.exec(address); + var protocol = match[1] ? match[1].toLowerCase() : ''; + var forwardSlashes = !!match[2]; + var otherSlashes = !!match[3]; + var slashesCount = 0; + var rest; + + if (forwardSlashes) { + if (otherSlashes) { + rest = match[2] + match[3] + match[4]; + slashesCount = match[2].length + match[3].length; + } else { + rest = match[2] + match[4]; + slashesCount = match[2].length; + } + } else { + if (otherSlashes) { + rest = match[3] + match[4]; + slashesCount = match[3].length; + } else { + rest = match[4] + } + } - var match = protocolre.exec(address) - , protocol = match[1] ? match[1].toLowerCase() : '' - , slashes = !!(match[2] && match[2].length >= 2) - , rest = match[2] && match[2].length === 1 ? '/' + match[3] : match[3]; + if (protocol === 'file:') { + if (slashesCount >= 2) { + rest = rest.slice(2); + } + } else if (isSpecial(protocol)) { + rest = match[4]; + } else if (protocol) { + if (forwardSlashes) { + rest = rest.slice(2); + } + } else if (slashesCount >= 2 && isSpecial(location.protocol)) { + rest = match[4]; + } return { protocol: protocol, - slashes: slashes, + slashes: forwardSlashes || isSpecial(protocol), + slashesCount: slashesCount, rest: rest }; } @@ -214,7 +269,7 @@ function Url(address, location, parser) { // // Extract protocol information before running the instructions. // - extracted = extractProtocol(address || ''); + extracted = extractProtocol(address || '', location); relative = !extracted.protocol && !extracted.slashes; url.slashes = extracted.slashes || relative && location.slashes; url.protocol = extracted.protocol || location.protocol || ''; @@ -224,13 +279,22 @@ function Url(address, location, parser) { // When the authority component is absent the URL starts with a path // component. // - if (!extracted.slashes) instructions[3] = [/(.*)/, 'pathname']; + if ( + extracted.protocol === 'file:' && ( + extracted.slashesCount !== 2 || windowsDriveLetter.test(address)) || + (!extracted.slashes && + (extracted.protocol || + extracted.slashesCount < 2 || + !isSpecial(url.protocol))) + ) { + instructions[3] = [/(.*)/, 'pathname']; + } for (; i < instructions.length; i++) { instruction = instructions[i]; if (typeof instruction === 'function') { - address = instruction(address); + address = instruction(address, url); continue; } @@ -288,7 +352,7 @@ function Url(address, location, parser) { // Default to a / for pathname if none exists. This normalizes the URL // to always have a / // - if (url.pathname.charAt(0) !== '/' && url.hostname) { + if (url.pathname.charAt(0) !== '/' && isSpecial(url.protocol)) { url.pathname = '/' + url.pathname; } @@ -312,7 +376,7 @@ function Url(address, location, parser) { url.password = instruction[1] || ''; } - url.origin = url.protocol && url.host && url.protocol !== 'file:' + url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host ? url.protocol +'//'+ url.host : 'null'; @@ -405,7 +469,7 @@ function set(part, value, fn) { if (ins[4]) url[ins[1]] = url[ins[1]].toLowerCase(); } - url.origin = url.protocol && url.host && url.protocol !== 'file:' + url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host ? url.protocol +'//'+ url.host : 'null'; @@ -430,7 +494,7 @@ function toString(stringify) { if (protocol && protocol.charAt(protocol.length - 1) !== ':') protocol += ':'; - var result = protocol + (url.slashes ? '//' : ''); + var result = protocol + (url.slashes || isSpecial(url.protocol) ? '//' : ''); if (url.username) { result += url.username; diff --git a/package.json b/package.json index f84b62e..1364b9b 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "url-parse", - "version": "1.5.1", + "version": "1.5.3", "description": "Small footprint URL parser that works seamlessly across Node.js and browser environments", "main": "index.js", "scripts": { "browserify": "rm -rf dist && mkdir -p dist && browserify index.js -s URLParse -o dist/url-parse.js", "minify": "uglifyjs dist/url-parse.js --source-map -cm -o dist/url-parse.min.js", - "test": "c8 --reporter=html --reporter=text mocha test/test.js", + "test": "c8 --reporter=lcov --reporter=text mocha test/test.js", "test-browser": "node test/browser.js", "prepublishOnly": "npm run browserify && npm run minify", "watch": "mocha --watch test/test.js" @@ -38,9 +38,8 @@ }, "devDependencies": { "assume": "^2.2.0", - "browserify": "^16.2.3", + "browserify": "^17.0.0", "c8": "^7.3.1", - "coveralls": "^3.1.0", "mocha": "^8.0.1", "pre-commit": "^1.2.2", "sauce-browsers": "^2.0.0", diff --git a/test/browser.js b/test/browser.js index 200ec5e..63ee99b 100644 --- a/test/browser.js +++ b/test/browser.js @@ -29,12 +29,12 @@ const platforms = sauceBrowsers([ }); run(path.join(__dirname, 'test.js'), 'saucelabs', { + jobInfo: { name: pkg.name, build: process.env.GITHUB_RUN_ID }, html: path.join(__dirname, 'index.html'), accessKey: process.env.SAUCE_ACCESS_KEY, username: process.env.SAUCE_USERNAME, browserify: true, disableSSL: true, - name: pkg.name, parallel: 5, platforms }).done((results) => { diff --git a/test/test.js b/test/test.js index 216891e..8b34f7a 100644 --- a/test/test.js +++ b/test/test.js @@ -71,7 +71,8 @@ describe('url-parse', function () { assume(parse.extractProtocol('http://example.com')).eql({ slashes: true, protocol: 'http:', - rest: 'example.com' + rest: 'example.com', + slashesCount: 2 }); }); @@ -79,7 +80,8 @@ describe('url-parse', function () { assume(parse.extractProtocol('')).eql({ slashes: false, protocol: '', - rest: '' + rest: '', + slashesCount: 0 }); }); @@ -87,13 +89,15 @@ describe('url-parse', function () { assume(parse.extractProtocol('/foo')).eql({ slashes: false, protocol: '', - rest: '/foo' + rest: '/foo', + slashesCount: 1 }); assume(parse.extractProtocol('//foo/bar')).eql({ slashes: true, protocol: '', - rest: 'foo/bar' + rest: '//foo/bar', + slashesCount: 2 }); }); @@ -103,7 +107,8 @@ describe('url-parse', function () { assume(parse.extractProtocol(input)).eql({ slashes: false, protocol: '', - rest: input + rest: input, + slashesCount: 0 }); }); @@ -111,7 +116,8 @@ describe('url-parse', function () { assume(parse.extractProtocol(' javascript://foo')).eql({ slashes: true, protocol: 'javascript:', - rest: 'foo' + rest: 'foo', + slashesCount: 2 }); }); }); @@ -281,20 +287,118 @@ describe('url-parse', function () { assume(parsed.host).equals('what-is-up.com'); assume(parsed.href).equals('http://what-is-up.com/'); + + url = '\\\\\\\\what-is-up.com' + parsed = parse(url, parse('http://google.com')); + + assume(parsed.host).equals('what-is-up.com'); + assume(parsed.href).equals('http://what-is-up.com/'); }); - it('does not see a slash after the protocol as path', function () { + it('ignores slashes after the protocol for special URLs', function () { var url = 'https:\\/github.com/foo/bar' , parsed = parse(url); assume(parsed.host).equals('github.com'); assume(parsed.hostname).equals('github.com'); assume(parsed.pathname).equals('/foo/bar'); + assume(parsed.slashes).is.true(); + assume(parsed.href).equals('https://github.com/foo/bar'); - url = 'https:/\/\/\github.com/foo/bar'; + url = 'https:/\\/\\/\\github.com/foo/bar'; + parsed = parse(url); assume(parsed.host).equals('github.com'); assume(parsed.hostname).equals('github.com'); assume(parsed.pathname).equals('/foo/bar'); + assume(parsed.slashes).is.true(); + assume(parsed.href).equals('https://github.com/foo/bar'); + + url = 'https:/github.com/foo/bar'; + parsed = parse(url); + assume(parsed.host).equals('github.com'); + assume(parsed.pathname).equals('/foo/bar'); + assume(parsed.slashes).is.true(); + assume(parsed.href).equals('https://github.com/foo/bar'); + + url = 'https:\\github.com/foo/bar'; + parsed = parse(url); + assume(parsed.host).equals('github.com'); + assume(parsed.pathname).equals('/foo/bar'); + assume(parsed.slashes).is.true(); + assume(parsed.href).equals('https://github.com/foo/bar'); + + url = 'https:github.com/foo/bar'; + parsed = parse(url); + assume(parsed.host).equals('github.com'); + assume(parsed.pathname).equals('/foo/bar'); + assume(parsed.slashes).is.true(); + assume(parsed.href).equals('https://github.com/foo/bar'); + + url = 'https:github.com/foo/bar'; + parsed = parse(url); + assume(parsed.host).equals('github.com'); + assume(parsed.pathname).equals('/foo/bar'); + assume(parsed.slashes).is.true(); + assume(parsed.href).equals('https://github.com/foo/bar'); + }); + + it('handles slashes after the protocol for non special URLs', function () { + var url = 'foo:example.com' + , parsed = parse(url); + + assume(parsed.hostname).equals(''); + assume(parsed.pathname).equals('example.com'); + assume(parsed.href).equals('foo:example.com'); + assume(parsed.slashes).is.false(); + + url = 'foo:/example.com'; + parsed = parse(url); + assume(parsed.hostname).equals(''); + assume(parsed.pathname).equals('/example.com'); + assume(parsed.href).equals('foo:/example.com'); + assume(parsed.slashes).is.false(); + + url = 'foo:\\example.com'; + parsed = parse(url); + assume(parsed.hostname).equals(''); + assume(parsed.pathname).equals('\\example.com'); + assume(parsed.href).equals('foo:\\example.com'); + assume(parsed.slashes).is.false(); + + url = 'foo://example.com'; + parsed = parse(url); + assume(parsed.hostname).equals('example.com'); + assume(parsed.pathname).equals(''); + assume(parsed.href).equals('foo://example.com'); + assume(parsed.slashes).is.true(); + + url = 'foo:\\\\example.com'; + parsed = parse(url); + assume(parsed.hostname).equals(''); + assume(parsed.pathname).equals('\\\\example.com'); + assume(parsed.href).equals('foo:\\\\example.com'); + assume(parsed.slashes).is.false(); + + url = 'foo:///example.com'; + parsed = parse(url); + assume(parsed.hostname).equals(''); + assume(parsed.pathname).equals('/example.com'); + assume(parsed.href).equals('foo:///example.com'); + assume(parsed.slashes).is.true(); + + url = 'foo:\\\\\\example.com'; + parsed = parse(url); + assume(parsed.hostname).equals(''); + assume(parsed.pathname).equals('\\\\\\example.com'); + assume(parsed.href).equals('foo:\\\\\\example.com'); + assume(parsed.slashes).is.false(); + + url = '\\\\example.com/foo/bar'; + parsed = parse(url, 'foo://bar.com'); + assume(parsed.hostname).equals('bar.com'); + assume(parsed.pathname).equals('/\\\\example.com/foo/bar'); + assume(parsed.href).equals('foo://bar.com/\\\\example.com/foo/bar'); + assume(parsed.slashes).is.true(); }); describe('origin', function () { @@ -319,6 +423,13 @@ describe('url-parse', function () { assume(parsed.origin).equals('null'); }); + it('is null for non special URLs', function () { + var o = parse('foo://example.com/pathname'); + assume(o.hostname).equals('example.com'); + assume(o.pathname).equals('/pathname'); + assume(o.origin).equals('null'); + }); + it('removes default ports for http', function () { var o = parse('http://google.com:80/pathname'); assume(o.origin).equals('http://google.com'); @@ -438,6 +549,67 @@ describe('url-parse', function () { data.set('protocol', 'https:'); assume(data.href).equals('https://google.com/foo'); }); + + it('handles the file: protocol', function () { + var slashes = ['', '/', '//', '///']; + var data; + var url; + + for (var i = 0; i < slashes.length; i++) { + data = parse('file:' + slashes[i]); + assume(data.protocol).equals('file:'); + assume(data.pathname).equals('/'); + assume(data.href).equals('file:///'); + } + + url = 'file:////'; + data = parse(url); + assume(data.protocol).equals('file:'); + assume(data.pathname).equals('//'); + assume(data.href).equals(url); + + url = 'file://///'; + data = parse(url); + assume(data.protocol).equals('file:'); + assume(data.pathname).equals('///'); + assume(data.href).equals(url); + + url = 'file:///Users/foo/BAR/baz.pdf'; + data = parse(url); + assume(data.protocol).equals('file:'); + assume(data.pathname).equals('/Users/foo/BAR/baz.pdf'); + assume(data.href).equals(url); + + url = 'file:///foo/bar?baz=qux#hash'; + data = parse(url); + assume(data.protocol).equals('file:'); + assume(data.hash).equals('#hash'); + assume(data.query).equals('?baz=qux'); + assume(data.pathname).equals('/foo/bar'); + assume(data.href).equals(url); + + data = parse('file://c:\\foo\\bar\\'); + assume(data.protocol).equals('file:'); + assume(data.pathname).equals('/c:/foo/bar/'); + assume(data.href).equals('file:///c:/foo/bar/'); + + data = parse('file://host/file'); + assume(data.protocol).equals('file:'); + assume(data.host).equals('host'); + assume(data.hostname).equals('host'); + assume(data.pathname).equals('/file'); + assume(data.href).equals('file://host/file'); + + data = parse('foo/bar', 'file:///baz'); + assume(data.protocol).equals('file:'); + assume(data.pathname).equals('/foo/bar'); + assume(data.href).equals('file:///foo/bar'); + + data = parse('foo/bar', 'file:///baz/'); + assume(data.protocol).equals('file:'); + assume(data.pathname).equals('/baz/foo/bar'); + assume(data.href).equals('file:///baz/foo/bar'); + }); }); describe('ip', function () {