diff --git a/.eslintrc.json b/.eslintrc.json index 948dbdb..cc69745 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,6 @@ { - "ecmaFeatures": { - "modules": true, - "experimentalObjectRestSpread": true + "parserOptions": { + "ecmaVersion": 2020 }, "env": { diff --git a/benchmark.js b/benchmark.js new file mode 100644 index 0000000..ca944ad --- /dev/null +++ b/benchmark.js @@ -0,0 +1,23 @@ +'use strict'; + +if (!global.BigInt) { + console.log('Benchmark requirest 10.x or newer.'); + process.exit(0); +} + +var assert = require('assert'); +var isGlob = require('./'); + +function runBenchmark(length) { + var start = process.hrtime.bigint(); + ['(', '[', '{', '\\'].forEach(function(char) { + isGlob(char.repeat(length)); + isGlob(char.repeat(length), {strict: false}); + }); + return process.hrtime.bigint() - start; +} + +var baseline = runBenchmark(1e6); +console.log('Benchmark took ' + baseline / global.BigInt(1000000) + ' milliseconds.'); + +assert.ok(baseline < global.BigInt(1000000000), 'Benchmark goal is less than one second'); diff --git a/index.js b/index.js index 83abb81..620f563 100644 --- a/index.js +++ b/index.js @@ -7,11 +7,16 @@ var isExtglob = require('is-extglob'); var chars = { '{': '}', '(': ')', '[': ']'}; -var strictCheck = function (str) { +var strictCheck = function(str) { if (str[0] === '!') { return true; } var index = 0; + var pipeIndex = -2; + var closeSquareIndex = -2; + var closeCurlyIndex = -2; + var closeParenIndex = -2; + var backSlashIndex = -2; while (index < str.length) { if (str[index] === '*') { return true; @@ -21,51 +26,58 @@ var strictCheck = function (str) { return true; } - if (str[index] === '[' && str[index + 1] !== ']') { - var closeIndex = str.indexOf(']', index); - if (closeIndex > index) { - var slashIndex = str.indexOf('\\', index); - if (slashIndex === -1 || slashIndex > closeIndex) { + if (closeSquareIndex !== -1 && str[index] === '[' && str[index + 1] !== ']') { + if (closeSquareIndex < index) { + closeSquareIndex = str.indexOf(']', index); + } + if (closeSquareIndex > index) { + if (backSlashIndex === -1 || backSlashIndex > closeSquareIndex) { + return true; + } + backSlashIndex = str.indexOf('\\', index); + if (backSlashIndex === -1 || backSlashIndex > closeSquareIndex) { return true; } } } - if (str[index] === '{' && str[index + 1] !== '}') { - closeIndex = str.indexOf('}', index); - if (closeIndex > index) { - slashIndex = str.indexOf('\\', index); - if (slashIndex === -1 || slashIndex > closeIndex) { + if (closeCurlyIndex !== -1 && str[index] === '{' && str[index + 1] !== '}') { + closeCurlyIndex = str.indexOf('}', index); + if (closeCurlyIndex > index) { + backSlashIndex = str.indexOf('\\', index); + if (backSlashIndex === -1 || backSlashIndex > closeCurlyIndex) { return true; } } } - if (str[index] === '(' && str[index + 1] === '?' && /[:!=]/.test(str[index + 2]) && str[index + 3] !== ')') { - closeIndex = str.indexOf(')', index); - if (closeIndex > index) { - slashIndex = str.indexOf('\\', index); - if (slashIndex === -1 || slashIndex > closeIndex) { + if (closeParenIndex !== -1 && str[index] === '(' && str[index + 1] === '?' && /[:!=]/.test(str[index + 2]) && str[index + 3] !== ')') { + closeParenIndex = str.indexOf(')', index); + if (closeParenIndex > index) { + backSlashIndex = str.indexOf('\\', index); + if (backSlashIndex === -1 || backSlashIndex > closeParenIndex) { return true; } } } - if (str[index] === '(' && str[index + 1] !== '|') { - var pipeIndex = str.indexOf('|', index); - if (pipeIndex > index && str[pipeIndex + 1] !== ')') { - closeIndex = str.indexOf(')', pipeIndex); - if (closeIndex > pipeIndex) { - slashIndex = str.indexOf('\\', pipeIndex); - if (slashIndex === -1 || slashIndex > closeIndex) { - return true; + if (pipeIndex !== -1 && str[index] === '(' && str[index + 1] !== '|') { + if (pipeIndex < index) { + pipeIndex = str.indexOf('|', index); + } + if (pipeIndex !== -1 && str[pipeIndex + 1] !== ')') { + closeParenIndex = str.indexOf(')', pipeIndex); + if (closeParenIndex > pipeIndex) { + backSlashIndex = str.indexOf('\\', pipeIndex); + if (backSlashIndex === -1 || backSlashIndex > closeParenIndex) { + return true; } } } } if (str[index] === '\\') { - var open = str[index+1]; + var open = str[index + 1]; index += 2; var close = chars[open]; @@ -84,9 +96,9 @@ var strictCheck = function (str) { } } return false; -} +}; -var relaxedCheck = function (str) { +var relaxedCheck = function(str) { if (str[0] === '!') { return true; } @@ -97,7 +109,7 @@ var relaxedCheck = function (str) { } if (str[index] === '\\') { - var open = str[index+1]; + var open = str[index + 1]; index += 2; var close = chars[open]; @@ -116,7 +128,7 @@ var relaxedCheck = function (str) { } } return false; -} +}; module.exports = function isGlob(str, options) { if (typeof str !== 'string' || str === '') { @@ -135,4 +147,4 @@ module.exports = function isGlob(str, options) { } return check(str); -} +}; diff --git a/package.json b/package.json index 1664e91..2797ae5 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "node": ">=0.10.0" }, "scripts": { - "test": "mocha" + "test": "mocha && node benchmark.js" }, "dependencies": { "is-extglob": "^2.1.1" diff --git a/test.js b/test.js index dfdd7ff..ccfe581 100644 --- a/test.js +++ b/test.js @@ -7,7 +7,6 @@ 'use strict'; -require('mocha'); var assert = require('assert'); var isGlob = require('./'); @@ -303,20 +302,5 @@ describe('isGlob', function() { assert(isGlob('\\*(abc|xyz)/*(abc|xyz)')); assert(isGlob('\\+(abc|xyz)/+(abc|xyz)')); }); - - it('should be performant and not subject to ReDoS/exponential backtracking', function() { - if (!String.prototype.repeat) { - return; - } - // These will time out if the algorithm is inefficient. - isGlob('('.repeat(1e5)); - isGlob('('.repeat(1e5), {strict: false}); - isGlob('['.repeat(1e5)); - isGlob('['.repeat(1e5), {strict: false}); - isGlob('{'.repeat(1e5)); - isGlob('{'.repeat(1e5), {strict: false}); - isGlob('\\'.repeat(1e5)); - isGlob('\\'.repeat(1e5), {strict: false}); - }); }); });