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();
+ }
+}