+4
-0
@@ -6,2 +6,4 @@ 'use strict'; | ||
| const DEFAULT_MAX_EXTGLOB_RECURSION = 0; | ||
| /** | ||
@@ -73,2 +75,3 @@ * Posix glob regex | ||
| const POSIX_REGEX_SOURCE = { | ||
| __proto__: null, | ||
| alnum: 'a-zA-Z0-9', | ||
@@ -91,2 +94,3 @@ alpha: 'a-zA-Z', | ||
| module.exports = { | ||
| DEFAULT_MAX_EXTGLOB_RECURSION, | ||
| MAX_LENGTH: 1024 * 64, | ||
@@ -93,0 +97,0 @@ POSIX_REGEX_SOURCE, |
+301
-0
@@ -48,2 +48,273 @@ 'use strict'; | ||
| const splitTopLevel = input => { | ||
| const parts = []; | ||
| let bracket = 0; | ||
| let paren = 0; | ||
| let quote = 0; | ||
| let value = ''; | ||
| let escaped = false; | ||
| for (const ch of input) { | ||
| if (escaped === true) { | ||
| value += ch; | ||
| escaped = false; | ||
| continue; | ||
| } | ||
| if (ch === '\\') { | ||
| value += ch; | ||
| escaped = true; | ||
| continue; | ||
| } | ||
| if (ch === '"') { | ||
| quote = quote === 1 ? 0 : 1; | ||
| value += ch; | ||
| continue; | ||
| } | ||
| if (quote === 0) { | ||
| if (ch === '[') { | ||
| bracket++; | ||
| } else if (ch === ']' && bracket > 0) { | ||
| bracket--; | ||
| } else if (bracket === 0) { | ||
| if (ch === '(') { | ||
| paren++; | ||
| } else if (ch === ')' && paren > 0) { | ||
| paren--; | ||
| } else if (ch === '|' && paren === 0) { | ||
| parts.push(value); | ||
| value = ''; | ||
| continue; | ||
| } | ||
| } | ||
| } | ||
| value += ch; | ||
| } | ||
| parts.push(value); | ||
| return parts; | ||
| }; | ||
| const isPlainBranch = branch => { | ||
| let escaped = false; | ||
| for (const ch of branch) { | ||
| if (escaped === true) { | ||
| escaped = false; | ||
| continue; | ||
| } | ||
| if (ch === '\\') { | ||
| escaped = true; | ||
| continue; | ||
| } | ||
| if (/[?*+@!()[\]{}]/.test(ch)) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| }; | ||
| const normalizeSimpleBranch = branch => { | ||
| let value = branch.trim(); | ||
| let changed = true; | ||
| while (changed === true) { | ||
| changed = false; | ||
| if (/^@\([^\\()[\]{}|]+\)$/.test(value)) { | ||
| value = value.slice(2, -1); | ||
| changed = true; | ||
| } | ||
| } | ||
| if (!isPlainBranch(value)) { | ||
| return; | ||
| } | ||
| return value.replace(/\\(.)/g, '$1'); | ||
| }; | ||
| const hasRepeatedCharPrefixOverlap = branches => { | ||
| const values = branches.map(normalizeSimpleBranch).filter(Boolean); | ||
| for (let i = 0; i < values.length; i++) { | ||
| for (let j = i + 1; j < values.length; j++) { | ||
| const a = values[i]; | ||
| const b = values[j]; | ||
| const char = a[0]; | ||
| if (!char || a !== char.repeat(a.length) || b !== char.repeat(b.length)) { | ||
| continue; | ||
| } | ||
| if (a === b || a.startsWith(b) || b.startsWith(a)) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| }; | ||
| const parseRepeatedExtglob = (pattern, requireEnd = true) => { | ||
| if ((pattern[0] !== '+' && pattern[0] !== '*') || pattern[1] !== '(') { | ||
| return; | ||
| } | ||
| let bracket = 0; | ||
| let paren = 0; | ||
| let quote = 0; | ||
| let escaped = false; | ||
| for (let i = 1; i < pattern.length; i++) { | ||
| const ch = pattern[i]; | ||
| if (escaped === true) { | ||
| escaped = false; | ||
| continue; | ||
| } | ||
| if (ch === '\\') { | ||
| escaped = true; | ||
| continue; | ||
| } | ||
| if (ch === '"') { | ||
| quote = quote === 1 ? 0 : 1; | ||
| continue; | ||
| } | ||
| if (quote === 1) { | ||
| continue; | ||
| } | ||
| if (ch === '[') { | ||
| bracket++; | ||
| continue; | ||
| } | ||
| if (ch === ']' && bracket > 0) { | ||
| bracket--; | ||
| continue; | ||
| } | ||
| if (bracket > 0) { | ||
| continue; | ||
| } | ||
| if (ch === '(') { | ||
| paren++; | ||
| continue; | ||
| } | ||
| if (ch === ')') { | ||
| paren--; | ||
| if (paren === 0) { | ||
| if (requireEnd === true && i !== pattern.length - 1) { | ||
| return; | ||
| } | ||
| return { | ||
| type: pattern[0], | ||
| body: pattern.slice(2, i), | ||
| end: i | ||
| }; | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| const getStarExtglobSequenceOutput = pattern => { | ||
| let index = 0; | ||
| const chars = []; | ||
| while (index < pattern.length) { | ||
| const match = parseRepeatedExtglob(pattern.slice(index), false); | ||
| if (!match || match.type !== '*') { | ||
| return; | ||
| } | ||
| const branches = splitTopLevel(match.body).map(branch => branch.trim()); | ||
| if (branches.length !== 1) { | ||
| return; | ||
| } | ||
| const branch = normalizeSimpleBranch(branches[0]); | ||
| if (!branch || branch.length !== 1) { | ||
| return; | ||
| } | ||
| chars.push(branch); | ||
| index += match.end + 1; | ||
| } | ||
| if (chars.length < 1) { | ||
| return; | ||
| } | ||
| const source = chars.length === 1 | ||
| ? utils.escapeRegex(chars[0]) | ||
| : `[${chars.map(ch => utils.escapeRegex(ch)).join('')}]`; | ||
| return `${source}*`; | ||
| }; | ||
| const repeatedExtglobRecursion = pattern => { | ||
| let depth = 0; | ||
| let value = pattern.trim(); | ||
| let match = parseRepeatedExtglob(value); | ||
| while (match) { | ||
| depth++; | ||
| value = match.body.trim(); | ||
| match = parseRepeatedExtglob(value); | ||
| } | ||
| return depth; | ||
| }; | ||
| const analyzeRepeatedExtglob = (body, options) => { | ||
| if (options.maxExtglobRecursion === false) { | ||
| return { risky: false }; | ||
| } | ||
| const max = | ||
| typeof options.maxExtglobRecursion === 'number' | ||
| ? options.maxExtglobRecursion | ||
| : constants.DEFAULT_MAX_EXTGLOB_RECURSION; | ||
| const branches = splitTopLevel(body).map(branch => branch.trim()); | ||
| if (branches.length > 1) { | ||
| if ( | ||
| branches.some(branch => branch === '') || | ||
| branches.some(branch => /^[*?]+$/.test(branch)) || | ||
| hasRepeatedCharPrefixOverlap(branches) | ||
| ) { | ||
| return { risky: true }; | ||
| } | ||
| } | ||
| for (const branch of branches) { | ||
| const safeOutput = getStarExtglobSequenceOutput(branch); | ||
| if (safeOutput) { | ||
| return { risky: true, safeOutput }; | ||
| } | ||
| if (repeatedExtglobRecursion(branch) > max) { | ||
| return { risky: true }; | ||
| } | ||
| } | ||
| return { risky: false }; | ||
| }; | ||
| /** | ||
@@ -229,2 +500,4 @@ * Parse the given input string. | ||
| token.output = state.output; | ||
| token.startIndex = state.index; | ||
| token.tokensIndex = tokens.length; | ||
| const output = (opts.capture ? '(' : '') + token.open; | ||
@@ -239,2 +512,30 @@ | ||
| const extglobClose = token => { | ||
| const literal = input.slice(token.startIndex, state.index + 1); | ||
| const body = input.slice(token.startIndex + 2, state.index); | ||
| const analysis = analyzeRepeatedExtglob(body, opts); | ||
| if ((token.type === 'plus' || token.type === 'star') && analysis.risky) { | ||
| const safeOutput = analysis.safeOutput | ||
| ? (token.output ? '' : ONE_CHAR) + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput) | ||
| : undefined; | ||
| const open = tokens[token.tokensIndex]; | ||
| open.type = 'text'; | ||
| open.value = literal; | ||
| open.output = safeOutput || utils.escapeRegex(literal); | ||
| for (let i = token.tokensIndex + 1; i < tokens.length; i++) { | ||
| tokens[i].value = ''; | ||
| tokens[i].output = ''; | ||
| delete tokens[i].suffix; | ||
| } | ||
| state.output = token.output + open.output; | ||
| state.backtrack = true; | ||
| push({ type: 'paren', extglob: true, value, output: '' }); | ||
| decrement('parens'); | ||
| return; | ||
| } | ||
| let output = token.close + (opts.capture ? ')' : ''); | ||
@@ -241,0 +542,0 @@ let rest; |
+11
-3
@@ -236,2 +236,10 @@ 'use strict'; | ||
| * | ||
| * ```js | ||
| * const picomatch = require('picomatch'); | ||
| * const state = picomatch.parse('*.js'); | ||
| * // picomatch.compileRe(state[, options]); | ||
| * | ||
| * console.log(picomatch.compileRe(state)); | ||
| * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ | ||
| * ``` | ||
| * @param {Object} `state` | ||
@@ -272,6 +280,6 @@ * @param {Object} `options` | ||
| * const picomatch = require('picomatch'); | ||
| * const state = picomatch.parse('*.js'); | ||
| * // picomatch.compileRe(state[, options]); | ||
| * // picomatch.makeRe(state[, options]); | ||
| * | ||
| * console.log(picomatch.compileRe(state)); | ||
| * const result = picomatch.makeRe('*.js'); | ||
| * console.log(result); | ||
| * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ | ||
@@ -278,0 +286,0 @@ * ``` |
+2
-3
| { | ||
| "name": "picomatch", | ||
| "description": "Blazing fast and accurate glob matcher written in JavaScript, with no dependencies and full support for standard and extended Bash glob features, including braces, extglobs, POSIX brackets, and regular expressions.", | ||
| "version": "4.0.3", | ||
| "version": "4.0.4", | ||
| "homepage": "https://github.com/micromatch/picomatch", | ||
@@ -35,4 +35,3 @@ "author": "Jon Schlinkert (https://github.com/jonschlinkert)", | ||
| "mocha": "^10.4.0", | ||
| "nyc": "^15.1.0", | ||
| "time-require": "github:jonschlinkert/time-require" | ||
| "nyc": "^15.1.0" | ||
| }, | ||
@@ -39,0 +38,0 @@ "keywords": [ |
+41
-30
@@ -268,2 +268,13 @@ <h1 align="center">Picomatch</h1> | ||
| **Example** | ||
| ```js | ||
| const picomatch = require('picomatch'); | ||
| const state = picomatch.parse('*.js'); | ||
| // picomatch.compileRe(state[, options]); | ||
| console.log(picomatch.compileRe(state)); | ||
| //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ | ||
| ``` | ||
| ### [.makeRe](lib/picomatch.js#L285) | ||
@@ -285,6 +296,6 @@ | ||
| const picomatch = require('picomatch'); | ||
| const state = picomatch.parse('*.js'); | ||
| // picomatch.compileRe(state[, options]); | ||
| // picomatch.makeRe(state[, options]); | ||
| console.log(picomatch.compileRe(state)); | ||
| const result = picomatch.makeRe('*.js'); | ||
| console.log(result); | ||
| //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ | ||
@@ -328,7 +339,5 @@ ``` | ||
| | `contains` | `boolean` | `undefined` | Allows glob to match any part of the given string(s). | | ||
| | `cwd` | `string` | `process.cwd()` | Current working directory. Used by `picomatch.split()` | | ||
| | `debug` | `boolean` | `undefined` | Debug regular expressions when an error is thrown. | | ||
| | `dot` | `boolean` | `false` | Enable dotfile matching. By default, dotfiles are ignored unless a `.` is explicitly defined in the pattern, or `options.dot` is true | | ||
| | `expandRange` | `function` | `undefined` | Custom function for expanding ranges in brace patterns, such as `{a..z}`. The function receives the range values as two arguments, and it must return a string to be used in the generated regex. It's recommended that returned strings be wrapped in parentheses. | | ||
| | `failglob` | `boolean` | `false` | Throws an error if no matches are found. Based on the bash option of the same name. | | ||
| | `fastpaths` | `boolean` | `true` | To speed up processing, full parsing is skipped for a handful common glob patterns. Disable this behavior by setting this option to `false`. | | ||
@@ -342,6 +351,6 @@ | `flags` | `string` | `undefined` | Regex flags to use in the generated regex. If defined, the `nocase` option will be overridden. | | ||
| | `maxLength` | `number` | `65536` | Limit the max length of the input string. An error is thrown if the input string is longer than this value. | | ||
| | `maxExtglobRecursion` | `number\|boolean` | `0` | Limit nested quantified extglobs and other risky repeated extglob forms. When the limit is exceeded, the extglob is treated as a literal string instead of being compiled to regex. Set to `false` to disable this safeguard. | | ||
| | `nobrace` | `boolean` | `false` | Disable brace matching, so that `{a,b}` and `{1..3}` would be treated as literal characters. | | ||
| | `nobracket` | `boolean` | `undefined` | Disable matching with regex brackets. | | ||
| | `nocase` | `boolean` | `false` | Make matching case-insensitive. Equivalent to the regex `i` flag. Note that this option is overridden by the `flags` option. | | ||
| | `nodupes` | `boolean` | `true` | Deprecated, use `nounique` instead. This option will be removed in a future major release. By default duplicates are removed. Disable uniquification by setting this option to false. | | ||
| | `noext` | `boolean` | `false` | Alias for `noextglob` | | ||
@@ -351,3 +360,2 @@ | `noextglob` | `boolean` | `false` | Disable support for matching with extglobs (like `+(a\|b)`) | | ||
| | `nonegate` | `boolean` | `false` | Disable support for negating with leading `!` | | ||
| | `noquantifiers` | `boolean` | `false` | Disable support for regex quantifiers (like `a{1,2}`) and treat them as brace patterns to be expanded. | | ||
| | [onIgnore](#optionsonIgnore) | `function` | `undefined` | Function to be called on ignored items. | | ||
@@ -357,3 +365,2 @@ | [onMatch](#optionsonMatch) | `function` | `undefined` | Function to be called on matched items. | | ||
| | `posix` | `boolean` | `false` | Support POSIX character classes ("posix brackets"). | | ||
| | `posixSlashes` | `boolean` | `undefined` | Convert all slashes in file paths to forward slashes. This does not convert slashes in the glob pattern itself | | ||
| | `prepend` | `boolean` | `undefined` | String to prepend to the generated regex used for matching. | | ||
@@ -364,3 +371,2 @@ | `regex` | `boolean` | `false` | Use regular expression rules for `+` (instead of matching literal `+`), and for stars that follow closing parentheses or brackets (as in `)*` and `]*`). | | ||
| | `unescape` | `boolean` | `undefined` | Remove backslashes preceding escaped characters in the glob pattern. By default, backslashes are retained. | | ||
| | `unixify` | `boolean` | `undefined` | Alias for `posixSlashes`, for backwards compatibility. | | ||
| | `windows` | `boolean` | `false` | Also accept backslashes as the path separator. | | ||
@@ -559,2 +565,9 @@ | ||
| console.log(pm.isMatch('foo.bar', '!(!(foo)).!(!(bar))')); // true | ||
| // risky quantified extglobs are treated literally by default | ||
| console.log(pm.makeRe('+(a|aa)')); | ||
| //=> /^(?:\+\(a\|aa\))$/ | ||
| // increase the limit to allow a small amount of nested quantified extglobs | ||
| console.log(pm.isMatch('aaa', '+(+(a))', { maxExtglobRecursion: 1 })); // true | ||
| ``` | ||
@@ -596,3 +609,3 @@ | ||
| Picomatch does not do brace expansion. For [brace expansion](https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html) and advanced matching with braces, use [micromatch](https://github.com/micromatch/micromatch) instead. Picomatch has very basic support for braces. | ||
| Picomatch only does [brace expansion](https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html) of comma-delimited lists (e.g. `a/{b,c}/d`). For advanced matching with braces, use [micromatch](https://github.com/micromatch/micromatch), which supports advanced syntax such as ranges (e.g. `{01..03}`) and increments (e.g. `{2..10..2}`). | ||
@@ -641,40 +654,38 @@ ### Matching special characters as literals | ||
| _(Pay special attention to the last three benchmarks. Minimatch freezes on long ranges.)_ | ||
| ``` | ||
| # .makeRe star (*) | ||
| picomatch x 4,449,159 ops/sec ±0.24% (97 runs sampled) | ||
| minimatch x 632,772 ops/sec ±0.14% (98 runs sampled) | ||
| picomatch x 3,251,247 ops/sec ±0.25% (95 runs sampled) | ||
| minimatch x 497,224 ops/sec ±0.11% (100 runs sampled) | ||
| # .makeRe star; dot=true (*) | ||
| picomatch x 3,500,079 ops/sec ±0.26% (99 runs sampled) | ||
| minimatch x 564,916 ops/sec ±0.23% (96 runs sampled) | ||
| picomatch x 2,624,035 ops/sec ±0.16% (98 runs sampled) | ||
| minimatch x 446,244 ops/sec ±0.63% (99 runs sampled) | ||
| # .makeRe globstar (**) | ||
| picomatch x 3,261,000 ops/sec ±0.27% (98 runs sampled) | ||
| minimatch x 1,664,766 ops/sec ±0.20% (100 runs sampled) | ||
| picomatch x 2,524,465 ops/sec ±0.13% (99 runs sampled) | ||
| minimatch x 1,396,257 ops/sec ±0.58% (96 runs sampled) | ||
| # .makeRe globstars (**/**/**) | ||
| picomatch x 3,284,469 ops/sec ±0.18% (97 runs sampled) | ||
| minimatch x 1,435,880 ops/sec ±0.34% (95 runs sampled) | ||
| picomatch x 2,545,674 ops/sec ±0.10% (99 runs sampled) | ||
| minimatch x 1,196,835 ops/sec ±0.63% (98 runs sampled) | ||
| # .makeRe with leading star (*.txt) | ||
| picomatch x 3,100,197 ops/sec ±0.35% (99 runs sampled) | ||
| minimatch x 428,347 ops/sec ±0.42% (94 runs sampled) | ||
| picomatch x 2,537,708 ops/sec ±0.11% (100 runs sampled) | ||
| minimatch x 345,284 ops/sec ±0.64% (96 runs sampled) | ||
| # .makeRe - basic braces ({a,b,c}*.txt) | ||
| picomatch x 443,578 ops/sec ±1.33% (89 runs sampled) | ||
| minimatch x 107,143 ops/sec ±0.35% (94 runs sampled) | ||
| picomatch x 505,430 ops/sec ±1.04% (94 runs sampled) | ||
| minimatch x 107,991 ops/sec ±0.54% (99 runs sampled) | ||
| # .makeRe - short ranges ({a..z}*.txt) | ||
| picomatch x 415,484 ops/sec ±0.76% (96 runs sampled) | ||
| minimatch x 14,299 ops/sec ±0.26% (96 runs sampled) | ||
| picomatch x 371,179 ops/sec ±2.91% (77 runs sampled) | ||
| minimatch x 14,104 ops/sec ±0.61% (99 runs sampled) | ||
| # .makeRe - medium ranges ({1..100000}*.txt) | ||
| picomatch x 395,020 ops/sec ±0.87% (89 runs sampled) | ||
| minimatch x 2 ops/sec ±4.59% (10 runs sampled) | ||
| picomatch x 384,958 ops/sec ±1.70% (82 runs sampled) | ||
| minimatch x 2.55 ops/sec ±3.22% (11 runs sampled) | ||
| # .makeRe - long ranges ({1..10000000}*.txt) | ||
| picomatch x 400,036 ops/sec ±0.83% (90 runs sampled) | ||
| minimatch (FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory) | ||
| picomatch x 382,552 ops/sec ±1.52% (71 runs sampled) | ||
| minimatch x 0.83 ops/sec ±5.67% (7 runs sampled)) | ||
| ``` | ||
@@ -681,0 +692,0 @@ |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
91415
7.22%5
-16.67%1981
14.51%750
1.49%6
20%