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

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

commit c8017866456671f9d8b1890adf40a03ab7fbdc6b
Author: tallison <[email protected]>
AuthorDate: Mon Aug 10 11:28:14 2026 -0400

    TIKA-4809: Make silent config and load failures visible
---
 .../java/org/apache/tika/config/ServiceLoader.java |  9 +-
 .../charsoup/CharSoupMetadataFilter.java           |  8 ++
 .../metadatafilter/OpenNLPMetadataFilter.java      |  8 ++
 .../metadatafilter/OptimaizeMetadataFilter.java    |  8 ++
 .../apache/tika/server/core/TikaServerProcess.java | 95 +++++++++++++++-------
 5 files changed, 99 insertions(+), 29 deletions(-)

diff --git a/tika-core/src/main/java/org/apache/tika/config/ServiceLoader.java 
b/tika-core/src/main/java/org/apache/tika/config/ServiceLoader.java
index ad2c616f0a..0bd661a445 100644
--- a/tika-core/src/main/java/org/apache/tika/config/ServiceLoader.java
+++ b/tika-core/src/main/java/org/apache/tika/config/ServiceLoader.java
@@ -35,6 +35,8 @@ import java.util.Set;
 import java.util.regex.Pattern;
 
 import aQute.bnd.annotation.spi.ServiceConsumer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import org.apache.tika.detect.Detector;
 import org.apache.tika.detect.EncodingDetector;
@@ -59,6 +61,8 @@ import org.apache.tika.utils.ServiceLoaderUtils;
 @ServiceConsumer(MetadataFilter.class)
 public class ServiceLoader {
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(ServiceLoader.class);
+
     /**
      * The dynamic set of services available in an OSGi environment.
      * Managed by the {@link TikaActivator} class and used as an additional
@@ -339,7 +343,10 @@ public class ServiceLoader {
                                 "Class " + name + " is not of type: " + iface);
                     }
                 } catch (Throwable t) {
-                    //TODO: swallow, log, throw?
+                    // Swallowed so one bad provider cannot break loading of 
the rest,
+                    // but logged: an SPI class that silently fails to load 
looks exactly
+                    // like one that was never registered.
+                    LOG.warn("failed to load service provider {} for {}", 
name, iface, t);
                 }
             }
         }
diff --git 
a/tika-langdetect/tika-langdetect-charsoup/src/main/java/org/apache/tika/langdetect/charsoup/CharSoupMetadataFilter.java
 
b/tika-langdetect/tika-langdetect-charsoup/src/main/java/org/apache/tika/langdetect/charsoup/CharSoupMetadataFilter.java
index 64561ea00c..430e066b16 100644
--- 
a/tika-langdetect/tika-langdetect-charsoup/src/main/java/org/apache/tika/langdetect/charsoup/CharSoupMetadataFilter.java
+++ 
b/tika-langdetect/tika-langdetect-charsoup/src/main/java/org/apache/tika/langdetect/charsoup/CharSoupMetadataFilter.java
@@ -15,6 +15,9 @@
  * limitations under the License.
  */
 package org.apache.tika.langdetect.charsoup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import org.apache.tika.annotation.TikaComponent;
 import org.apache.tika.langdetect.charsoup.core.CharSoupFeatureExtractor;
 import org.apache.tika.language.detect.LanguageResult;
@@ -39,6 +42,8 @@ import org.apache.tika.metadata.filter.MetadataFilterBase;
 @TikaComponent(name = "charsoup-metadata-filter")
 public class CharSoupMetadataFilter extends MetadataFilterBase {
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(CharSoupMetadataFilter.class);
+
     private int maxLength = CharSoupFeatureExtractor.MAX_TEXT_LENGTH;
 
     public void setMaxLength(int maxLength) {
@@ -49,6 +54,9 @@ public class CharSoupMetadataFilter extends 
MetadataFilterBase {
     public void filter(Metadata metadata) {
         String content = metadata.get(TikaCoreProperties.TIKA_CONTENT);
         if (content == null || content.isEmpty()) {
+            // No tk:content to detect from -- e.g. the ignore handler, which 
/meta pins.
+            // Silence here reads as "no language found" rather than "never 
ran".
+            LOG.debug("no content to detect language from; filter is a no-op 
for this document");
             return;
         }
         CharSoupLanguageDetector detector = new CharSoupLanguageDetector();
diff --git 
a/tika-langdetect/tika-langdetect-opennlp/src/main/java/org/apache/tika/langdetect/opennlp/metadatafilter/OpenNLPMetadataFilter.java
 
b/tika-langdetect/tika-langdetect-opennlp/src/main/java/org/apache/tika/langdetect/opennlp/metadatafilter/OpenNLPMetadataFilter.java
index 754261aec4..c448e288c0 100644
--- 
a/tika-langdetect/tika-langdetect-opennlp/src/main/java/org/apache/tika/langdetect/opennlp/metadatafilter/OpenNLPMetadataFilter.java
+++ 
b/tika-langdetect/tika-langdetect-opennlp/src/main/java/org/apache/tika/langdetect/opennlp/metadatafilter/OpenNLPMetadataFilter.java
@@ -16,6 +16,9 @@
  */
 package org.apache.tika.langdetect.opennlp.metadatafilter;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import org.apache.tika.annotation.TikaComponent;
 import org.apache.tika.langdetect.opennlp.OpenNLPDetector;
 import org.apache.tika.language.detect.LanguageResult;
@@ -26,6 +29,8 @@ import org.apache.tika.metadata.filter.MetadataFilterBase;
 @TikaComponent
 public class OpenNLPMetadataFilter extends MetadataFilterBase {
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(OpenNLPMetadataFilter.class);
+
     private int maxCharsForDetection = 10000;
 
     public void setMaxCharsForDetection(int maxCharsForDetection) {
@@ -38,6 +43,9 @@ public class OpenNLPMetadataFilter extends MetadataFilterBase 
{
         detector.setMaxLength(maxCharsForDetection);
         String content = metadata.get(TikaCoreProperties.TIKA_CONTENT);
         if (content == null) {
+            // No tk:content to detect from -- e.g. the ignore handler, which 
/meta pins.
+            // Silence here reads as "no language found" rather than "never 
ran".
+            LOG.debug("no content to detect language from; filter is a no-op 
for this document");
             return;
         }
         LanguageResult r = detector.detect(content);
diff --git 
a/tika-langdetect/tika-langdetect-optimaize/src/main/java/org/apache/tika/langdetect/optimaize/metadatafilter/OptimaizeMetadataFilter.java
 
b/tika-langdetect/tika-langdetect-optimaize/src/main/java/org/apache/tika/langdetect/optimaize/metadatafilter/OptimaizeMetadataFilter.java
index 44babda274..62f85959fc 100644
--- 
a/tika-langdetect/tika-langdetect-optimaize/src/main/java/org/apache/tika/langdetect/optimaize/metadatafilter/OptimaizeMetadataFilter.java
+++ 
b/tika-langdetect/tika-langdetect-optimaize/src/main/java/org/apache/tika/langdetect/optimaize/metadatafilter/OptimaizeMetadataFilter.java
@@ -16,6 +16,9 @@
  */
 package org.apache.tika.langdetect.optimaize.metadatafilter;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import org.apache.tika.annotation.TikaComponent;
 import org.apache.tika.langdetect.optimaize.OptimaizeLangDetector;
 import org.apache.tika.language.detect.LanguageResult;
@@ -26,6 +29,8 @@ import org.apache.tika.metadata.filter.MetadataFilterBase;
 @TikaComponent
 public class OptimaizeMetadataFilter extends MetadataFilterBase {
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(OptimaizeMetadataFilter.class);
+
     private int maxCharsForDetection = 
OptimaizeLangDetector.DEFAULT_MAX_CHARS_FOR_DETECTION;
 
     public void setMaxCharsForDetection(int maxCharsForDetection) {
@@ -38,6 +43,9 @@ public class OptimaizeMetadataFilter extends 
MetadataFilterBase {
         detector.loadModels();
         String content = metadata.get(TikaCoreProperties.TIKA_CONTENT);
         if (content == null) {
+            // No tk:content to detect from -- e.g. the ignore handler, which 
/meta pins.
+            // Silence here reads as "no language found" rather than "never 
ran".
+            LOG.debug("no content to detect language from; filter is a no-op 
for this document");
             return;
         }
         LanguageResult r = detector.detect(content);
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 93bde62770..99095fc5ad 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
@@ -105,6 +105,10 @@ public class TikaServerProcess {
     private static final Logger LOG = 
LoggerFactory.getLogger(TikaServerProcess.class);
     public static int DO_NOT_RESTART_EXIT_VALUE = -100;
 
+    private static final List<String> VALID_ENDPOINTS = List.of("tika", 
"rmeta", "meta",
+            "unpack", "detect", "language", "translate", "mime", "mime-types", 
"detectors",
+            "parsers", "version", "status", "pipes", "async");
+
     private static Options getOptions() {
         Options options = new Options();
         options.addOption("h", "host", true, "host name, use * for all)");
@@ -163,6 +167,35 @@ public class TikaServerProcess {
         LOG.info("Started Apache Tika server {} at {}", 
serverDetails.serverId, serverDetails.url);
     }
 
+    /**
+     * Warns for settings that widen what a caller can reach. The security 
prose already
+     * exists, but only in the message shown when a flag is *not* set -- i.e. 
only to the
+     * operators who did not enable the risky thing.
+     */
+    private static void warnOnPermissiveConfig(TikaServerConfig config) {
+        if (config.isAllowPipes()) {
+            LOG.warn("allowPipes is enabled: /pipes and /async can fetch and 
emit through "
+                    + "configured fetchers/emitters, reaching files and 
network resources at "
+                    + "the server's privilege level. Expose only to trusted 
callers.");
+        }
+        if (config.isAllowPerRequestConfig()) {
+            LOG.warn("allowPerRequestConfig is enabled: callers may set any 
parser option, "
+                    + "including options that spawn external processes such as 
OCR.");
+        }
+        if (config.isReturnStackTrace()) {
+            LOG.warn("returnStackTrace is enabled: error responses include 
server-side stack "
+                    + "traces. Intended for development, not production.");
+        }
+        if ("*".equals(config.getCors())) {
+            LOG.warn("cors is '*': any origin may call this server from a 
browser.");
+        }
+        if ("0.0.0.0".equals(config.getHost()) || 
"*".equals(config.getHost())) {
+            LOG.warn("host is {}: the server is reachable on every interface. 
tika-server "
+                    + "performs no authentication; restrict access at the 
network layer.",
+                    config.getHost());
+        }
+    }
+
     //This returns the server, configured and ready to be started.
     private static ServerDetails initServer(TikaServerConfig tikaServerConfig) 
throws Exception {
         String host = tikaServerConfig.getHost();
@@ -204,6 +237,8 @@ public class TikaServerProcess {
         sf.setOutInterceptors(Collections.singletonList(new 
GZIPOutInterceptor()));
         sf.setInInterceptors(Collections.singletonList(new 
GZIPInInterceptor()));
 
+        warnOnPermissiveConfig(tikaServerConfig);
+
         String protocol = tikaServerConfig
                 .getTlsConfig()
                 .isActive() ? "https" : "http";
@@ -370,34 +405,38 @@ public class TikaServerProcess {
             // this default set.
         } else {
             for (String endPoint : tikaServerConfig.getEndpoints()) {
-                if ("meta".equals(endPoint)) {
-                    resourceProviders.add(new SingletonResourceProvider(new 
MetadataResource(tikaResource)));
-                } else if ("rmeta".equals(endPoint)) {
-                    resourceProviders.add(new SingletonResourceProvider(new 
RecursiveMetadataResource(tikaResource)));
-                } else if ("detect".equals(endPoint)) {
-                    resourceProviders.add(new SingletonResourceProvider(new 
DetectorResource(serverStatus, tikaResource)));
-                } else if ("language".equals(endPoint)) {
-                    resourceProviders.add(new SingletonResourceProvider(new 
LanguageResource()));
-                } else if ("translate".equals(endPoint)) {
-                    resourceProviders.add(new SingletonResourceProvider(new 
TranslateResource(serverStatus)));
-                } else if ("tika".equals(endPoint)) {
-                    resourceProviders.add(new 
SingletonResourceProvider(tikaResource));
-                } else if ("unpack".equals(endPoint)) {
-                    resourceProviders.add(new SingletonResourceProvider(new 
UnpackerResource(tikaResource)));
-                } else if ("mime".equals(endPoint)) {
-                    resourceProviders.add(new SingletonResourceProvider(new 
TikaMimeTypes(tikaResource)));
-                } else if ("detectors".equals(endPoint)) {
-                    resourceProviders.add(new SingletonResourceProvider(new 
TikaDetectors(tikaResource)));
-                } else if ("parsers".equals(endPoint)) {
-                    resourceProviders.add(new SingletonResourceProvider(new 
TikaParsers(tikaResource)));
-                } else if ("version".equals(endPoint)) {
-                    resourceProviders.add(new SingletonResourceProvider(new 
TikaVersion()));
-                } else if ("pipes".equals(endPoint)) {
-                    addPipesResource = true;
-                } else if ("async".equals(endPoint)) {
-                    addAsyncResource = true;
-                } else if ("status".equals(endPoint)) {
-                    resourceProviders.add(new SingletonResourceProvider(new 
TikaServerStatus(serverStatus)));
+                switch (endPoint) {
+                    case "meta" -> resourceProviders.add(
+                            new SingletonResourceProvider(new 
MetadataResource(tikaResource)));
+                    case "rmeta" -> resourceProviders.add(
+                            new SingletonResourceProvider(new 
RecursiveMetadataResource(tikaResource)));
+                    case "detect" -> resourceProviders.add(
+                            new SingletonResourceProvider(new 
DetectorResource(serverStatus, tikaResource)));
+                    case "language" -> resourceProviders.add(
+                            new SingletonResourceProvider(new 
LanguageResource()));
+                    case "translate" -> resourceProviders.add(
+                            new SingletonResourceProvider(new 
TranslateResource(serverStatus)));
+                    case "tika" -> resourceProviders.add(
+                            new SingletonResourceProvider(tikaResource));
+                    case "unpack" -> resourceProviders.add(
+                            new SingletonResourceProvider(new 
UnpackerResource(tikaResource)));
+                    // "mime-types" accepted too: that is the path, and it is 
the natural guess.
+                    case "mime", "mime-types" -> resourceProviders.add(
+                            new SingletonResourceProvider(new 
TikaMimeTypes(tikaResource)));
+                    case "detectors" -> resourceProviders.add(
+                            new SingletonResourceProvider(new 
TikaDetectors(tikaResource)));
+                    case "parsers" -> resourceProviders.add(
+                            new SingletonResourceProvider(new 
TikaParsers(tikaResource)));
+                    case "version" -> resourceProviders.add(
+                            new SingletonResourceProvider(new TikaVersion()));
+                    case "status" -> resourceProviders.add(
+                            new SingletonResourceProvider(new 
TikaServerStatus(serverStatus)));
+                    case "pipes" -> addPipesResource = true;
+                    case "async" -> addAsyncResource = true;
+                    // Refuse rather than skip: a dropped name is a missing 
endpoint at
+                    // runtime with nothing in the log pointing at the config.
+                    default -> throw new TikaConfigException("Unrecognized 
endpoint '" + endPoint
+                            + "'. Valid endpoints: " + VALID_ENDPOINTS);
                 }
             }
         }

Reply via email to