Repository navigation
Description
When Jsoup.clean() processes XML/SVG input that contains a DOCTYPE declaration with an internal DTD subset (i.e., entity declarations enclosed in [...]), jsoup correctly removes the DOCTYPE tag itself but leaves the closing ]> of the subset in the output string.
This was discovered while sanitizing SVG files that follow the Adobe Illustrator export format.
jsoup version: 1.22.1
Java version: 25
Steps to Reproduce
Call Jsoup.clean() with the following input SVG that contains a DOCTYPE with multiple <!ENTITY> declarations inside an internal DTD subset:
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Safelist;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class SvgSanitizerTest {
Safelist safelist = Safelist.none()
.addTags("svg", "circle")
.addAttributes("svg", "viewBox")
.addAttributes("circle", "cx", "cy", "r", "fill");
@Test
void shouldRemoveDocTypeTag() {
String dirtySvg = """
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
<!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
<!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
<!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/">
<!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/">
<!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/">
<!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/">
<!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/">
]>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="red"/>
</svg>
""";
String expectedSvg = """
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="red" />
</svg>
""";
Document.OutputSettings outputSettings = new Document.OutputSettings()
.syntax(Document.OutputSettings.Syntax.xml)
.prettyPrint(false);
var cleanedSvg = Jsoup.clean(dirtySvg, "", safelist, outputSettings);
assertThat(cleanedSvg).isEqualToNormalizingWhitespace(expectedSvg);
}
}
Expected Behaviour
The entire DOCTYPE block (including its internal subset [...]>) is removed. Output:
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="red" />
</svg>
Actual Behaviour
The DOCTYPE declaration is removed but the closing ]> of the internal DTD subset is left behind in the output:
]>
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="red" />
</svg>
Impact
Any application using Jsoup.clean() to sanitize SVG (or XML) produced by tools like Adobe Illustrator will silently receive malformed output containing the ]> artifact, which can break downstream XML parsers and SVG renderers.
Could you please have a look into this please ?
Kindly let me know if you need any more details about the issue.
Many thanks in advance!!
BR
Ram
Activity
jhy commentedon Apr 17, 2026
Hi,
I had a look at this. The issue you are seeing is not from the Cleaner per se but the HTML and XML parser.
In HTML those internal subset entities are not supported in a doctype, and so when the parser sees the first it skips them according to the bogus doctype rules in the spec.
With the way you are using the jsoup cleaner, you are using the HTML parser. And this HTML is the expected output. (And you can confirm same by saving the file as .html and opening in Chrome.)
However, configuring it to use the XML parser won't help in the current form, because our XML parser actually uses the same doctype parsing state machine and will get the same output.
I'm actually a bit surprised to see entities like this in the wild, given e.g. the Billion Laughs Attack and how people generally disable entity support in XML parsers. How prevalent is it for your data? You might be able to workaround by doing some pre or post cleaning.
E.g. you could use the
Cleaner#clean(Document)form of the cleaner withJsoup#Parse(xml, XmlParser)providing the input doc parsed as XML (not HTML). Then you could justclean.select("svg")to get the SVG element (cleaned)I will have a think about if we want to add some better support in the XML parser for these - perhaps just a passthrough such that we parse the doctype correctly and store the internal entities for serialization, but not actually expanding or otherwise supporting the entities.
[-]Jsoup.clean() leaves ]> characters when DOCTYPE contains DTD subset with multiple <!ENTITY> declarations[/-][+]XML parser does not support DOCTYPE with <!ENTITY> declarations[/+]Preserve XML doctype internal subset
Preserve XML doctype internal subset
jhy commentedon Apr 18, 2026
OK, I added pass through support for doctype internal subsets so that these entities would be preserved.
Here's a fuller example of how you can use the Cleaner set up for XML input / output:
Gives:
Because the Cleaner is body fragment orientated (for now; #2284 will make it simpler when working at a outer Document level) we jump through a shell body.
nramc commentedon Apr 22, 2026
thank you so much, @jhy !!