Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid slowdown from URL#searchParams in glob embed #94

Merged
merged 2 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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