Skip to content

Commit

Permalink
Avoid slowdown from URL#searchParams in glob embed (#94)
Browse files Browse the repository at this point in the history
* Add explainer for glob regex pattern

* Switch to encodeURIComponent for glob URL generation
  • Loading branch information
MattIPv4 committed Jan 19, 2024
1 parent ab1066c commit 081867e
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 7 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Any non-code changes should be prefixed with `(docs)`.
See `PUBLISH.md` for instructions on how to publish a new version.
-->

- (patch) Avoid slowdown from `URL#searchParams` in glob embed


## v1.12.1 - 39a3836

Expand Down
10 changes: 6 additions & 4 deletions rules/embeds/glob.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ module.exports = md => {
if (closingMark === -1) return false;

// Check for glob match
const match = currentLines.slice(0, closingMark + 3).match(/^\[glob (.+?(?:(?: [^ \n]+?)+|(?:\n.+?)+))\](?:$|\n)/);
// .+(?:\n.+)+ allows for a glob with spaces on the first line, and then tests separated by newlines
// [^ \n]+(?: [^ \n]+)+ allows for a glob with no spaces on the first line, and then tests separated by spaces
const match = currentLines.slice(0, closingMark + 3).match(/^\[glob (.+(?:\n.+)+|[^ \n]+(?: [^ \n]+)+)\](?:$|\n)/);
if (!match) return false;

// Get the full strings
Expand Down Expand Up @@ -147,9 +149,9 @@ module.exports = md => {
const tests = token.glob.tests.map((x, i) => `data-glob-test-${i}="${md.utils.escapeHtml(x)}"`).join(' ');

// Construct the fallback URL
const url = new URL('https://www.digitalocean.com/community/tools/glob');
url.searchParams.append('glob', token.glob.glob);
token.glob.tests.forEach(x => url.searchParams.append('tests', x));
// Don't use URL#searchParams because it is very slow for large numbers of params
// https://twitter.com/MattIPv4/status/1748102513646047584
const url = `https://www.digitalocean.com/community/tools/glob?glob=${encodeURIComponent(token.glob.glob)}${token.glob.tests.map(x => `&tests=${encodeURIComponent(x)}`).join('')}`;

// Return the HTML
return `<div data-glob-tool-embed data-glob-string="${md.utils.escapeHtml(token.glob.glob)}" ${tests}>
Expand Down
16 changes: 13 additions & 3 deletions rules/embeds/glob.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,27 @@ it('handles glob embeds with linebreaks', () => {

it('handles glob embeds with linebreaks and spaces in glob', () => {
expect(md.render('[glob * test.js\n/a\n/b]')).toBe(`<div data-glob-tool-embed data-glob-string="* test.js" data-glob-test-0="/a" data-glob-test-1="/b">
<a href="https://www.digitalocean.com/community/tools/glob?glob=*+test.js&tests=%2Fa&tests=%2Fb" target="_blank">
<a href="https://www.digitalocean.com/community/tools/glob?glob=*%20test.js&tests=%2Fa&tests=%2Fb" target="_blank">
Explore <code>* test.js</code> as a glob string in our glob testing tool
</a>
</div>
<script async defer src="https://do-community.github.io/glob-tool-embed/bundle.js" type="text/javascript" onload="window.GlobToolEmbeds()"></script>
`);
});

it('handles glob embeds with many spaces in glob and a linebreak (ReDos)', () => {
it('handles glob embeds with many spaces in glob (DoS)', () => {
expect(md.render(`[glob ${Array.from('a'.repeat(50000)).join(' ')}]`)).toBe(`<div data-glob-tool-embed data-glob-string="a" ${Array.from('a'.repeat(50000 - 1)).map((a, i) => `data-glob-test-${i}="${a}"`).join(' ')}>
<a href="https://www.digitalocean.com/community/tools/glob?glob=a${Array.from('a'.repeat(50000 - 1)).map(a => `&tests=${a}`).join('')}" target="_blank">
Explore <code>a</code> as a glob string in our glob testing tool
</a>
</div>
<script async defer src="https://do-community.github.io/glob-tool-embed/bundle.js" type="text/javascript" onload="window.GlobToolEmbeds()"></script>
`);
});

it('handles glob embeds with many spaces in glob and a linebreak (ReDoS)', () => {
expect(md.render(`[glob ${Array.from('a'.repeat(50)).join(' ')}\nb\nc]`)).toBe(`<div data-glob-tool-embed data-glob-string="${Array.from('a'.repeat(50)).join(' ')}" data-glob-test-0="b" data-glob-test-1="c">
<a href="https://www.digitalocean.com/community/tools/glob?glob=${Array.from('a'.repeat(50)).join('+')}&tests=b&tests=c" target="_blank">
<a href="https://www.digitalocean.com/community/tools/glob?glob=${Array.from('a'.repeat(50)).join('%20')}&tests=b&tests=c" target="_blank">
Explore <code>${Array.from('a'.repeat(50)).join(' ')}</code> as a glob string in our glob testing tool
</a>
</div>
Expand Down

0 comments on commit 081867e

Please sign in to comment.