Browse Source

Fixed perf regression in linkify-it wrapper

pull/1073/merge
Vitaly Puzrin 2 months ago
parent
commit
4b4bbcae5e
  1. 6
      CHANGELOG.md
  2. 9
      lib/rules_inline/linkify.mjs
  3. 11
      test/pathological.mjs
  4. 4
      test/pathological_worker.js

6
CHANGELOG.md

@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
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).
## [14.1.1] - 2026-01-11
### Security
- Fixed regression from v13 in linkify inline rule. Specific patterns could
cause high CPU use. Thanks to @ltduc147 for report.
## [14.1.0] - 2024-03-19
### Changed
@ -648,6 +653,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Renamed presets folder (configs -> presets).
[14.1.1]: https://github.com/markdown-it/markdown-it/compare/14.1.0...14.1.1
[14.1.0]: https://github.com/markdown-it/markdown-it/compare/14.0.0...14.1.0
[14.0.0]: https://github.com/markdown-it/markdown-it/compare/13.0.2...14.0.0
[13.0.2]: https://github.com/markdown-it/markdown-it/compare/13.0.1...13.0.2

9
lib/rules_inline/linkify.mjs

@ -30,7 +30,14 @@ export default function linkify (state, silent) {
if (url.length <= proto.length) return false
// disallow '*' at the end of the link (conflicts with emphasis)
url = url.replace(/\*+$/, '')
// do manual backsearch to avoid perf issues with regex /\*+$/ on "****...****a".
let urlEnd = url.length
while (urlEnd > 0 && url.charCodeAt(urlEnd - 1) === 0x2A/* * */) {
urlEnd--
}
if (urlEnd !== url.length) {
url = url.slice(0, urlEnd)
}
const fullUrl = state.md.normalizeLink(url)
if (!state.md.validateLink(fullUrl)) return false

11
test/pathological.mjs

@ -4,7 +4,7 @@ import crypto from 'node:crypto'
import { Worker as JestWorker } from 'jest-worker'
import { readFileSync } from 'fs'
async function test_pattern (str) {
async function test_pattern (str, mdOpts) {
const worker = new JestWorker(
new URL('./pathological_worker.js', import.meta.url),
{
@ -18,7 +18,7 @@ async function test_pattern (str) {
try {
result = await Promise.race([
worker.render(str),
worker.render(str, mdOpts),
new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Terminated (timeout exceeded)')), 3000).unref()
})
@ -163,5 +163,12 @@ describe('Pathological sequences speed', () => {
it('hardbreak whitespaces pattern', async () => {
await test_pattern('x' + ' '.repeat(150000) + 'x \nx')
})
it('linkify-it wrapper trailing asterisks pattern', async () => {
await test_pattern(
'https://test.com?' + '*'.repeat(70000) + 'a',
{ linkify: true }
)
})
})
})

4
test/pathological_worker.js

@ -1,5 +1,5 @@
'use strict'
exports.render = async (str) => {
return (await import('../index.mjs')).default().render(str)
exports.render = async (str, mdOpts) => {
return (await import('../index.mjs')).default(mdOpts).render(str)
}

Loading…
Cancel
Save