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="&lt;(?:window|dialog|wizard|overlay|prefwindow|page)\\s[^&gt;]*xmlns\\s*=\\s*[&quot;']http://www\\.mozilla\\.org/keymaster/gatekeeper/there\\.is\\.only\\.xul[&quot;']"
+             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();
 

Reply via email to