[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#991737: marked as done (unblock: node-url-parse/1.5.3-1)



Your message dated Sun, 1 Aug 2021 13:37:28 +0200
with message-id <CAM8zJQv2HfdZ7_sm3TRBCwNj6Cbz2CZPrZ-+Hi1Ea-W9dy8f3w@mail.gmail.com>
and subject line Re: Bug#991737: unblock: node-url-parse/1.5.3-1
has caused the Debian Bug report #991737,
regarding unblock: node-url-parse/1.5.3-1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
991737: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991737
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
User: release.debian.org@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
 
-[![Made by unshift](https://img.shields.io/badge/made%20by-unshift-00ffcc.svg?style=flat-square)](http://unshift.io)[![Version npm](https://img.shields.io/npm/v/url-parse.svg?style=flat-square)](https://www.npmjs.com/package/url-parse)[![Build Status](https://img.shields.io/travis/unshiftio/url-parse/master.svg?style=flat-square)](https://travis-ci.org/unshiftio/url-parse)[![Dependencies](https://img.shields.io/david/unshiftio/url-parse.svg?style=flat-square)](https://david-dm.org/unshiftio/url-parse)[![Coverage Status](https://img.shields.io/coveralls/unshiftio/url-parse/master.svg?style=flat-square)](https://coveralls.io/r/unshiftio/url-parse?branch=master)[![IRC channel](https://img.shields.io/badge/IRC-irc.freenode.net%23unshift-00a8ff.svg?style=flat-square)](https://webchat.freenode.net/?channels=unshift)
+[![Made by unshift](https://img.shields.io/badge/made%20by-unshift-00ffcc.svg?style=flat-square)](http://unshift.io)[![Version npm](https://img.shields.io/npm/v/url-parse.svg?style=flat-square)](https://www.npmjs.com/package/url-parse)[![Build Status](https://img.shields.io/github/workflow/status/unshiftio/url-parse/CI/master?label=CI&style=flat-square)](https://github.com/unshiftio/url-parse/actions?query=workflow%3ACI+branch%3Amaster)[![Dependencies](https://img.shields.io/david/unshiftio/url-parse.svg?style=flat-square)](https://david-dm.org/unshiftio/url-parse)[![Coverage Status](https://img.shields.io/coveralls/unshiftio/url-parse/master.svg?style=flat-square)](https://coveralls.io/r/unshiftio/url-parse?branch=master)[![IRC channel](https://img.shields.io/badge/IRC-irc.freenode.net%23unshift-00a8ff.svg?style=flat-square)](https://webchat.freenode.net/?channels=unshift)
 
 [![Sauce Test Status](https://saucelabs.com/browser-matrix/url-parse.svg)](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 <yadd@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 () {

--- End Message ---
--- Begin Message ---
Unblocked.

--- End Message ---

Reply to: