Skip to content

Commit 4b4bbca

Browse files
committed
Fixed perf regression in linkify-it wrapper
1 parent d2782d8 commit 4b4bbca

File tree

4 files changed

+25
-5
lines changed

4 files changed

+25
-5
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [14.1.1] - 2026-01-11
9+
### Security
10+
- Fixed regression from v13 in linkify inline rule. Specific patterns could
11+
cause high CPU use. Thanks to @ltduc147 for report.
12+
813

914
## [14.1.0] - 2024-03-19
1015
### Changed
@@ -648,6 +653,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
648653
- Renamed presets folder (configs -> presets).
649654

650655

656+
[14.1.1]: https://github.com/markdown-it/markdown-it/compare/14.1.0...14.1.1
651657
[14.1.0]: https://github.com/markdown-it/markdown-it/compare/14.0.0...14.1.0
652658
[14.0.0]: https://github.com/markdown-it/markdown-it/compare/13.0.2...14.0.0
653659
[13.0.2]: https://github.com/markdown-it/markdown-it/compare/13.0.1...13.0.2

lib/rules_inline/linkify.mjs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,14 @@ export default function linkify (state, silent) {
3030
if (url.length <= proto.length) return false
3131

3232
// disallow '*' at the end of the link (conflicts with emphasis)
33-
url = url.replace(/\*+$/, '')
33+
// do manual backsearch to avoid perf issues with regex /\*+$/ on "****...****a".
34+
let urlEnd = url.length
35+
while (urlEnd > 0 && url.charCodeAt(urlEnd - 1) === 0x2A/* * */) {
36+
urlEnd--
37+
}
38+
if (urlEnd !== url.length) {
39+
url = url.slice(0, urlEnd)
40+
}
3441

3542
const fullUrl = state.md.normalizeLink(url)
3643
if (!state.md.validateLink(fullUrl)) return false

test/pathological.mjs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import crypto from 'node:crypto'
44
import { Worker as JestWorker } from 'jest-worker'
55
import { readFileSync } from 'fs'
66

7-
async function test_pattern (str) {
7+
async function test_pattern (str, mdOpts) {
88
const worker = new JestWorker(
99
new URL('./pathological_worker.js', import.meta.url),
1010
{
@@ -18,7 +18,7 @@ async function test_pattern (str) {
1818

1919
try {
2020
result = await Promise.race([
21-
worker.render(str),
21+
worker.render(str, mdOpts),
2222
new Promise((resolve, reject) => {
2323
setTimeout(() => reject(new Error('Terminated (timeout exceeded)')), 3000).unref()
2424
})
@@ -163,5 +163,12 @@ describe('Pathological sequences speed', () => {
163163
it('hardbreak whitespaces pattern', async () => {
164164
await test_pattern('x' + ' '.repeat(150000) + 'x \nx')
165165
})
166+
167+
it('linkify-it wrapper trailing asterisks pattern', async () => {
168+
await test_pattern(
169+
'https://test.com?' + '*'.repeat(70000) + 'a',
170+
{ linkify: true }
171+
)
172+
})
166173
})
167174
})

test/pathological_worker.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'use strict'
22

3-
exports.render = async (str) => {
4-
return (await import('../index.mjs')).default().render(str)
3+
exports.render = async (str, mdOpts) => {
4+
return (await import('../index.mjs')).default(mdOpts).render(str)
55
}

0 commit comments

Comments
 (0)