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 ddaebd0ef3 TIKA-4683 -- fix ole digesting (#2798)
ddaebd0ef3 is described below
commit ddaebd0ef36ba94658d0a4a1c093b24dc45b43bb
Author: Tim Allison <[email protected]>
AuthorDate: Sat May 2 13:38:32 2026 -0400
TIKA-4683 -- fix ole digesting (#2798)
---
.../java/org/apache/tika/digest/DigestHelper.java | 8 +-
.../microsoft/ooxml/OleObjectRegressionTest.java | 116 +++++++++++++++++++++
2 files changed, 123 insertions(+), 1 deletion(-)
diff --git a/tika-core/src/main/java/org/apache/tika/digest/DigestHelper.java
b/tika-core/src/main/java/org/apache/tika/digest/DigestHelper.java
index bbcd6b1242..07bc9c4e6f 100644
--- a/tika-core/src/main/java/org/apache/tika/digest/DigestHelper.java
+++ b/tika-core/src/main/java/org/apache/tika/digest/DigestHelper.java
@@ -80,8 +80,12 @@ public class DigestHelper {
Digester digester = digesterFactory.build();
- // Handle embedded stream translation if needed (e.g., for OLE2
objects in TikaInputStream's open container)
+ // Handle embedded stream translation if needed (e.g., for OLE2
objects in TikaInputStream's
+ // open container). The translator consumes `tis` to produce the
translated bytes that get
+ // digested, so we enableRewind() before and rewind() after —
otherwise the caller (typically
+ // AutoDetectParser) would see an exhausted stream and fail its
zero-byte probe (TIKA-XXXX).
if (EMBEDDED_STREAM_TRANSLATOR.shouldTranslate(tis, metadata)) {
+ tis.enableRewind();
try (TemporaryResources tmp = new TemporaryResources()) {
Path tmpBytes = tmp.createTempFile();
try (OutputStream os = Files.newOutputStream(tmpBytes)) {
@@ -90,6 +94,8 @@ public class DigestHelper {
try (TikaInputStream translated =
TikaInputStream.get(tmpBytes)) {
digester.digest(translated, metadata, context);
}
+ } finally {
+ tis.rewind();
}
} else {
digester.digest(tis, metadata, context);
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/microsoft/ooxml/OleObjectRegressionTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/microsoft/ooxml/OleObjectRegressionTest.java
new file mode 100644
index 0000000000..a258badfb0
--- /dev/null
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/microsoft/ooxml/OleObjectRegressionTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.TikaTest;
+import org.apache.tika.digest.DigesterFactory;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.digestutils.CommonsDigesterFactory;
+
+/**
+ * Regression guard for the alpha4-era attachment loss in OOXML containers
with embedded
+ * oleObjectN.bin parts.
+ *
+ * When a {@link DigesterFactory} is on the {@link ParseContext} (a common
production
+ * configuration) the embedded OLE part used to be routed through
+ * {@code MSEmbeddedStreamTranslator} which consumed the underlying
+ * {@code TikaInputStream} without rewinding it. {@code AutoDetectParser}'s
zero-byte
+ * probe then read {@code -1} from the now-exhausted stream and threw
+ * {@link org.apache.tika.exception.ZeroByteFileException}, which silently
dropped the
+ * grandchildren (images, ms-equation parts, ms-chart workbook) of every
oleObject
+ * wrapper.
+ *
+ * Fixed in {@code DigestHelper#maybeDigest} by enableRewind/rewind around the
translate
+ * call so the original stream is intact when control returns to
AutoDetectParser.
+ */
+public class OleObjectRegressionTest extends TikaTest {
+
+ private static final String TEST_FILE = "testWPSAttachment.docx";
+
+ /**
+ * Sanity baseline: without a digester configured, the embedded ms-graph
oleObject is
+ * detected and its inner POIFS is recursed.
+ */
+ @Test
+ public void testOleObjectExtractedWithoutDigester() throws Exception {
+ List<Metadata> list = getRecursiveMetadata(TEST_FILE);
+ assertOleGraphPresent(list);
+ }
+
+ /**
+ * Reproducer for the alpha4 attachment-loss regression. With a digester
wired in, the
+ * embedded oleObject must still resolve to {@code
application/vnd.ms-graph} (or
+ * similar inner-OLE2 detected type), not stay stuck as
+ * {@code application/vnd.openxmlformats-officedocument.oleobject} with a
+ * ZeroByteFileException.
+ */
+ @Test
+ public void testOleObjectExtractedWithDigester() throws Exception {
+ ParseContext context = new ParseContext();
+ context.set(DigesterFactory.class, new CommonsDigesterFactory());
+
+ List<Metadata> list = getRecursiveMetadata(TEST_FILE, context, true);
+ assertOleGraphPresent(list);
+
+ // Sanity check that the digester actually ran (so we know the test is
exercising
+ // the digest code path).
+ long digested = list.stream()
+ .filter(m -> m.get("X-TIKA:digest:MD5") != null)
+ .count();
+ assertTrue(digested > 0,
+ "expected the digester to have computed at least one MD5; "
+ + "did the digester wire up correctly?");
+ }
+
+ private static void assertOleGraphPresent(List<Metadata> list) {
+ List<String> contentTypes = list.stream()
+ .map(m -> m.get(TikaCoreProperties.EMBEDDED_RESOURCE_PATH) + "
-> "
+ + m.get(Metadata.CONTENT_TYPE))
+ .collect(Collectors.toList());
+
+ // testWPSAttachment.docx has three "raw OLE2" embeds at
/oleObject{4,6,8}.bin
+ // wrapping respectively a Word doc, an Excel sheet, and a PowerPoint
deck. They
+ // must be detected as their inner-POIFS types, not left as the OOXML
oleobject
+ // dead-end MIME.
+ for (String n : new String[]{"/oleObject4.bin", "/oleObject6.bin",
"/oleObject8.bin"}) {
+ Metadata m = list.stream()
+ .filter(x ->
n.equals(x.get(TikaCoreProperties.EMBEDDED_RESOURCE_PATH)))
+ .findFirst()
+ .orElse(null);
+ assertTrue(m != null, "expected " + n + " to be present; items=" +
contentTypes);
+ String ct = m.get(Metadata.CONTENT_TYPE);
+ assertTrue(ct != null
+ && !ct.toLowerCase(java.util.Locale.ROOT)
+
.contains("openxmlformats-officedocument.oleobject"),
+ "expected " + n + " to be detected as its inner POIFS
type, not "
+ + "as the dead-end oleobject mime; ct=" + ct);
+ assertNull(m.get("X-TIKA:EXCEPTION:embedded_exception"),
+ "expected no embedded_exception on " + n
+ + "; got: " +
m.get("X-TIKA:EXCEPTION:embedded_exception"));
+ }
+ }
+}