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

tballison pushed a commit to branch TIKA-4809-stage-9
in repository https://gitbox.apache.org/repos/asf/tika.git

commit f236f33b528f84e0e589c2350b46ecd4cd7a95c2
Author: tallison <[email protected]>
AuthorDate: Mon Aug 10 19:47:05 2026 -0400

    TIKA-4809: Run /detect in a forked pipes worker
---
 .../ROOT/pages/using-tika/server/index.adoc        | 17 +++++++++------
 .../apache/tika/server/core/TikaServerProcess.java | 10 ++++-----
 .../server/core/resource/DetectorResource.java     | 25 +++++++++++-----------
 3 files changed, 27 insertions(+), 25 deletions(-)

diff --git a/docs/modules/ROOT/pages/using-tika/server/index.adoc 
b/docs/modules/ROOT/pages/using-tika/server/index.adoc
index e979e6d926..181750c4b3 100644
--- a/docs/modules/ROOT/pages/using-tika/server/index.adoc
+++ b/docs/modules/ROOT/pages/using-tika/server/index.adoc
@@ -195,18 +195,21 @@ and parsing); selecting either without it causes the 
server to refuse to start.
 is a plain opt-in endpoint — enable it simply by listing it under `endpoints`. 
See
 <<_security_configuration,Security Configuration>>.
 
-WARNING: `/language` and `/detect/stream` do their work *in the server's own 
JVM*, not in a
-forked pipes worker. They are therefore outside the process isolation that 
protects `/tika`,
-`/rmeta`, `/meta`, and `/unpack` — a crash or memory exhaustion takes the 
server with it
-rather than one worker.
+WARNING: `/language` does its work *in the server's own JVM*, not in a forked 
pipes worker.
+It is therefore outside the process isolation that protects `/tika`, `/rmeta`, 
`/meta`,
+`/detect/stream`, and `/unpack` — a crash or memory exhaustion takes the 
server with it rather than
+one worker.
 
 `/language` caps detection at the first 100,000 characters, since accuracy 
saturates well
 before that. That bounds the CPU per request, but *not* the memory: the 
request body is read
 into the server's heap before the cap applies, and the server has no maximum 
request size. A
 caller can still exhaust the heap with a large enough body, or with enough 
concurrent ones.
-Treat these endpoints as available only to trusted callers, the same as the 
rest of the
-server — see xref:security.adoc[the security model]. If you do not need them, 
omit them from
-`endpoints`.
+Treat it as available only to trusted callers, the same as the rest of the 
server — see
+xref:security.adoc[the security model]. If you do not need it, omit it from 
`endpoints`.
+
+NOTE: `/detect/stream` runs in a forked pipes worker like the parsing 
endpoints. Detection opens
+containers — zip, OPC, POIFS — over caller-supplied bytes, so it gets the same 
isolation,
+timeouts, and worker restart. It does not require `allowPipes`.
 
 == Error Responses
 
diff --git 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
index f13773c2af..5cf042ae90 100644
--- 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
+++ 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
@@ -185,7 +185,7 @@ public class TikaServerProcess {
         PipesParsingHelper pipesParsingHelper = null;
         if (needsPipesParsingHelper(tikaServerConfig)) {
             pipesParsingHelper = initPipesParsingHelper(tikaServerConfig);
-            LOG.info("Pipes-based parsing enabled for /tika, /rmeta, /unpack, 
/meta, and /pipes endpoints");
+            LOG.info("Pipes-based parsing enabled for /tika, /rmeta, /unpack, 
/meta, /detect, and /pipes endpoints");
         }
 
         TikaResource tikaResource = new TikaResource(tikaLoader, serverStatus, 
pipesParsingHelper,
@@ -457,9 +457,9 @@ public class TikaServerProcess {
 
     /**
      * Determines if the shared PipesParser (wrapped in PipesParsingHelper) is 
needed
-     * based on configured endpoints. It's needed when /tika, /rmeta, /unpack, 
/meta, or
-     * /pipes are enabled (either explicitly or by default) -- all five now 
share one
-     * parser. (Note: unlike the others, /pipes also requires allowPipes to 
actually
+     * based on configured endpoints. It's needed when /tika, /rmeta, /unpack, 
/meta,
+     * /detect, or /pipes are enabled (either explicitly or by default) -- all 
six now
+     * share one parser. (Note: unlike the others, /pipes also requires 
allowPipes to actually
      * start; if it's listed without allowPipes, loadCoreProviders will refuse 
to start
      * regardless of whether this method already triggered building the shared 
parser.)
      */
@@ -472,7 +472,7 @@ public class TikaServerProcess {
         }
         return endpoints.contains("tika") || endpoints.contains("rmeta")
                 || endpoints.contains("unpack") || endpoints.contains("pipes")
-                || endpoints.contains("meta");
+                || endpoints.contains("meta") || endpoints.contains("detect");
     }
 
     /**
diff --git 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/DetectorResource.java
 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/DetectorResource.java
index 1960c22323..65b096f276 100644
--- 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/DetectorResource.java
+++ 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/DetectorResource.java
@@ -18,6 +18,7 @@ package org.apache.tika.server.core.resource;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.List;
 
 import jakarta.ws.rs.Consumes;
 import jakarta.ws.rs.PUT;
@@ -29,12 +30,12 @@ import jakarta.ws.rs.core.UriInfo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import org.apache.tika.exception.TikaConfigException;
 import org.apache.tika.io.TikaInputStream;
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.mime.MediaType;
 import org.apache.tika.parser.ParseContext;
+import org.apache.tika.pipes.api.ParseMode;
 import org.apache.tika.server.core.ServerStatus;
 
 @Path("/detect")
@@ -62,20 +63,18 @@ public class DetectorResource {
         long taskId = serverStatus.start(ServerStatus.TASK.DETECT, filename);
 
         try (TikaInputStream tis = TikaInputStream.get(is)) {
-            return tikaResource
-                    .getTikaLoader()
-                    .loadDetectors()
-                    .detect(tis, met, parseContext)
-                    .toString();
-        } catch (IOException | TikaConfigException e) {
+            tis.getPath(); // Spool to temp file for pipes-based parsing
+            // NO_PARSE: the child detects (and digests, if configured) 
without parsing.
+            // Detection still opens containers -- zip, OPC, POIFS -- over 
caller-supplied
+            // bytes, so it belongs in the forked worker for the same reason 
parsing does.
+            List<Metadata> metadataList =
+                    tikaResource.parseWithPipes(tis, met, parseContext, 
ParseMode.NO_PARSE);
+            String detected = metadataList.isEmpty()
+                    ? null : metadataList.get(0).get(Metadata.CONTENT_TYPE);
+            return detected == null ? MediaType.OCTET_STREAM.toString() : 
detected;
+        } catch (IOException e) {
             LOG.warn("Unable to detect MIME type for file. Reason: {} ({})", 
e.getMessage(), filename, e);
             return MediaType.OCTET_STREAM.toString();
-        } catch (OutOfMemoryError e) {
-            LOG.error("OOM while detecting: ({})", filename, e);
-            throw e;
-        } catch (Throwable e) {
-            LOG.error("Exception while detecting: ({})", filename, e);
-            throw e;
         } finally {
             serverStatus.complete(taskId);
         }

Reply via email to