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
26 changes: 26 additions & 0 deletions lib/HTMLParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -1973,6 +1973,18 @@ function HTMLParser(address, fragmentContext, options) {
var templateInsertionModes = []; // Stack of template insertion modes.
var stack = new HTMLParser.ElementStack(); // Stack of open elements
var afe = new HTMLParser.ActiveFormattingElements(); // mis-nested tags

// Reconstructing stale active formatting elements can otherwise turn a
// small input into a quadratic number of DOM allocations. Keep the total
// reconstruction work linear in the amount of input supplied to this
// parser. The floor avoids affecting small documents with ordinary HTML
// error recovery, while the per-character allowance scales for large
// documents.
var input_char_count = 0;
var afe_reconstruction_count = 0;
var MIN_AFE_RECONSTRUCTION_BUDGET = 4096;
var AFE_RECONSTRUCTIONS_PER_INPUT_CHAR = 2;

var fragment = (fragmentContext!==undefined); // For innerHTML, etc.
var head_element_pointer = null;
var form_element_pointer = null;
Expand Down Expand Up @@ -2040,6 +2052,11 @@ function HTMLParser(address, fragmentContext, options) {
parse: function(s, end, shouldPauseFunc) {
var moreToDo;

// Count only newly supplied input. Buffered leftovers are already
// accounted for, while process() and resume() re-enter with an empty
// string and therefore do not count the same input twice.
input_char_count += s.length;

// If we're paused, remember the text to parse, but
// don't parse it now.
// (Don't invoke shouldPauseFunc because we haven't handled 'end' yet.)
Expand Down Expand Up @@ -2850,6 +2867,15 @@ function HTMLParser(address, fragmentContext, options) {
// one, recreating formatting elements and pushing them back onto
// the list of open elements
for(i = i+1; i < afe.list.length; i++) {
afe_reconstruction_count++;
var reconstruction_budget = Math.max(
MIN_AFE_RECONSTRUCTION_BUDGET,
input_char_count * AFE_RECONSTRUCTIONS_PER_INPUT_CHAR
);
if (afe_reconstruction_count > reconstruction_budget) {
throw new Error('HTML parser active formatting reconstruction limit exceeded');
}

var newelt = insertElement(function(doc) { return afeclone(doc, i).elt; });
afe.list[i] = newelt;
}
Expand Down
31 changes: 31 additions & 0 deletions test/parsing.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,34 @@ exports.parseAlgorithm.activeFormattingElements = {
}
}
};


exports.parseAlgorithm['active formatting reconstruction budget'] = {
'bounds pathological fragment reconstruction': function() {
var doc = domino.createDocument();
var template = doc.createElement('template');
var html = '';

for (var i = 0; i < 100; i++) {
html += '<p><b a' + i + '></p>';
}

assert.throws(function() {
template.innerHTML = html;
}, /active formatting reconstruction limit exceeded/);
},

'allows normally nested formatting elements': function() {
var doc = domino.createDocument();
var template = doc.createElement('template');
var html = '';

for (var i = 0; i < 100; i++) {
html += '<p><b a' + i + '></b></p>';
}

assert.doesNotThrow(function() {
template.innerHTML = html;
});
}
};
Loading