From 1529ff4944329eb8080a8558dab85164b6c212cd Mon Sep 17 00:00:00 2001 From: Alex Kocharin Date: Sat, 21 May 2022 18:00:01 +0300 Subject: [PATCH] Guard against custom rule not incrementing pos --- CHANGELOG.md | 6 ++++++ lib/parser_block.js | 13 +++++++++++-- lib/parser_inline.js | 13 ++++++++++--- test/misc.js | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e110bc3..100c3fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [13.1.0] - WIP +### Changed +- Throw an error if 3rd party plugin doesn't increment `line` or `pos` counters + (previously, markdown-it would likely go into infinite loop instead), #847. + ## [13.0.1] - 2022-05-03 ### Fixed - Bumped `linkify-it` to 4.0.1. That should fix some hangs, caused by wrong @@ -616,6 +621,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Renamed presets folder (configs -> presets). +[13.1.0]: https://github.com/markdown-it/markdown-it/compare/13.0.1...13.1.0 [13.0.1]: https://github.com/markdown-it/markdown-it/compare/13.0.0...13.0.1 [13.0.0]: https://github.com/markdown-it/markdown-it/compare/12.3.2...13.0.0 [12.3.2]: https://github.com/markdown-it/markdown-it/compare/12.3.1...12.3.2 diff --git a/lib/parser_block.js b/lib/parser_block.js index 5450c2a..61f184f 100644 --- a/lib/parser_block.js +++ b/lib/parser_block.js @@ -46,7 +46,7 @@ function ParserBlock() { // Generate tokens for input range // ParserBlock.prototype.tokenize = function (state, startLine, endLine) { - var ok, i, + var ok, i, prevLine, rules = this.ruler.getRules(''), len = rules.length, line = startLine, @@ -74,12 +74,21 @@ ParserBlock.prototype.tokenize = function (state, startLine, endLine) { // - update `state.line` // - update `state.tokens` // - return true + prevLine = state.line; for (i = 0; i < len; i++) { ok = rules[i](state, line, endLine, false); - if (ok) { break; } + if (ok) { + if (prevLine >= state.line) { + throw new Error("block rule didn't increment state.line"); + } + break; + } } + // this can only happen if user disables paragraph rule + if (!ok) throw new Error('none of the block rules matched'); + // set state.tight if we had an empty line before current tag // i.e. latest empty line should not count state.tight = !hasEmptyLines; diff --git a/lib/parser_inline.js b/lib/parser_inline.js index 5f384a1..a076e42 100644 --- a/lib/parser_inline.js +++ b/lib/parser_inline.js @@ -99,7 +99,10 @@ ParserInline.prototype.skipToken = function (state) { ok = rules[i](state, true); state.level--; - if (ok) { break; } + if (ok) { + if (pos >= state.pos) { throw new Error("inline rule didn't increment state.pos"); } + break; + } } } else { // Too much nesting, just skip until the end of the paragraph. @@ -124,7 +127,7 @@ ParserInline.prototype.skipToken = function (state) { // Generate tokens for input range // ParserInline.prototype.tokenize = function (state) { - var ok, i, + var ok, i, prevPos, rules = this.ruler.getRules(''), len = rules.length, end = state.posMax, @@ -137,11 +140,15 @@ ParserInline.prototype.tokenize = function (state) { // - update `state.pos` // - update `state.tokens` // - return true + prevPos = state.pos; if (state.level < maxNesting) { for (i = 0; i < len; i++) { ok = rules[i](state, false); - if (ok) { break; } + if (ok) { + if (prevPos >= state.pos) { throw new Error("inline rule didn't increment state.pos"); } + break; + } } } diff --git a/test/misc.js b/test/misc.js index fb0999c..11cd0f4 100644 --- a/test/misc.js +++ b/test/misc.js @@ -169,6 +169,45 @@ describe('API', function () { }); +describe('Plugins', function () { + + it('should not loop infinitely if all rules are disabled', function () { + var md = markdownit(); + + md.inline.ruler.enableOnly([]); + md.inline.ruler2.enableOnly([]); + md.block.ruler.enableOnly([]); + + assert.throws(() => md.render(' - *foo*\n - `bar`'), /none of the block rules matched/); + }); + + it('should not loop infinitely if inline rule doesn\'t increment pos', function () { + var md = markdownit(); + + md.inline.ruler.after('text', 'custom', function (state/*, silent*/) { + if (state.src.charCodeAt(state.pos) !== 0x40/* @ */) return false; + return true; + }); + + assert.throws(() => md.render('foo@bar'), /inline rule didn't increment state.pos/); + assert.throws(() => md.render('[foo@bar]()'), /inline rule didn't increment state.pos/); + }); + + it('should not loop infinitely if block rule doesn\'t increment pos', function () { + var md = markdownit(); + + md.block.ruler.before('paragraph', 'custom', function (state, startLine/*, endLine, silent*/) { + var pos = state.bMarks[startLine] + state.tShift[startLine]; + if (state.src.charCodeAt(pos) !== 0x40/* @ */) return false; + return true; + }, { alt: [ 'paragraph' ] }); + + assert.throws(() => md.render('foo\n@bar\nbaz'), /block rule didn't increment state.line/); + assert.throws(() => md.render('foo\n\n@bar\n\nbaz'), /block rule didn't increment state.line/); + }); +}); + + describe('Misc', function () { it('Should replace NULL characters', function () {