From f7774f608418b59a43ad1816c954654fdc8b1248 Mon Sep 17 00:00:00 2001
From: Luigi Pinca <luigipinca@gmail.com>
Date: Sun, 9 Jan 2022 07:59:52 +0100
Subject: [PATCH 01/12] [security] Fix typos in SECURITY.md

---
 SECURITY.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/SECURITY.md b/SECURITY.md
index 3a97067..f85d48f 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -33,8 +33,8 @@ 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
+> url-parse mishandles certain uses of a single (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.
 

From 9be7ee88afd2bb04e4d5a1a8da9a389ac13f8c40 Mon Sep 17 00:00:00 2001
From: Luigi Pinca <luigipinca@gmail.com>
Date: Sat, 8 Jan 2022 21:10:56 +0100
Subject: [PATCH 02/12] [fix] Correctly handle userinfo containing the at sign

---
 index.js     | 37 ++++++++++++++++++++++------
 test/test.js | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 98 insertions(+), 7 deletions(-)

diff --git a/index.js b/index.js
index 702308b..b819461 100644
--- a/index.js
+++ b/index.js
@@ -304,7 +304,11 @@ function Url(address, location, parser) {
     if (parse !== parse) {
       url[key] = address;
     } else if ('string' === typeof parse) {
-      if (~(index = address.indexOf(parse))) {
+      index = parse === '@'
+        ? address.lastIndexOf(parse)
+        : address.indexOf(parse);
+
+      if (~index) {
         if ('number' === typeof instruction[2]) {
           url[key] = address.slice(0, index);
           address = address.slice(index + instruction[2]);
@@ -370,10 +374,21 @@ function Url(address, location, parser) {
   // Parse down the `auth` for the username and password.
   //
   url.username = url.password = '';
+
   if (url.auth) {
-    instruction = url.auth.split(':');
-    url.username = instruction[0];
-    url.password = instruction[1] || '';
+    index = url.auth.indexOf(':');
+
+    if (~index) {
+      url.username = url.auth.slice(0, index);
+      url.username = encodeURIComponent(decodeURIComponent(url.username));
+
+      url.password = url.auth.slice(index + 1);
+      url.password = encodeURIComponent(decodeURIComponent(url.password))
+    } else {
+      url.username = encodeURIComponent(decodeURIComponent(url.auth));
+    }
+
+    url.auth = url.password ? url.username +':'+ url.password : url.username;
   }
 
   url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host
@@ -465,9 +480,17 @@ function set(part, value, fn) {
       break;
 
     case 'auth':
-      var splits = value.split(':');
-      url.username = splits[0];
-      url.password = splits.length === 2 ? splits[1] : '';
+      var index = value.indexOf(':');
+
+      if (~index) {
+        url.username = value.slice(0, index);
+        url.username = encodeURIComponent(decodeURIComponent(url.username));
+
+        url.password = value.slice(index + 1);
+        url.password = encodeURIComponent(decodeURIComponent(url.password));
+      } else {
+        url.username = encodeURIComponent(decodeURIComponent(value));
+      }
   }
 
   for (var i = 0; i < rules.length; i++) {
diff --git a/test/test.js b/test/test.js
index 8130081..18f16ef 100644
--- a/test/test.js
+++ b/test/test.js
@@ -689,6 +689,54 @@ describe('url-parse', function () {
       assume(parsed.hostname).equals('www.example.com');
       assume(parsed.href).equals(url);
     });
+
+    it('handles @ in username', function () {
+      var url = 'http://user@@www.example.com/'
+        , parsed = parse(url);
+
+      assume(parsed.protocol).equals('http:');
+      assume(parsed.auth).equals('user%40');
+      assume(parsed.username).equals('user%40');
+      assume(parsed.password).equals('');
+      assume(parsed.hostname).equals('www.example.com');
+      assume(parsed.pathname).equals('/');
+      assume(parsed.href).equals('http://user%40@www.example.com/');
+
+      url = 'http://user%40@www.example.com/';
+      parsed = parse(url);
+
+      assume(parsed.protocol).equals('http:');
+      assume(parsed.auth).equals('user%40');
+      assume(parsed.username).equals('user%40');
+      assume(parsed.password).equals('');
+      assume(parsed.hostname).equals('www.example.com');
+      assume(parsed.pathname).equals('/');
+      assume(parsed.href).equals('http://user%40@www.example.com/');
+    });
+
+    it('handles @ in password', function () {
+      var url = 'http://user@:pas:s@@www.example.com/'
+        , parsed = parse(url);
+
+      assume(parsed.protocol).equals('http:');
+      assume(parsed.auth).equals('user%40:pas%3As%40');
+      assume(parsed.username).equals('user%40');
+      assume(parsed.password).equals('pas%3As%40');
+      assume(parsed.hostname).equals('www.example.com');
+      assume(parsed.pathname).equals('/');
+      assume(parsed.href).equals('http://user%40:pas%3As%40@www.example.com/');
+
+      url = 'http://user%40:pas%3As%40@www.example.com/'
+      parsed = parse(url);
+
+      assume(parsed.protocol).equals('http:');
+      assume(parsed.auth).equals('user%40:pas%3As%40');
+      assume(parsed.username).equals('user%40');
+      assume(parsed.password).equals('pas%3As%40');
+      assume(parsed.hostname).equals('www.example.com');
+      assume(parsed.pathname).equals('/');
+      assume(parsed.href).equals('http://user%40:pas%3As%40@www.example.com/');
+    });
   });
 
   it('accepts multiple ???', function () {
@@ -1124,6 +1172,26 @@ describe('url-parse', function () {
       assume(data.username).equals('');
       assume(data.password).equals('quux');
       assume(data.href).equals('https://:quux@example.com/');
+
+      assume(data.set('auth', 'user@:pass@')).equals(data);
+      assume(data.username).equals('user%40');
+      assume(data.password).equals('pass%40');
+      assume(data.href).equals('https://user%40:pass%40@example.com/');
+
+      assume(data.set('auth', 'user%40:pass%40')).equals(data);
+      assume(data.username).equals('user%40');
+      assume(data.password).equals('pass%40');
+      assume(data.href).equals('https://user%40:pass%40@example.com/');
+
+      assume(data.set('auth', 'user:pass:word')).equals(data);
+      assume(data.username).equals('user');
+      assume(data.password).equals('pass%3Aword');
+      assume(data.href).equals('https://user:pass%3Aword@example.com/');
+
+      assume(data.set('auth', 'user:pass%3Aword')).equals(data);
+      assume(data.username).equals('user');
+      assume(data.password).equals('pass%3Aword');
+      assume(data.href).equals('https://user:pass%3Aword@example.com/');
     });
 
     it('updates other values', function () {

From 4e53a8cad35c25e0004cee3afc1ed37ce47cad83 Mon Sep 17 00:00:00 2001
From: Luigi Pinca <luigipinca@gmail.com>
Date: Fri, 11 Feb 2022 21:15:41 +0100
Subject: [PATCH 03/12] [doc] Document that the returned hostname might be
 invalid

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 94f08d1..b476ed7 100644
--- a/README.md
+++ b/README.md
@@ -80,8 +80,8 @@ The returned `url` instance contains the following properties:
 - `auth`: Authentication information portion (e.g. `username:password`).
 - `username`: Username of basic authentication.
 - `password`: Password of basic authentication.
-- `host`: Host name with port number.
-- `hostname`: Host name without port number.
+- `host`: Host name with port number. The hostname might be invalid.
+- `hostname`: Host name without port number. This might be an invalid hostname.
 - `port`: Optional port number.
 - `pathname`: URL path.
 - `query`: Parsed object containing query string, unless parsing is set to false.

From 319851bf1c294796fc73e29ff31b14d9084e4a0d Mon Sep 17 00:00:00 2001
From: Luigi Pinca <luigipinca@gmail.com>
Date: Sun, 13 Feb 2022 08:53:54 +0100
Subject: [PATCH 04/12] [fix] Remove CR, HT, and LF

Copy the behavior of browser `URL` interface and remove CR, HT, and LF
from the input URL.
---
 index.js     |  3 +++
 test/test.js | 38 ++++++++++++++++++++++++++++++++++++--
 2 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/index.js b/index.js
index 702308b..4cd646f 100644
--- a/index.js
+++ b/index.js
@@ -2,6 +2,7 @@
 
 var required = require('requires-port')
   , qs = require('querystringify')
+  , CRHTLF = /[\n\r\t]/g
   , slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\/\//
   , protocolre = /^([a-z][a-z0-9.+-]*:)?(\/\/)?([\\/]+)?([\S\s]*)/i
   , windowsDriveLetter = /^[a-zA-Z]:/
@@ -135,6 +136,7 @@ function isSpecial(scheme) {
  */
 function extractProtocol(address, location) {
   address = trimLeft(address);
+  address = address.replace(CRHTLF, '');
   location = location || {};
 
   var match = protocolre.exec(address);
@@ -235,6 +237,7 @@ function resolve(relative, base) {
  */
 function Url(address, location, parser) {
   address = trimLeft(address);
+  address = address.replace(CRHTLF, '');
 
   if (!(this instanceof Url)) {
     return new Url(address, location, parser);
diff --git a/test/test.js b/test/test.js
index 8130081..2799280 100644
--- a/test/test.js
+++ b/test/test.js
@@ -102,7 +102,7 @@ describe('url-parse', function () {
     });
 
     it('does not truncate the input string', function () {
-      var input = 'foo\nbar\rbaz\u2028qux\u2029';
+      var input = 'foo\x0bbar\x0cbaz\u2028qux\u2029';
 
       assume(parse.extractProtocol(input)).eql({
         slashes: false,
@@ -113,7 +113,16 @@ describe('url-parse', function () {
     });
 
     it('trimsLeft', function () {
-      assume(parse.extractProtocol(' javascript://foo')).eql({
+      assume(parse.extractProtocol('\x0b\x0c javascript://foo')).eql({
+        slashes: true,
+        protocol: 'javascript:',
+        rest: 'foo',
+        slashesCount: 2
+      });
+    });
+
+    it('removes CR, HT, and LF', function () {
+      assume(parse.extractProtocol('jav\n\rasc\nript\r:/\t/fo\no')).eql({
         slashes: true,
         protocol: 'javascript:',
         rest: 'foo',
@@ -408,6 +417,31 @@ describe('url-parse', function () {
     assume(parsed.href).equals('//example.com');
   });
 
+  it('removes CR, HT, and LF', function () {
+    var parsed = parse(
+      'ht\ntp://a\rb:\tcd@exam\rple.com:80\t80/pat\thname?fo\no=b\rar#ba\tz'
+    );
+
+    assume(parsed.protocol).equals('http:');
+    assume(parsed.username).equals('ab');
+    assume(parsed.password).equals('cd');
+    assume(parsed.host).equals('example.com:8080');
+    assume(parsed.hostname).equals('example.com');
+    assume(parsed.port).equals('8080');
+    assume(parsed.pathname).equals('/pathname');
+    assume(parsed.query).equals('?foo=bar');
+    assume(parsed.hash).equals('#baz');
+    assume(parsed.href).equals(
+      'http://ab:cd@example.com:8080/pathname?foo=bar#baz'
+    );
+
+    parsed = parse('s\nip:al\rice@atl\tanta.com');
+
+    assume(parsed.protocol).equals('sip:');
+    assume(parsed.pathname).equals('alice@atlanta.com');
+    assume(parsed.href).equals('sip:alice@atlanta.com');
+  });
+
   describe('origin', function () {
     it('generates an origin property', function () {
       var url = 'http://google.com:80/pathname'

From 193b44baf3d203560735e05eedc99d8244c9e16c Mon Sep 17 00:00:00 2001
From: Luigi Pinca <luigipinca@gmail.com>
Date: Sun, 13 Feb 2022 09:14:05 +0100
Subject: [PATCH 05/12] [minor] Simplify whitespace regex

---
 index.js | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/index.js b/index.js
index 4cd646f..02c86e7 100644
--- a/index.js
+++ b/index.js
@@ -6,8 +6,7 @@ var required = require('requires-port')
   , 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 +'+');
+  , whitespace = /^[ \f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/;
 
 /**
  * Trim a given string.
@@ -16,7 +15,7 @@ var required = require('requires-port')
  * @public
  */
 function trimLeft(str) {
-  return (str ? str : '').toString().replace(left, '');
+  return (str ? str : '').toString().replace(whitespace, '');
 }
 
 /**

From e4a5807d95b971577e4d888f5b99d64a40851386 Mon Sep 17 00:00:00 2001
From: Martijn Swaagman <martijn@swaagman.online>
Date: Sun, 13 Feb 2022 11:37:14 +0100
Subject: [PATCH 06/12] 1.5.5

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b777acb..f079c10 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "url-parse",
-  "version": "1.5.4",
+  "version": "1.5.5",
   "description": "Small footprint URL parser that works seamlessly across Node.js and browser environments",
   "main": "index.js",
   "scripts": {

From 4c9fa234c01dca52698666378360ad2fdfb05470 Mon Sep 17 00:00:00 2001
From: Martijn Swaagman <martijn@swaagman.online>
Date: Sun, 13 Feb 2022 16:25:42 +0100
Subject: [PATCH 07/12] 1.5.6

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index f079c10..cf472f5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "url-parse",
-  "version": "1.5.5",
+  "version": "1.5.6",
   "description": "Small footprint URL parser that works seamlessly across Node.js and browser environments",
   "main": "index.js",
   "scripts": {

From e6fa43422c52f34c73146552ec9916125dc59525 Mon Sep 17 00:00:00 2001
From: Luigi Pinca <luigipinca@gmail.com>
Date: Sun, 13 Feb 2022 20:21:27 +0100
Subject: [PATCH 08/12] [security] Add credits for incorrect handling of
 userinfo vulnerability

---
 SECURITY.md | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/SECURITY.md b/SECURITY.md
index f85d48f..f3e7892 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -33,6 +33,14 @@ acknowledge your responsible disclosure, if you wish.
 
 ## History
 
+> Incorrect handling of username and password can lead to authorization bypass.
+
+- **Reporter credits**
+  - ranjit-git
+  - GitHub: [@ranjit-git](https://github.com/ranjit-git)
+- Huntr report: https://www.huntr.dev/bounties/6d1bc51f-1876-4f5b-a2c2-734e09e8e05b/
+- Fixed in: 1.5.6
+
 > url-parse mishandles certain uses of a single (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

From 78e9f2f41285d83e7d91706be5bd439656fe3bc3 Mon Sep 17 00:00:00 2001
From: Luigi Pinca <luigipinca@gmail.com>
Date: Sun, 13 Feb 2022 20:40:49 +0100
Subject: [PATCH 09/12] [security] Fix nits

---
 SECURITY.md | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/SECURITY.md b/SECURITY.md
index f3e7892..af05717 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -41,14 +41,16 @@ acknowledge your responsible disclosure, if you wish.
 - Huntr report: https://www.huntr.dev/bounties/6d1bc51f-1876-4f5b-a2c2-734e09e8e05b/
 - Fixed in: 1.5.6
 
+---
+
 > url-parse mishandles certain uses of a single (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)
+  - ready-research
+  - GitHub: [@ready-research](https://github.com/ready-research)
 - Huntr report: https://www.huntr.dev/bounties/1625557993985-unshiftio/url-parse/
 - Fixed in: 1.5.2
 

From 88df2346855f70cec9713b362ca32a4691dc271a Mon Sep 17 00:00:00 2001
From: Luigi Pinca <luigipinca@gmail.com>
Date: Wed, 16 Feb 2022 19:00:41 +0100
Subject: [PATCH 10/12] [doc] Add soft deprecation notice

---
 README.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/README.md b/README.md
index b476ed7..e5bf8d7 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,12 @@
 
 [![Sauce Test Status](https://saucelabs.com/browser-matrix/url-parse.svg)](https://saucelabs.com/u/url-parse)
 
+**`url-parse` was created in 2014 when the WHATWG URL API was not available in
+Node.js and the `URL` interface was supported only in some browsers. Today this
+is no longer true. The `URL` interface is available in all supported Node.js
+release lines and basically all browsers. Consider using it for better security
+and accuracy.**
+
 The `url-parse` method exposes two different API interfaces. The
 [`url`](https://nodejs.org/api/url.html) interface that you know from Node.js
 and the new [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL)

From ef45a1355375a8244063793a19059b4f62fc8788 Mon Sep 17 00:00:00 2001
From: Luigi Pinca <luigipinca@gmail.com>
Date: Wed, 16 Feb 2022 19:05:36 +0100
Subject: [PATCH 11/12] [fix] Readd the empty userinfo to `url.href` (#226)

If the userinfo is present but empty, the parsed host is also empty, and
`url.pathname` is not `'/'`, then readd the empty userinfo to `url.href`,
otherwise the original invalid URL might be transformed into a valid one
with `url.pathname` as host.
---
 index.js     | 11 ++++++++++
 test/test.js | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 70 insertions(+)

diff --git a/index.js b/index.js
index 517b6b6..d808b13 100644
--- a/index.js
+++ b/index.js
@@ -539,6 +539,17 @@ function toString(stringify) {
   } else if (url.password) {
     result += ':'+ url.password;
     result += '@';
+  } else if (
+    url.protocol !== 'file:' &&
+    isSpecial(url.protocol) &&
+    !url.host &&
+    url.pathname !== '/'
+  ) {
+    //
+    // Add back the empty userinfo, otherwise the original invalid URL
+    // might be transformed into a valid one with `url.pathname` as host.
+    //
+    result += '@';
   }
 
   result += url.host + url.pathname;
diff --git a/test/test.js b/test/test.js
index 6d53eb9..98880b3 100644
--- a/test/test.js
+++ b/test/test.js
@@ -771,6 +771,65 @@ describe('url-parse', function () {
       assume(parsed.pathname).equals('/');
       assume(parsed.href).equals('http://user%40:pas%3As%40@www.example.com/');
     });
+
+    it('adds @ to href if auth and host are empty', function () {
+      var parsed, i = 0;
+      var urls = [
+        'http:@/127.0.0.1',
+        'http::@/127.0.0.1',
+        'http:/@/127.0.0.1',
+        'http:/:@/127.0.0.1',
+        'http://@/127.0.0.1',
+        'http://:@/127.0.0.1',
+        'http:///@/127.0.0.1',
+        'http:///:@/127.0.0.1'
+      ];
+
+      for (; i < urls.length; i++) {
+        parsed = parse(urls[i]);
+
+        assume(parsed.protocol).equals('http:');
+        assume(parsed.auth).equals('');
+        assume(parsed.username).equals('');
+        assume(parsed.password).equals('');
+        assume(parsed.host).equals('');
+        assume(parsed.hostname).equals('');
+        assume(parsed.pathname).equals('/127.0.0.1');
+        assume(parsed.origin).equals('null');
+        assume(parsed.href).equals('http://@/127.0.0.1');
+        assume(parsed.toString()).equals('http://@/127.0.0.1');
+      }
+
+      urls = [
+        'http:@/',
+        'http:@',
+        'http::@/',
+        'http::@',
+        'http:/@/',
+        'http:/@',
+        'http:/:@/',
+        'http:/:@',
+        'http://@/',
+        'http://@',
+        'http://:@/',
+        'http://:@'
+      ];
+
+      for (i = 0; i < urls.length; i++) {
+        parsed = parse(urls[i]);
+
+        assume(parsed.protocol).equals('http:');
+        assume(parsed.auth).equals('');
+        assume(parsed.username).equals('');
+        assume(parsed.password).equals('');
+        assume(parsed.host).equals('');
+        assume(parsed.hostname).equals('');
+        assume(parsed.pathname).equals('/');
+        assume(parsed.origin).equals('null');
+        assume(parsed.href).equals('http:///');
+        assume(parsed.toString()).equals('http:///');
+      }
+    });
   });
 
   it('accepts multiple ???', function () {

From 8b3f5f2c88a4cfc2880f2319c307994cb25bb10a Mon Sep 17 00:00:00 2001
From: Martijn Swaagman <martijn@swaagman.online>
Date: Wed, 16 Feb 2022 21:02:02 +0100
Subject: [PATCH 12/12] 1.5.7

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index cf472f5..7bddf4e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "url-parse",
-  "version": "1.5.6",
+  "version": "1.5.7",
   "description": "Small footprint URL parser that works seamlessly across Node.js and browser environments",
   "main": "index.js",
   "scripts": {