diff --git a/index.js b/index.js index 1ef5187..1d3e349 100644 --- a/index.js +++ b/index.js @@ -6,10 +6,15 @@ module.exports = isUrl; /** - * Matcher. + * RegExps. + * A URL must match #1 and then at least one of #2/#3. + * Use two levels of REs to avoid REDOS. */ -var matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/; +var protocolAndDomainRE = /^(?:\w+:)?\/\/(\S+)$/; + +var localhostDomainRE = /^localhost[\:?\d]*(?:[^\:?\d]\S*)?$/ +var nonLocalhostDomainRE = /^[^\s\.]+\.\S{2,}$/; /** * Loosely validate a URL `string`. @@ -19,5 +24,20 @@ var matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/; */ function isUrl(string){ - return matcher.test(string); + var match = string.match(protocolAndDomainRE); + if (!match) { + return false; + } + + var everythingAfterProtocol = match[1]; + if (!everythingAfterProtocol) { + return false; + } + + if (localhostDomainRE.test(everythingAfterProtocol) || + nonLocalhostDomainRE.test(everythingAfterProtocol)) { + return true; + } + + return false; } diff --git a/test/index.js b/test/index.js index 5f7aebc..52cfacb 100644 --- a/test/index.js +++ b/test/index.js @@ -119,4 +119,15 @@ describe('is-url', function () { assert(!url('google.com')); }); }); + + describe('redos', function () { + it('redos exploit', function () { + // Invalid. This should be discovered in under 1 second. + var attackString = 'a://localhost' + '9'.repeat(100000) + '\t'; + var before = process.hrtime(); + assert(!url(attackString), 'attackString was valid'); + var elapsed = process.hrtime(before); + assert(elapsed[0] < 1, 'attackString took ' + elapsed[0] + ' > 1 seconds'); + }); + }); });