This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new 90f7f141be Xul html parser (#3387)
90f7f141be is described below
commit 90f7f141be778a2638397d932f2c8926e9ed300c
Author: Tim Allison <[email protected]>
AuthorDate: Wed Oct 21 20:04:44 2026 -0400
Xul html parser (#3387)
* TIKA-4985: parse XUL with JSoupParser
* TIKA-4985: detect XUL by content
---
CHANGES.txt | 4 +++-
.../org/apache/tika/mime/tika-mimetypes.xml | 14 ++++++++++++++
.../org/apache/tika/mime/MimeDetectionTest.java | 22 ++++++++++++++++++++++
.../apache/tika/parser/AutoDetectParserTest.java | 19 +++++++++++++++++++
.../org/apache/tika/parser/html/JSoupParser.java | 4 +++-
5 files changed, 61 insertions(+), 2 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 6175006c9c..3a7244534e 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -313,7 +313,9 @@ Release 4.2.0 - unreleased
* Detection: *.rs is text/x-rust (TIKA-4977); a MIME email nesting
multipart/related is message/rfc822, not MHTML (TIKA-4973); XML with an
element
- like <htmlPath> is no longer HTML (TIKA-4962).
+ like <htmlPath> is no longer HTML (TIKA-4962), so XUL with <html:...>
elements is
+ now XUL; XUL is detected by content, not just *.xul, and the HTML parser
still
+ parses it, as XMLParser fails on its DTD entities (TIKA-4985).
* Image checks (byte-size, min-image-size, blank-image-check; new
tika-image-processing module) keep blank and tiny images from text
recognizers.
diff --git
a/tika-core/src/main/resources/org/apache/tika/mime/tika-mimetypes.xml
b/tika-core/src/main/resources/org/apache/tika/mime/tika-mimetypes.xml
index 6cf9dd7a9c..8530bdeab2 100644
--- a/tika-core/src/main/resources/org/apache/tika/mime/tika-mimetypes.xml
+++ b/tika-core/src/main/resources/org/apache/tika/mime/tika-mimetypes.xml
@@ -2048,6 +2048,20 @@
<mime-type type="application/vnd.motorola.flexsuite.wem"/>
<mime-type type="application/vnd.motorola.iprm"/>
<mime-type type="application/vnd.mozilla.xul+xml">
+ <sub-class-of type="application/xml"/>
+ <acronym>XUL</acronym>
+ <_comment>XML User Interface Language</_comment>
+ <!-- root-XML misses roots whose attributes use DTD entities; above
application/xml -->
+ <magic priority="51">
+ <match
value="<(?:window|dialog|wizard|overlay|prefwindow|page)\\s[^>]*xmlns\\s*=\\s*["']http://www\\.mozilla\\.org/keymaster/gatekeeper/there\\.is\\.only\\.xul["']"
+ type="regex" offset="0:8192"/>
+ </magic>
+ <root-XML localName="window"
namespaceURI="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
+ <root-XML localName="dialog"
namespaceURI="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
+ <root-XML localName="wizard"
namespaceURI="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
+ <root-XML localName="overlay"
namespaceURI="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
+ <root-XML localName="prefwindow"
namespaceURI="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
+ <root-XML localName="page"
namespaceURI="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
<glob pattern="*.xul"/>
</mime-type>
<mime-type type="application/vnd.ms-artgalry">
diff --git
a/tika-core/src/test/java/org/apache/tika/mime/MimeDetectionTest.java
b/tika-core/src/test/java/org/apache/tika/mime/MimeDetectionTest.java
index 3d7f214169..8c29f1c609 100644
--- a/tika-core/src/test/java/org/apache/tika/mime/MimeDetectionTest.java
+++ b/tika-core/src/test/java/org/apache/tika/mime/MimeDetectionTest.java
@@ -224,6 +224,28 @@ public class MimeDetectionTest {
}
}
+ @Test
+ public void testXulByContent() throws IOException {
+ MediaType xul = MediaType.application("vnd.mozilla.xul+xml");
+ String ns =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ byte[] plain = ("<?xml version=\"1.0\"?><window xmlns=\"" + ns +
"\"/>").getBytes(UTF_8);
+ // the root's attribute entity stops the root-element sniff
+ byte[] entityInRoot = ("<?xml version=\"1.0\"?>\n<!DOCTYPE wizard
[<!ENTITY % d SYSTEM"
+ + " \"chrome://a/b.dtd\"> %d;]>\n<wizard
title=\"&wizard.title;\" xmlns=\"" + ns
+ + "\"/>").getBytes(UTF_8);
+ byte[] noDeclaration = ("<window title=\"t\" xmlns=\"" + ns +
"\"/>").getBytes(UTF_8);
+ for (byte[] doc : new byte[][]{plain, entityInRoot, noDeclaration}) {
+ assertEquals(xul, detect(doc, null));
+ assertEquals(xul, detect(doc, "attachment.xml"));
+ }
+ // mentioning the namespace is not enough
+ assertEquals(MediaType.TEXT_PLAIN,
+ detect(("document.createElementNS(\"" + ns + "\",
\"label\");").getBytes(UTF_8), null));
+ assertEquals(MediaType.APPLICATION_XML,
+ detect(("<?xml version=\"1.0\"?><bindings
xmlns=\"http://www.mozilla.org/xbl\""
+ + " xmlns:xul=\"" + ns + "\"/>").getBytes(UTF_8),
null));
+ }
+
@Test
public void testRustVersusRlsServices() throws IOException {
MediaType rust = MediaType.text("x-rust");
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/AutoDetectParserTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/AutoDetectParserTest.java
index c1124e09b9..4e96457028 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/AutoDetectParserTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/AutoDetectParserTest.java
@@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.fail;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
@@ -44,6 +45,7 @@ import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.metadata.XMPDM;
import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.html.JSoupParser;
import org.apache.tika.parser.ogg.FlacParser;
import org.apache.tika.parser.ogg.OpusParser;
import org.apache.tika.parser.ogg.VorbisParser;
@@ -382,6 +384,23 @@ public class AutoDetectParserTest extends TikaTest {
assertEquals("value", metadata.get("MyParser"));
}
+ @Test
+ public void testXulWithExternalDtdEntities() throws Exception {
+ String xul = "<?xml version=\"1.0\"?>\n"
+ + "<!DOCTYPE window [<!ENTITY % appDTD SYSTEM
\"chrome://app/locale/app.dtd\">"
+ + " %appDTD;]>\n"
+ + "<window
xmlns=\"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul\""
+ + " xmlns:html=\"http://www.w3.org/1999/xhtml\">"
+ + "<html:p>&app.name; release notes</html:p></window>";
+ Metadata m = new Metadata();
+ m.set(TikaCoreProperties.RESOURCE_NAME_KEY, "aboutDialog.xul");
+ XMLResult r = getXML(TikaInputStream.get(xul.getBytes(UTF_8)),
AUTO_DETECT_PARSER, m);
+ assertEquals("application/vnd.mozilla.xul+xml",
m.get(HttpHeaders.CONTENT_TYPE));
+ assertContains("release notes", r.xml);
+ assertContains(JSoupParser.class.getName(),
+ Arrays.asList(m.getValues(TikaCoreProperties.TIKA_PARSED_BY)));
+ }
+
@Test
public void testZeroByteFileException() throws Exception {
String[] exts = new String[]{"xls", "doc", "pdf", "rtf"};
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-html-module/src/main/java/org/apache/tika/parser/html/JSoupParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-html-module/src/main/java/org/apache/tika/parser/html/JSoupParser.java
index f5d33f6f50..e96730cbcb 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-html-module/src/main/java/org/apache/tika/parser/html/JSoupParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-html-module/src/main/java/org/apache/tika/parser/html/JSoupParser.java
@@ -86,9 +86,11 @@ public class JSoupParser extends
AbstractEncodingDetectorParser {
private static final MediaType XHTML = MediaType.application("xhtml+xml");
private static final MediaType WAP_XHTML =
MediaType.application("vnd.wap.xhtml+xml");
private static final MediaType X_ASP = MediaType.application("x-asp");
+ // XUL leans on entities from chrome:// DTDs, which strict XML parsing
rejects
+ private static final MediaType XUL =
MediaType.application("vnd.mozilla.xul+xml");
private static final Set<MediaType> SUPPORTED_TYPES =
Collections.unmodifiableSet(
- new HashSet<MediaType>(Arrays.asList(MediaType.text("html"),
XHTML, WAP_XHTML, X_ASP)));
+ new HashSet<MediaType>(Arrays.asList(MediaType.text("html"),
XHTML, WAP_XHTML, X_ASP, XUL)));
private static final TagSet SELF_CLOSEABLE_TAGS = TagSet.Html();