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

Reply via email to