This is an automated email from the ASF dual-hosted git repository.

tballison pushed a commit to branch branch_3x
in repository https://gitbox.apache.org/repos/asf/tika.git


The following commit(s) were added to refs/heads/branch_3x by this push:
     new e474b81d6a TIKA-4692 -- bug fixes (#2945)
e474b81d6a is described below

commit e474b81d6aff942537bcaa3e8f2cc081913cb6d8
Author: Tim Allison <[email protected]>
AuthorDate: Sat Jul 11 09:01:30 2026 -0400

    TIKA-4692 -- bug fixes (#2945)
---
 .../ooxml/SXWPFWordExtractorDecorator.java         |  24 ++--
 .../ooxml/SXWPFMissingRelatedPartTest.java         | 122 +++++++++++++++++++++
 2 files changed, 138 insertions(+), 8 deletions(-)

diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/SXWPFWordExtractorDecorator.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/SXWPFWordExtractorDecorator.java
index 38426a1279..86cdb6e03e 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/SXWPFWordExtractorDecorator.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/SXWPFWordExtractorDecorator.java
@@ -194,7 +194,7 @@ public class SXWPFWordExtractorDecorator extends 
AbstractOOXMLExtractor {
             PackageRelationshipCollection settingsRels =
                     documentPart.getRelationshipsByType(SETTINGS_RELATION);
             if (settingsRels != null && settingsRels.size() > 0) {
-                PackagePart settingsPart = 
documentPart.getRelatedPart(settingsRels.getRelationship(0));
+                PackagePart settingsPart = safeGetRelatedPart(documentPart, 
settingsRels.getRelationship(0));
                 if (settingsPart != null) {
                     try (InputStream is = settingsPart.getInputStream()) {
                         WordSettingsHandler handler = new 
WordSettingsHandler(xhtml);
@@ -214,7 +214,7 @@ public class SXWPFWordExtractorDecorator extends 
AbstractOOXMLExtractor {
             PackageRelationshipCollection webSettingsRels =
                     documentPart.getRelationshipsByType(WEB_SETTINGS_RELATION);
             if (webSettingsRels != null && webSettingsRels.size() > 0) {
-                PackagePart webSettingsPart = 
documentPart.getRelatedPart(webSettingsRels.getRelationship(0));
+                PackagePart webSettingsPart = safeGetRelatedPart(documentPart, 
webSettingsRels.getRelationship(0));
                 if (webSettingsPart != null) {
                     try (InputStream is = webSettingsPart.getInputStream()) {
                         WebSettingsHandler handler = new 
WebSettingsHandler(xhtml);
@@ -272,7 +272,10 @@ public class SXWPFWordExtractorDecorator extends 
AbstractOOXMLExtractor {
                 if (headersPRC != null) {
                     for (int i = 0; i < headersPRC.size(); i++) {
                         PackagePart header =
-                                
documentPart.getRelatedPart(headersPRC.getRelationship(i));
+                                safeGetRelatedPart(documentPart, 
headersPRC.getRelationship(i));
+                        if (header == null) {
+                            continue;
+                        }
                         handlePart(header, styles, listManager, xhtml,
                                 OOXMLInlineBodyPartMap.EMPTY);
                     }
@@ -305,7 +308,10 @@ public class SXWPFWordExtractorDecorator extends 
AbstractOOXMLExtractor {
                 if (prc != null) {
                     for (int i = 0; i < prc.size(); i++) {
                         PackagePart packagePart =
-                                
documentPart.getRelatedPart(prc.getRelationship(i));
+                                safeGetRelatedPart(documentPart, 
prc.getRelationship(i));
+                        if (packagePart == null) {
+                            continue;
+                        }
                         handlePart(packagePart, styles, listManager, xhtml,
                                 OOXMLInlineBodyPartMap.EMPTY);
                     }
@@ -457,7 +463,7 @@ public class SXWPFWordExtractorDecorator extends 
AbstractOOXMLExtractor {
             if (stylesRelationShip == null) {
                 return null;
             }
-            PackagePart stylesPart = 
packagePart.getRelatedPart(stylesRelationShip);
+            PackagePart stylesPart = safeGetRelatedPart(packagePart, 
stylesRelationShip);
             if (stylesPart == null) {
                 return null;
             }
@@ -477,7 +483,7 @@ public class SXWPFWordExtractorDecorator extends 
AbstractOOXMLExtractor {
                 if (numberingRelationShip == null) {
                     return null;
                 }
-                PackagePart numberingPart = 
packagePart.getRelatedPart(numberingRelationShip);
+                PackagePart numberingPart = safeGetRelatedPart(packagePart, 
numberingRelationShip);
                 if (numberingPart == null) {
                     return null;
                 }
@@ -518,8 +524,10 @@ public class SXWPFWordExtractorDecorator extends 
AbstractOOXMLExtractor {
                 if (prc != null) {
                     for (int i = 0; i < prc.size(); i++) {
                         PackagePart packagePart =
-                                
documentPart.getRelatedPart(prc.getRelationship(i));
-                        relatedParts.add(packagePart);
+                                safeGetRelatedPart(documentPart, 
prc.getRelationship(i));
+                        if (packagePart != null) {
+                            relatedParts.add(packagePart);
+                        }
                     }
                 }
             } catch (InvalidFormatException e) {
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/SXWPFMissingRelatedPartTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/SXWPFMissingRelatedPartTest.java
new file mode 100644
index 0000000000..dd7f807757
--- /dev/null
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/SXWPFMissingRelatedPartTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.TikaTest;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.microsoft.OfficeParserConfig;
+
+/**
+ * A docx may declare a relationship to an optional part (numbering.xml,
+ * settings.xml, ...) whose target is missing from the package -- a truncated 
or
+ * otherwise malformed file. POI's {@code PackagePart.getRelatedPart} then 
throws
+ * an unchecked {@code IllegalArgumentException}. The streaming (SAX) docx
+ * extractor must skip the missing part and keep going rather than aborting the
+ * whole parse (which also drops the file's embedded documents).
+ *
+ * <p>Regression guard for the 3.3.2 tika-eval finding: 740 docx crashed on a
+ * missing numbering.xml (484) or settings.xml (254) because those
+ * getRelatedPart calls bypassed {@code safeGetRelatedPart}.
+ */
+public class SXWPFMissingRelatedPartTest extends TikaTest {
+
+    @Test
+    public void testMissingNumberingPart() throws Exception {
+        assertParsesWithoutPart("word/numbering.xml");
+    }
+
+    @Test
+    public void testMissingSettingsPart() throws Exception {
+        assertParsesWithoutPart("word/settings.xml");
+    }
+
+    @Test
+    public void testMissingStylesPart() throws Exception {
+        assertParsesWithoutPart("word/styles.xml");
+    }
+
+    @Test
+    public void testMissingWebSettingsPart() throws Exception {
+        assertParsesWithoutPart("word/webSettings.xml");
+    }
+
+    /**
+     * Strips {@code partToDrop} from a known-good docx (leaving the dangling
+     * relationship in document.xml.rels) and asserts the SAX docx extractor
+     * still parses it -- without the fix, suppressException=false rethrows the
+     * IllegalArgumentException and this fails.
+     */
+    private void assertParsesWithoutPart(String partToDrop) throws Exception {
+        byte[] docx = docxWithoutPart("testWORD_numbered_list.docx", 
partToDrop);
+        List<Metadata> metadataList;
+        try (InputStream is = new ByteArrayInputStream(docx)) {
+            metadataList = getRecursiveMetadata(is, new Metadata(), 
saxDocxContext(), false);
+        }
+        assertEquals(1, metadataList.size(), "dropped " + partToDrop);
+        Metadata m = metadataList.get(0);
+        
assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+                m.get(Metadata.CONTENT_TYPE), "dropped " + partToDrop);
+        String content = m.get(TikaCoreProperties.TIKA_CONTENT);
+        assertNotNull(content, "dropped " + partToDrop);
+        //body text is still recovered, not lost to a catastrophic abort
+        assertContains("This is another list", content);
+        assertContains("Within cell 1", content);
+    }
+
+    private ParseContext saxDocxContext() {
+        ParseContext pc = new ParseContext();
+        OfficeParserConfig config = new OfficeParserConfig();
+        config.setUseSAXDocxExtractor(true);
+        pc.set(OfficeParserConfig.class, config);
+        return pc;
+    }
+
+    /** Copies the resource docx, omitting a single zip entry. */
+    private byte[] docxWithoutPart(String resource, String entryName) throws 
Exception {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        try (ZipArchiveInputStream zin =
+                     new 
ZipArchiveInputStream(getResourceAsStream("/test-documents/" + resource));
+             ZipArchiveOutputStream zout = new ZipArchiveOutputStream(bos)) {
+            ZipArchiveEntry entry;
+            while ((entry = zin.getNextEntry()) != null) {
+                if (entry.getName().equals(entryName)) {
+                    continue;
+                }
+                zout.putArchiveEntry(new ZipArchiveEntry(entry.getName()));
+                IOUtils.copy(zin, zout);
+                zout.closeArchiveEntry();
+            }
+        }
+        return bos.toByteArray();
+    }
+}

Reply via email to