diff --git a/CHANGELOG.md b/CHANGELOG.md index f960aa4aec..e519de1f0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Please add one entry in this file for each change in Yarn's behavior. Use the sa ## Master +- Prevent infinite loop when parsing corrupted lockfile with unterminated string + + [#4965](https://github.com/yarnpkg/yarn/pull/4965) - [**Ryan Hendrickson**](https://github.com/rhendric) + - Environment variables now have to **start** with `YARN_` (instead of just contain it) to be considered [#6518](https://github.com/yarnpkg/yarn/pull/6518) - [**Michael Gmelin**](https://blog.grem.de) diff --git a/src/lockfile/parse.js b/src/lockfile/parse.js index 4db0830ac4..2348746063 100644 --- a/src/lockfile/parse.js +++ b/src/lockfile/parse.js @@ -69,44 +69,43 @@ function* tokenise(input: string): Iterator { } else if (input[0] === '#') { chop++; - let val = ''; - while (input[chop] !== '\n') { - val += input[chop]; - chop++; + let nextNewline = input.indexOf('\n', chop); + if (nextNewline === -1) { + nextNewline = input.length; } + const val = input.substring(chop, nextNewline); + chop = nextNewline; yield buildToken(TOKEN_TYPES.comment, val); } else if (input[0] === ' ') { if (lastNewline) { - let indent = ''; - for (let i = 0; input[i] === ' '; i++) { - indent += input[i]; + let indentSize = 1; + for (let i = 1; input[i] === ' '; i++) { + indentSize++; } - if (indent.length % 2) { + if (indentSize % 2) { throw new TypeError('Invalid number of spaces'); } else { - chop = indent.length; - yield buildToken(TOKEN_TYPES.indent, indent.length / 2); + chop = indentSize; + yield buildToken(TOKEN_TYPES.indent, indentSize / 2); } } else { chop++; } } else if (input[0] === '"') { - let val = ''; - - for (let i = 0; ; i++) { - const currentChar = input[i]; - val += currentChar; - - if (i > 0 && currentChar === '"') { + let i = 1; + for (; i < input.length; i++) { + if (input[i] === '"') { const isEscaped = input[i - 1] === '\\' && input[i - 2] !== '\\'; if (!isEscaped) { + i++; break; } } } + const val = input.substring(0, i); - chop = val.length; + chop = i; try { yield buildToken(TOKEN_TYPES.string, JSON.parse(val)); @@ -118,10 +117,7 @@ function* tokenise(input: string): Iterator { } } } else if (/^[0-9]/.test(input)) { - let val = ''; - for (let i = 0; /^[0-9]$/.test(input[i]); i++) { - val += input[i]; - } + const val = /^[0-9]+/.exec(input)[0]; chop = val.length; yield buildToken(TOKEN_TYPES.number, +val); @@ -138,16 +134,15 @@ function* tokenise(input: string): Iterator { yield buildToken(TOKEN_TYPES.comma); chop++; } else if (/^[a-zA-Z\/-]/g.test(input)) { - let name = ''; - for (let i = 0; i < input.length; i++) { + let i = 0; + for (; i < input.length; i++) { const char = input[i]; if (char === ':' || char === ' ' || char === '\n' || char === '\r' || char === ',') { break; - } else { - name += char; } } - chop = name.length; + const name = input.substring(0, i); + chop = i; yield buildToken(TOKEN_TYPES.string, name); } else {