Skip to content
Open
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
4 changes: 2 additions & 2 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ const entities = require('entities');
const xml2js = require('xml2js');

utils.stripHtml = function(str) {
str = str.replace(/([^\n])<\/?(h|br|p|ul|ol|li|blockquote|section|table|tr|div)(?:.|\n)*?>([^\n])/gm, '$1\n$3')
str = str.replace(/<(?:.|\n)*?>/gm, '');
str = str.replace(/([^\n])<\/?(h|br|p|ul|ol|li|blockquote|section|table|tr|div)[^<>]*>([^\n])/gm, '$1\n$3')
Comment thread
y0d4a marked this conversation as resolved.
str = str.replace(/<[^<>]*>/gm, '');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Behavior change: a literal < inside a tag now truncates the match, leaking a fragment into the snippet.

Measured with the old vs. new bodies of stripHtml:

'a<p title="5 < 3">b'  OLD -> 'a\nb'      NEW -> 'a<p title="5 b'
'a<b<c>d'              OLD -> 'ad'        NEW -> 'a<bd'

A literal < in a quoted attribute value is valid HTML5, and the HTML5 tokenizer also treats < in tag-name/attribute-name position as an ordinary character, so both cases above are "a tag" to a browser but now leave visible markup in contentSnippet. This is a reasonable trade-off for killing the quadratic scan — the leftover fragment can never be a complete tag, since the match stops precisely at the next < — but it should be a deliberate decision rather than a side effect.

Concretely: add a case to the testCases table in test/html.js pinning the new output for < inside a tag. That documents the intent, and it means a future "improvement" to this regex can't silently change snippet text again.

(If you ever want the old semantics back without the ReDoS, /<[^<>"']*(?:"[^"]*"|'[^']*')?[^<>"']*>/ style attribute-aware matching is possible, but it is materially more complex for a case this rare — I would not do it here.)

return str;
}

Expand Down
6 changes: 6 additions & 0 deletions test/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,11 @@ describe('Utils', function() {
Expect('|' + utils.getSnippet(tc.input) + '|').to.equal('|' + tc.output + '|', tc.input);
});
})

it('should handle repeated unterminated HTML tags efficiently', function() {
this.timeout(2000);
var input = 'a<br'.repeat(40000);
Expect(utils.getSnippet(input)).to.equal(input);
Comment thread
y0d4a marked this conversation as resolved.
Comment thread
y0d4a marked this conversation as resolved.
})
});