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 4ead960bff TIKA-4879: carry on safer get missing part in xlsx and vsdx
(#3135)
4ead960bff is described below
commit 4ead960bffffb7a83016e8f2d71c09e98359007b
Author: Tim Allison <[email protected]>
AuthorDate: Fri Sep 4 15:44:19 2026 -0400
TIKA-4879: carry on safer get missing part in xlsx and vsdx (#3135)
---
CHANGES.txt | 7 +
.../microsoft/ooxml/VSDXExtractorDecorator.java | 4 +-
.../ooxml/XSSFExcelExtractorDecorator.java | 4 +-
.../ooxml/OOXMLMissingRelatedPartTest.java | 154 +++++++++++++++++++++
4 files changed, 165 insertions(+), 4 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 10f3fc3ddc..64bfc36c74 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,12 @@
Release 4.1.0 - unreleased
+ * A missing OOXML relationship target no longer aborts the whole file:
+ the threaded-comment and person lookups in xlsx and the page lookups in
+ vsdx went straight to POI's getRelatedPart, whose unchecked
+ IllegalArgumentException surfaced as "Error creating OOXML extractor" and
+ dropped the text already extracted. They route through
+ safeGetRelatedPart, as branch_3x already did (TIKA-4879).
+
* RawTiffDetector rejects a BigTIFF directory offset near Long.MAX_VALUE
instead of letting the bounds check overflow. Adding the entry-count
size to such an offset wrapped negative and read as "already in the
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/VSDXExtractorDecorator.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/VSDXExtractorDecorator.java
index 7fd0953c55..c8585f3138 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/VSDXExtractorDecorator.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/VSDXExtractorDecorator.java
@@ -90,7 +90,7 @@ public class VSDXExtractorDecorator extends
AbstractOOXMLExtractor {
PackageRelationshipCollection pageRels =
pagesPart.getRelationshipsByType(VISIO_PAGE_REL);
for (PackageRelationship rel : pageRels) {
- PackagePart pagePart = pagesPart.getRelatedPart(rel);
+ PackagePart pagePart = safeGetRelatedPart(pagesPart, rel);
if (pagePart != null) {
pageParts.add(pagePart);
}
@@ -113,7 +113,7 @@ public class VSDXExtractorDecorator extends
AbstractOOXMLExtractor {
if (rels.isEmpty()) {
return null;
}
- return part.getRelatedPart(rels.getRelationship(0));
+ return safeGetRelatedPart(part, rels.getRelationship(0));
}
@Override
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/XSSFExcelExtractorDecorator.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XSSFExcelExtractorDecorator.java
index 08d7c22f57..c7907f93e7 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XSSFExcelExtractorDecorator.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XSSFExcelExtractorDecorator.java
@@ -663,7 +663,7 @@ public class XSSFExcelExtractorDecorator extends
AbstractOOXMLExtractor {
return;
}
for (PackageRelationship rel : coll) {
- PackagePart threadedCommentPart = sheetPart.getRelatedPart(rel);
+ PackagePart threadedCommentPart = safeGetRelatedPart(sheetPart,
rel);
if (threadedCommentPart == null) {
continue;
}
@@ -690,7 +690,7 @@ public class XSSFExcelExtractorDecorator extends
AbstractOOXMLExtractor {
return;
}
for (PackageRelationship rel : coll) {
- PackagePart personsPart = workbookPart.getRelatedPart(rel);
+ PackagePart personsPart = safeGetRelatedPart(workbookPart, rel);
if (personsPart == null) {
continue;
}
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/OOXMLMissingRelatedPartTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLMissingRelatedPartTest.java
new file mode 100644
index 0000000000..1886234c2e
--- /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/OOXMLMissingRelatedPartTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+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.io.TikaInputStream;
+import org.apache.tika.metadata.HttpHeaders;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+
+/**
+ * An OOXML package may declare a relationship whose target part is missing --
a truncated
+ * or otherwise malformed file. POI's {@code PackagePart.getRelatedPart} then
throws an
+ * unchecked {@code IllegalArgumentException}, which
+ * {@link OOXMLExtractorFactory} converts into a TikaException: the whole file
aborts and
+ * even the text already extracted is lost. Every such call must go through
+ * {@link AbstractOOXMLExtractor#safeGetRelatedPart}.
+ *
+ * <p>Same failure class as the 3.3.2 docx regression (740 files crashed on a
missing
+ * numbering.xml/settings.xml). 3.3.2 guards these four xlsx/vsdx sites; 4.0.0
shipped
+ * without them (TIKA-4879).
+ */
+public class OOXMLMissingRelatedPartTest extends TikaTest {
+
+ private static final String XLSX_TYPE =
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+ private static final String VSDX_TYPE = "application/vnd.ms-visio.drawing";
+
+ @Test
+ public void testDanglingThreadedCommentRelationship() throws Exception {
+ //XSSFExcelExtractorDecorator.getThreadedComments
+ byte[] xlsx = withExtraRelationship("testComment.xlsx",
+ "xl/worksheets/_rels/sheet1.xml.rels",
+
"http://schemas.microsoft.com/office/2017/10/relationships/threadedComment",
+ "../threadedComments/threadedComment1.xml");
+ assertSheetTextSurvives(xlsx);
+ }
+
+ @Test
+ public void testDanglingPersonRelationship() throws Exception {
+ //XSSFExcelExtractorDecorator.getPersons
+ byte[] xlsx = withExtraRelationship("testComment.xlsx",
"xl/_rels/workbook.xml.rels",
+
"http://schemas.microsoft.com/office/2017/10/relationships/person",
+ "persons/person.xml");
+ assertSheetTextSurvives(xlsx);
+ }
+
+ @Test
+ public void testMissingVisioPage() throws Exception {
+ //VSDXExtractorDecorator.getPageParts, the per-page loop
+ assertVisioParseCompletes(withoutEntry("testVISIO.vsdx",
"visio/pages/page1.xml"));
+ }
+
+ @Test
+ public void testMissingVisioPagesPart() throws Exception {
+ //VSDXExtractorDecorator.getRelatedPart(PackagePart, String),
document.xml -> pages.xml
+ assertVisioParseCompletes(withoutEntry("testVISIO.vsdx",
"visio/pages/pages.xml"));
+ }
+
+ /**
+ * The pages are unreachable, but the parse must still run to completion:
the EMF
+ * thumbnail is emitted after buildXHTML, so its presence proves we did
not abort.
+ */
+ private void assertVisioParseCompletes(byte[] vsdx) throws Exception {
+ List<Metadata> metadataList = assertParses(vsdx, VSDX_TYPE);
+ assertEquals(2, metadataList.size());
+ assertEquals("image/emf",
metadataList.get(1).get(HttpHeaders.CONTENT_TYPE));
+ }
+
+ /** The dangling relationship must not cost us the sheet text that parsed
fine. */
+ private void assertSheetTextSurvives(byte[] xlsx) throws Exception {
+ List<Metadata> metadataList = assertParses(xlsx, XLSX_TYPE);
+ assertEquals(1, metadataList.size());
+ assertContains("Here is some text",
+ metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT));
+ }
+
+ private List<Metadata> assertParses(byte[] bytes, String expectedType)
throws Exception {
+ List<Metadata> metadataList;
+ try (TikaInputStream tis = TikaInputStream.get(bytes)) {
+ //suppressException=false: an escaping IllegalArgumentException
fails the test here
+ metadataList = getRecursiveMetadata(tis, false);
+ }
+ Metadata m = metadataList.get(0);
+ assertEquals(expectedType, m.get(HttpHeaders.CONTENT_TYPE));
+ assertNotNull(m.get(TikaCoreProperties.TIKA_CONTENT));
+ return metadataList;
+ }
+
+ /** Copies the resource, omitting one zip entry and leaving its
relationship dangling. */
+ private byte[] withoutEntry(String resource, String entryName) throws
Exception {
+ return copy(resource, entryName, null, null, null);
+ }
+
+ /** Copies the resource, appending a relationship whose target is not in
the package. */
+ private byte[] withExtraRelationship(String resource, String relsEntry,
String type,
+ String target) throws Exception {
+ return copy(resource, null, relsEntry, type, target);
+ }
+
+ private byte[] copy(String resource, String dropEntry, String relsEntry,
String type,
+ String target) 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(dropEntry)) {
+ continue;
+ }
+ byte[] data = IOUtils.toByteArray(zin);
+ if (entry.getName().equals(relsEntry)) {
+ String rels = new String(data, StandardCharsets.UTF_8);
+ String injected = "<Relationship Id=\"rIdMissingTarget\"
Type=\"" + type +
+ "\" Target=\"" + target + "\"/></Relationships>";
+ data = rels.replace("</Relationships>", injected)
+ .getBytes(StandardCharsets.UTF_8);
+ }
+ zout.putArchiveEntry(new ZipArchiveEntry(entry.getName()));
+ zout.write(data);
+ zout.closeArchiveEntry();
+ }
+ }
+ return bos.toByteArray();
+ }
+}