diff --git a/lib/NodeUtils.js b/lib/NodeUtils.js
index a0a4962..325311e 100644
--- a/lib/NodeUtils.js
+++ b/lib/NodeUtils.js
@@ -135,11 +135,17 @@ function attrname(a) {
function fallbackRawContentTags(node) {
const tags = [];
- while (node?.nodeType === 1 /*ELEMENT_NODE*/) {
- if (node.namespaceURI === NAMESPACE.HTML && hasRawContentFallback[node.tagName]) {
- tags.push(node.localName);
+ while (node) {
+ if (node.nodeType === 1 /*ELEMENT_NODE*/) {
+ if (node.namespaceURI === NAMESPACE.HTML && hasRawContentFallback[node.tagName]) {
+ tags.push(node.localName);
+ }
+ node = node.parentNode;
+ } else if (node.nodeType === 11 /*DOCUMENT_FRAGMENT_NODE*/ && node._host) {
+ node = node._host;
+ } else {
+ node = node.parentNode;
}
- node = node.parentNode;
}
return tags;
}
@@ -309,7 +315,13 @@ function serializeOne(kid, parent) {
s += '';
break;
case 7: //PROCESSING_INSTRUCTION_NODE
- const content = escapeProcessingInstructionContent(kid.data);
+ let content = escapeProcessingInstructionContent(kid.data);
+ if (content.includes('')) {
+ const fallbackTags = fallbackRawContentTags(parent);
+ for (const fallbackTag of fallbackTags) {
+ content = escapeMatchingClosingTag(content, fallbackTag);
+ }
+ }
s += '' + kid.target + ' ' + content + '?>';
break;
case 10: //DOCUMENT_TYPE_NODE
diff --git a/lib/htmlelts.js b/lib/htmlelts.js
index 923f7f9..c2a941d 100644
--- a/lib/htmlelts.js
+++ b/lib/htmlelts.js
@@ -1141,10 +1141,21 @@ define({
ctor: function HTMLTemplateElement(doc, localName, prefix) {
HTMLElement.call(this, doc, localName, prefix);
this._contentFragment = doc._templateDoc.createDocumentFragment();
+ this._contentFragment._host = this;
},
props: {
content: { get: function() { return this._contentFragment; } },
- serialize: { value: function() { return this.content.serialize(); } }
+ serialize: { value: function() { return this.content.serialize(); } },
+ cloneNode: {
+ value: function(deep) {
+ var clone = HTMLElement.prototype.cloneNode.call(this, deep);
+ if (deep) {
+ clone._contentFragment = this._contentFragment.cloneNode(true);
+ clone._contentFragment._host = clone;
+ }
+ return clone;
+ }
+ }
}
});
diff --git a/test/domino.js b/test/domino.js
index 916e09d..bf0b33b 100644
--- a/test/domino.js
+++ b/test/domino.js
@@ -1556,3 +1556,72 @@ exports.worksWithBase64DataImages = function () {
'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII);">'
);
};
+
+exports.processingInstructionClosingTagEscapedInNoscript = function () {
+ const document = domino.createDocument('
');
+ const noscript = document.createElement('noscript');
+ noscript.appendChild(document.createProcessingInstruction('x', '
'
+ );
+ html.should.not.containEql('');
+ html.should.not.containEql('
';
+ template.content.appendChild(xmp);
+ fallback.appendChild(template);
+ document.body.appendChild(fallback);
+
+ const html = document.body.serialize();
+ html.should.equal(
+ '<' + tagName + '></' + tagName + '>
' + tagName + '>'
+ );
+ document.body.removeChild(fallback);
+ }
+};
+
+exports.clonedTemplateContentMaintainsHostReference = function () {
+ const document = domino.createDocument('');
+ const fallback = document.createElement('noscript');
+ const template = document.createElement('template');
+ const xmp = document.createElement('xmp');
+ xmp.textContent = '
';
+ template.content.appendChild(xmp);
+
+ const clonedTemplate = template.cloneNode(true);
+ fallback.appendChild(clonedTemplate);
+ document.body.appendChild(fallback);
+
+ const html = document.body.serialize();
+ html.should.equal(
+ ''
+ );
+};
+
diff --git a/test/xss.js b/test/xss.js
index 3988b28..aa71a01 100644
--- a/test/xss.js
+++ b/test/xss.js
@@ -869,3 +869,121 @@ exports.fallbackRawTextForeignContentEscapesAncestorClosingTag = async function
}
}
};
+
+exports.fallbackRawTextTemplateContentEscapesAncestorClosingTag = async function () {
+ const fallbackTags = ['noscript', 'iframe', 'noembed', 'noframes'];
+
+ for (const tag of fallbackTags) {
+ // noscript/iframe/noembed/noframes > template > xmp
+ {
+ const document = domino.createDocument('');
+ const fallbackEl = document.createElement(tag);
+ const template = document.createElement('template');
+ const xmp = document.createElement('xmp');
+ xmp.textContent = `${tag}>
`;
+ template.content.appendChild(xmp);
+ fallbackEl.appendChild(template);
+ document.body.appendChild(fallbackEl);
+
+ const serialized = document.body.serialize();
+ serialized.should.equal(
+ `<${tag}></${tag}>
${tag}>`,
+ );
+
+ const reparsedDoc = domino.createDocument('' + serialized + '');
+ reparsedDoc.getElementsByTagName('img').length.should.equal(0);
+
+ const reparsed = reparsedDoc.body.innerHTML;
+ reparsed.should.not.containEql(`${tag}>
: ` + reparsed,
+ );
+ }
+
+ // noscript/iframe/noembed/noframes > template > #comment
+ {
+ const document = domino.createDocument('');
+ const fallbackEl = document.createElement(tag);
+ const template = document.createElement('template');
+ const comment = document.createComment(`${tag}>
`);
+ template.content.appendChild(comment);
+ fallbackEl.appendChild(template);
+ document.body.appendChild(fallbackEl);
+
+ const serialized = document.body.serialize();
+ serialized.should.equal(
+ `<${tag}>${tag}>`,
+ );
+
+ const reparsedDoc = domino.createDocument('' + serialized + '');
+ reparsedDoc.getElementsByTagName('img').length.should.equal(0);
+
+ const reparsed = reparsedDoc.body.innerHTML;
+ reparsed.should.not.containEql(`${tag}>
: ` + reparsed,
+ );
+ }
+ }
+};
+
+exports.fallbackRawTextProcessingInstructionEscapesAncestorClosingTag = async function () {
+ const fallbackTags = ['noscript', 'iframe', 'noembed', 'noframes'];
+
+ for (const tag of fallbackTags) {
+ // noscript/iframe/noembed/noframes > ProcessingInstruction
+ {
+ const document = domino.createDocument('');
+ const fallbackEl = document.createElement(tag);
+ fallbackEl.appendChild(document.createProcessingInstruction('x', `${tag}>
`));
+ document.body.appendChild(fallbackEl);
+
+ const serialized = document.body.serialize();
+ serialized.should.equal(
+ `<${tag}>${tag}>`,
+ );
+
+ const reparsedDoc = domino.createDocument('' + serialized + '');
+ reparsedDoc.getElementsByTagName('img').length.should.equal(0);
+
+ const reparsed = reparsedDoc.body.innerHTML;
+ reparsed.should.not.containEql(`${tag}>
: ` + reparsed,
+ );
+ }
+
+ // noscript/iframe/noembed/noframes > template > ProcessingInstruction
+ {
+ const document = domino.createDocument('');
+ const fallbackEl = document.createElement(tag);
+ const template = document.createElement('template');
+ template.content.appendChild(document.createProcessingInstruction('x', `${tag}>
`));
+ fallbackEl.appendChild(template);
+ document.body.appendChild(fallbackEl);
+
+ const serialized = document.body.serialize();
+ serialized.should.equal(
+ `<${tag}>${tag}>`,
+ );
+
+ const reparsedDoc = domino.createDocument('' + serialized + '');
+ reparsedDoc.getElementsByTagName('img').length.should.equal(0);
+
+ const reparsed = reparsedDoc.body.innerHTML;
+ reparsed.should.not.containEql(`${tag}>
: ` + reparsed,
+ );
+ }
+ }
+};
+