From 53b1794e54d0711ceb52505e0f74145270570d5a Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Sun, 29 Jul 2018 14:26:05 +0200 Subject: [PATCH] [security] Sanitize paths, hosts before parsing. --- index.js | 30 +++++++++++++++++++++--------- test/test.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index b88077a..dcc4b41 100644 --- a/index.js +++ b/index.js @@ -20,6 +20,9 @@ var required = require('requires-port') 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('\\', '/'); + }, ['/', 'pathname'], // Extract from the back. ['@', 'auth', 1], // Extract from the front. [NaN, 'host', undefined, 1, 1], // Set left over value. @@ -47,7 +50,7 @@ var ignore = { hash: 1, query: 1 }; * * @param {Object|String} loc Optional default location object. * @returns {Object} lolcation object. - * @api public + * @public */ function lolcation(loc) { var location = global && global.location || {}; @@ -89,7 +92,7 @@ function lolcation(loc) { * * @param {String} address URL we want to extract from. * @return {ProtocolExtract} Extracted information. - * @api private + * @private */ function extractProtocol(address) { var match = protocolre.exec(address); @@ -107,7 +110,7 @@ function extractProtocol(address) { * @param {String} relative Pathname of the relative URL. * @param {String} base Pathname of the base URL. * @return {String} Resolved pathname. - * @api private + * @private */ function resolve(relative, base) { var path = (base || '/').split('/').slice(0, -1).concat(relative.split('/')) @@ -140,11 +143,14 @@ function resolve(relative, base) { * create an actual constructor as it's much more memory efficient and * faster and it pleases my OCD. * + * It is worth noting that we should not use `URL` as class name to prevent + * clashes with the global URL instance that got introduced in browsers. + * * @constructor * @param {String} address URL we want to parse. * @param {Object|String} location Location defaults for relative paths. * @param {Boolean|Function} parser Parser for the query string. - * @api public + * @private */ function Url(address, location, parser) { if (!(this instanceof Url)) { @@ -190,10 +196,16 @@ function Url(address, location, parser) { // When the authority component is absent the URL starts with a path // component. // - if (!extracted.slashes) instructions[2] = [/(.*)/, 'pathname']; + if (!extracted.slashes) instructions[3] = [/(.*)/, 'pathname']; for (; i < instructions.length; i++) { instruction = instructions[i]; + + if (typeof instruction === 'function') { + address = instruction(address); + continue; + } + parse = instruction[0]; key = instruction[1]; @@ -284,8 +296,8 @@ function Url(address, location, parser) { * used to parse the query. * When setting the protocol, double slash will be * removed from the final url if it is true. - * @returns {URL} - * @api public + * @returns {URL} URL instance for chaining. + * @public */ function set(part, value, fn) { var url = this; @@ -370,8 +382,8 @@ function set(part, value, fn) { * Transform the properties back in to a valid and full URL string. * * @param {Function} stringify Optional query stringify function. - * @returns {String} - * @api public + * @returns {String} Compiled version of the URL. + * @public */ function toString(stringify) { if (!stringify || 'function' !== typeof stringify) stringify = qs.stringify; diff --git a/test/test.js b/test/test.js index 8f47aea..e7a8651 100644 --- a/test/test.js +++ b/test/test.js @@ -192,6 +192,28 @@ describe('url-parse', function () { assume(parsed.pathname).equals('/b/c'); }); + it('ignores \\ in pathnames', function () { + var url = 'http://google.com:80\\@yahoo.com/#what\\is going on' + , parsed = parse(url); + + assume(parsed.port).equals(''); + assume(parsed.username).equals(''); + assume(parsed.password).equals(''); + assume(parsed.hostname).equals('google.com'); + assume(parsed.hash).equals('#what\\is going on'); + + parsed = parse('//\\what-is-up.com'); + assume(parsed.pathname).equals('/what-is-up.com'); + }); + + it('correctly ignores multiple slashes //', function () { + var url = '////what-is-up.com' + , parsed = parse(url); + + assume(parsed.host).equals(''); + assume(parsed.hostname).equals(''); + }); + describe('origin', function () { it('generates an origin property', function () { var url = 'http://google.com:80/pathname' @@ -252,6 +274,13 @@ describe('url-parse', function () { o = parse('wss://google.com:80/pathname'); assume(o.origin).equals('wss://google.com:80'); }); + + it('maintains the port number for non-default port numbers', function () { + var parsed = parse('http://google.com:8080/pathname'); + + assume(parsed.host).equals('http://google.com:8080'); + assume(parsed.href).equals('http://google.com:8080/pathname'); + }); }); describe('protocol', function () {