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 72f4df7cb1 TIKA-4848: server, step 4 (#3106)
72f4df7cb1 is described below

commit 72f4df7cb1038516e976496d42b9ceaf82325efe
Author: Tim Allison <[email protected]>
AuthorDate: Mon Aug 31 10:34:08 2026 -0400

    TIKA-4848: server, step 4 (#3106)
---
 CHANGES.txt                                        |   5 +
 .../ROOT/pages/advanced/setting-limits.adoc        |   6 +-
 docs/modules/ROOT/pages/configuration/index.adoc   |   1 +
 docs/modules/ROOT/pages/security.adoc              |   5 +-
 .../ROOT/pages/using-tika/server/index.adoc        |  29 ++--
 .../core/TikaServerParseExceptionMapper.java       |  42 +++---
 .../apache/tika/server/core/TikaServerProcess.java |   2 +-
 .../server/core/resource/MetadataResource.java     |  12 +-
 .../tika/server/core/resource/TikaResource.java    |   8 ++
 .../tika/server/core/RedactedStackTraceTest.java   | 149 +++++++++++++++++++++
 .../core/TikaServerParseExceptionMapperTest.java   |  60 +++++++++
 .../resources/config-examples/server-basic.json    |   4 +
 12 files changed, 280 insertions(+), 43 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 7061f1e087..3880a79543 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,10 @@
 Release 4.1.0 - unreleased
 
+   * tika-server error bodies (the 422/500 exception mapper, /meta/{field})
+     now honor the exception-reporting policy; /meta/{field} returns the
+     already-formatted container exception instead of re-wrapping it with
+     server frames. Completes TIKA-4848 (TIKA-4848).
+
    * The exception-reporting policy now also governs the messages a pipes
      worker returns (fetch/emit/crash) and the container exception it
      records; part of TIKA-4848 step 3 (TIKA-4848).
diff --git a/docs/modules/ROOT/pages/advanced/setting-limits.adoc 
b/docs/modules/ROOT/pages/advanced/setting-limits.adoc
index d1f4fe0848..98588bf65f 100644
--- a/docs/modules/ROOT/pages/advanced/setting-limits.adoc
+++ b/docs/modules/ROOT/pages/advanced/setting-limits.adoc
@@ -383,8 +383,10 @@ chain are kept). `REDACTED`: exception class names only, 
no frames.
 }
 ----
 
-The same policy applies to the container and to every embedded document. It is
-loaded from the config only and is rejected in a per-request `parse-context`
+The same policy applies to the container and to every embedded document, to
+parser-level warnings (`tk:exception:warn`, `tk:exception:embedded-stream`), to
+the messages a pipes worker returns on fetch/emit/crash, and to tika-server's
+`422` error bodies. It is loaded from the config only and is rejected in a 
per-request `parse-context`
 (tika-server `/rmeta/config` and friends, `/pipes`, `/async`), so a caller 
cannot
 turn redaction back off.
 
diff --git a/docs/modules/ROOT/pages/configuration/index.adoc 
b/docs/modules/ROOT/pages/configuration/index.adoc
index 9d1407a603..c525061962 100644
--- a/docs/modules/ROOT/pages/configuration/index.adoc
+++ b/docs/modules/ROOT/pages/configuration/index.adoc
@@ -43,6 +43,7 @@ optional; anything you omit uses its defaults.
   "auto-detect-parser": { /* AutoDetectParser options */ },
   "parse-context": {
     "timeout-limits": { /* progress + total task timeouts */ },
+    "exception-reporting": { /* how much exception detail is reported */ },
     "unpack-config": { /* embedded-byte extraction */ }
     /* other SelfConfiguring components by component name */
   },
diff --git a/docs/modules/ROOT/pages/security.adoc 
b/docs/modules/ROOT/pages/security.adoc
index 24c2c080fa..b7f7f936d7 100644
--- a/docs/modules/ROOT/pages/security.adoc
+++ b/docs/modules/ROOT/pages/security.adoc
@@ -34,7 +34,10 @@ process, and containing that is the caller's responsibility: 
the project does no
 of service as a security issue when you opt out of the built-in sandboxing and 
parse files
 directly in your application. Tika's limits (`output-limits.writeLimit`,
 `embedded-limits.maxCount`, the metadata limiter) bound what a parse 
*produces*, not the work
-it does to produce it.
+it does to produce it. Exception detail (stack traces, messages that may quote 
paths or document
+text) is reported in full by default; `exception-reporting` in the 
`parse-context` section
+redacts and bounds it for callers less trusted than the operator
+(xref:advanced/setting-limits.adoc#_exception_reporting[details]).
 
 Concretely: an uncaught `RuntimeException`, `StackOverflowError`, or 
`OutOfMemoryError` from an
 in-process parse of a malformed or malicious file is a bug — please report it 
in
diff --git a/docs/modules/ROOT/pages/using-tika/server/index.adoc 
b/docs/modules/ROOT/pages/using-tika/server/index.adoc
index 3328c0824e..12f3c6df55 100644
--- a/docs/modules/ROOT/pages/using-tika/server/index.adoc
+++ b/docs/modules/ROOT/pages/using-tika/server/index.adoc
@@ -446,15 +446,26 @@ before the exception are discarded. Known gap.
 
 [IMPORTANT]
 ====
-Exception detail is returned in full, and Tika does not redact it. Stack 
traces and their messages
-can contain the spooled file's path, the source filename, and fragments of the 
document. That
-detail appears in `tk:exception:*` metadata on **successful** parses as well 
as in error bodies —
-including inside the `*.metadata.json` entries of an `/unpack` zip — so no 
single switch keeps it
-inside the server.
-
-If Tika's output is forwarded somewhere less trusted than the server itself, 
filter it on the way
-out: configure a `MetadataFilter` to drop the fields you do not want, and 
treat the whole response
-as potentially containing document-derived text.
+By default exception detail is returned in full. Stack traces and their 
messages can contain the
+spooled file's path, the source filename, and fragments of the document. That 
detail appears in
+`tk:exception:*` metadata on **successful** parses as well as in error bodies 
— including inside
+the `*.metadata.json` entries of an `/unpack` zip.
+
+One config setting governs all of those channels — the metadata values, the 
`422` bodies above,
+and the `message` of a `/pipes` or `/async` result:
+
+[source,json]
+----
+"parse-context": {
+  "exception-reporting": { "level": "MESSAGE_REDACTED", "maxLength": 10000 }
+}
+----
+
+`MESSAGE_REDACTED` keeps class names and frames but strips every message; 
`REDACTED` keeps class
+names only. It is refused in a per-request config so callers cannot turn it 
back off. See
+xref:advanced/setting-limits.adoc#_exception_reporting[Exception reporting]. 
Set it whenever the
+server's callers are less trusted than its operator; a `MetadataFilter` can 
additionally drop the
+`tk:exception:*` fields outright.
 ====
 
 === Truncated results (`PARTIAL_TIMEOUT`)
diff --git 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerParseExceptionMapper.java
 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerParseExceptionMapper.java
index 76ceac24fd..8456d72e1e 100644
--- 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerParseExceptionMapper.java
+++ 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerParseExceptionMapper.java
@@ -17,25 +17,28 @@
 package org.apache.tika.server.core;
 
 
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.io.Writer;
-
 import jakarta.ws.rs.WebApplicationException;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.ExceptionMapper;
 import jakarta.ws.rs.ext.Provider;
 
+import org.apache.tika.config.ExceptionReporting;
 import org.apache.tika.exception.EncryptedDocumentException;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.exception.UnsupportedFormatException;
+import org.apache.tika.utils.ExceptionUtils;
 
 @Provider
 public class TikaServerParseExceptionMapper implements 
ExceptionMapper<TikaServerParseException> {
 
+    private final ExceptionReporting exceptionReporting;
 
     public TikaServerParseExceptionMapper() {
+        this(ExceptionReporting.DEFAULT);
+    }
+
+    public TikaServerParseExceptionMapper(ExceptionReporting 
exceptionReporting) {
+        this.exceptionReporting = exceptionReporting;
     }
 
     public Response toResponse(TikaServerParseException e) {
@@ -70,28 +73,13 @@ public class TikaServerParseExceptionMapper implements 
ExceptionMapper<TikaServe
     }
 
     private Response buildResponse(Throwable cause, int i) {
-        if (cause != null) {
-            Writer result = new StringWriter();
-            PrintWriter writer = new PrintWriter(result);
-            cause.printStackTrace(writer);
-            writer.flush();
-            try {
-                result.flush();
-            } catch (IOException e) {
-                //something went seriously wrong
-                return Response
-                        .status(500)
-                        .build();
-            }
-            return Response
-                    .status(i)
-                    .entity(result.toString())
-                    .type("text/plain")
-                    .build();
-        } else {
-            return Response
-                    .status(i)
-                    .build();
+        if (cause == null) {
+            return Response.status(i).build();
         }
+        return Response
+                .status(i)
+                .entity(ExceptionUtils.format(cause, exceptionReporting))
+                .type("text/plain")
+                .build();
     }
 }
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 e83b8a67cb..0de1e88ceb 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
@@ -431,7 +431,7 @@ public class TikaServerProcess {
         writers.add(new JSONMessageBodyWriter());
         writers.add(new TextMessageBodyWriter());
         writers.addAll(loadWriterServices());
-        writers.add(new TikaServerParseExceptionMapper());
+        writers.add(new 
TikaServerParseExceptionMapper(tikaResource.getExceptionReporting()));
         writers.add(new BadRequestExceptionMapper());
         writers.add(new JSONObjWriter());
 
diff --git 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/MetadataResource.java
 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/MetadataResource.java
index eaecf7aa8b..fe6ece30c2 100644
--- 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/MetadataResource.java
+++ 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/MetadataResource.java
@@ -27,8 +27,10 @@ import jakarta.ws.rs.PUT;
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.PathParam;
 import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.WebApplicationException;
 import jakarta.ws.rs.core.Context;
 import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.MultivaluedMap;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.core.UriInfo;
@@ -36,7 +38,6 @@ import org.apache.cxf.jaxrs.ext.multipart.Attachment;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import org.apache.tika.exception.TikaException;
 import org.apache.tika.extractor.DocumentSelector;
 import org.apache.tika.extractor.SkipEmbeddedDocumentSelector;
 import org.apache.tika.io.TikaInputStream;
@@ -46,7 +47,6 @@ import org.apache.tika.parser.ParseContext;
 import org.apache.tika.pipes.api.ParseMode;
 import org.apache.tika.sax.BasicContentHandlerFactory;
 import org.apache.tika.sax.ContentHandlerFactory;
-import org.apache.tika.server.core.TikaServerParseException;
 
 
 @Path("/meta")
@@ -153,7 +153,13 @@ public class MetadataResource {
 
         String containerException = 
metadata.get(TikaCoreProperties.CONTAINER_EXCEPTION);
         if (containerException != null && !containerException.isEmpty()) {
-            throw new TikaServerParseException(new 
TikaException(containerException));
+            // Already policy-formatted; wrapping it as an exception message 
would let the
+            // mapper redact it away and append this server's own frames.
+            throw new WebApplicationException(Response
+                    .status(422)
+                    .entity(containerException)
+                    .type(MediaType.TEXT_PLAIN)
+                    .build());
         }
 
         if (metadata.get(field) == null) {
diff --git 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/TikaResource.java
 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/TikaResource.java
index 03f1354895..aa57ed9ef6 100644
--- 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/TikaResource.java
+++ 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/TikaResource.java
@@ -51,6 +51,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.tika.Tika;
+import org.apache.tika.config.ExceptionReporting;
 import org.apache.tika.config.JsonConfig;
 import org.apache.tika.config.OutputLimits;
 import org.apache.tika.config.loader.TikaLoader;
@@ -92,6 +93,7 @@ public class TikaResource {
     // createRequestContext); the forked worker loads these same defaults from 
the same config.
     private final MetadataWriteLimiterFactory 
configMetadataWriteLimiterFactory;
     private final OutputLimits configOutputLimits;
+    private final ExceptionReporting configExceptionReporting;
     private final boolean configSuppliesContentHandlerFactory;
 
     /**
@@ -110,6 +112,7 @@ public class TikaResource {
         ParseContext configDefaults = loadConfigDefaults();
         this.configMetadataWriteLimiterFactory = 
configDefaults.get(MetadataWriteLimiterFactory.class);
         this.configOutputLimits = OutputLimits.get(configDefaults);
+        this.configExceptionReporting = ExceptionReporting.get(configDefaults);
         this.configSuppliesContentHandlerFactory =
                 configDefaults.get(ContentHandlerFactory.class) != null;
     }
@@ -123,6 +126,11 @@ public class TikaResource {
         return pipesParsingHelper;
     }
 
+    /** The config's exception-reporting policy, for channels that have no 
ParseContext. */
+    public ExceptionReporting getExceptionReporting() {
+        return configExceptionReporting;
+    }
+
     /**
      * Reads the config's {@code parse-context} section. Private and called 
once: the values we
      * need are cached above, and a request must not carry these defaults (see
diff --git 
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/RedactedStackTraceTest.java
 
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/RedactedStackTraceTest.java
new file mode 100644
index 0000000000..0e699a4d1b
--- /dev/null
+++ 
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/RedactedStackTraceTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.server.core;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import jakarta.ws.rs.core.Response;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.lifecycle.ResourceProvider;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.serialization.JsonMetadataList;
+import org.apache.tika.serialization.config.JsonConfigHelper;
+import org.apache.tika.server.core.resource.MetadataResource;
+import org.apache.tika.server.core.resource.RecursiveMetadataResource;
+import org.apache.tika.server.core.resource.UnpackerResource;
+import org.apache.tika.server.core.writer.JSONMessageBodyWriter;
+import org.apache.tika.server.core.writer.MetadataListMessageBodyWriter;
+import org.apache.tika.server.core.writer.TextMessageBodyWriter;
+
+/**
+ * With exception-reporting set to MESSAGE_REDACTED in the server config, no 
channel may leak
+ * an exception message: /rmeta metadata, /unpack 422 body, /meta/{field} 422 
body.
+ * {@link StackTraceTest} pins the FULL default.
+ */
+public class RedactedStackTraceTest extends CXFTestBase {
+
+    private static final String TEST_NULL = 
"test-documents/mock/null_pointer.xml";
+    private static final String MESSAGE = "null pointer message";
+    private static final String CLASS = "java.lang.NullPointerException";
+    private static final String UNPACK_CONFIG_TEMPLATE = 
"/configs/cxf-unpack-test-template.json";
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    @TempDir
+    private static Path unpackTempDir;
+
+    @Override
+    protected void setUpResources(JAXRSServerFactoryBean sf) {
+        List<ResourceProvider> providers = new ArrayList<>();
+        providers.add(new SingletonResourceProvider(new 
MetadataResource(tikaResource)));
+        providers.add(new SingletonResourceProvider(new 
RecursiveMetadataResource(tikaResource)));
+        providers.add(new SingletonResourceProvider(tikaResource));
+        providers.add(new SingletonResourceProvider(new 
UnpackerResource(tikaResource)));
+        sf.setResourceProviders(providers);
+    }
+
+    @Override
+    protected void setUpProviders(JAXRSServerFactoryBean sf) {
+        List<Object> providers = new ArrayList<>();
+        providers.add(new 
TikaServerParseExceptionMapper(tikaResource.getExceptionReporting()));
+        providers.add(new JSONMessageBodyWriter());
+        providers.add(new TextMessageBodyWriter());
+        providers.add(new MetadataListMessageBodyWriter());
+        sf.setProviders(providers);
+    }
+
+    @Override
+    protected InputStream getPipesConfigInputStream() throws IOException {
+        Map<String, Object> replacements = new HashMap<>();
+        replacements.put("UNPACK_EMITTER_BASE_PATH", 
unpackTempDir.toAbsolutePath().toString());
+        replacements.put("PLUGINS_PATHS",
+                
Paths.get("target/plugins").toAbsolutePath().toString().replace("\\", "/"));
+        replacements.put("TIMEOUT_MILLIS", 60000L);
+        JsonNode config = 
JsonConfigHelper.loadFromResource(UNPACK_CONFIG_TEMPLATE,
+                CXFTestBase.class, replacements);
+        ((ObjectNode) 
config.get("parse-context")).putObject("exception-reporting")
+                .put("level", "MESSAGE_REDACTED").put("maxLength", 10000);
+        return new ByteArrayInputStream(
+                
MAPPER.writeValueAsString(config).getBytes(StandardCharsets.UTF_8));
+    }
+
+    @Override
+    protected Path getUnpackEmitterBasePath() {
+        return unpackTempDir;
+    }
+
+    private static void assertRedacted(String s) {
+        assertTrue(s.contains(CLASS), s);
+        assertTrue(s.contains("\tat "), s);
+        assertFalse(s.contains(MESSAGE), s);
+    }
+
+    @Test
+    public void rmeta() throws Exception {
+        Response response = WebClient.create(endPoint + "/rmeta")
+                .accept("application/json")
+                .put(ClassLoader.getSystemResourceAsStream(TEST_NULL));
+        assertEquals(200, response.getStatus());
+        List<Metadata> list = JsonMetadataList.fromJson(
+                new java.io.InputStreamReader((InputStream) 
response.getEntity(),
+                        StandardCharsets.UTF_8));
+        
assertRedacted(list.get(0).get(TikaCoreProperties.CONTAINER_EXCEPTION));
+    }
+
+    @Test
+    public void unpack() throws Exception {
+        Response response = WebClient.create(endPoint + "/unpack")
+                .put(ClassLoader.getSystemResourceAsStream(TEST_NULL));
+        assertEquals(422, response.getStatus());
+        assertRedacted(getStringFromInputStream((InputStream) 
response.getEntity()));
+    }
+
+    @Test
+    public void metaField() throws Exception {
+        Response response = WebClient.create(endPoint + "/meta/Content-Type")
+                .accept("text/plain")
+                .put(ClassLoader.getSystemResourceAsStream(TEST_NULL));
+        assertEquals(422, response.getStatus());
+        String body = getStringFromInputStream((InputStream) 
response.getEntity());
+        assertRedacted(body);
+        // the body is the container exception itself, not re-wrapped with 
server frames
+        assertFalse(body.contains("MetadataResource"), body);
+    }
+}
diff --git 
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerParseExceptionMapperTest.java
 
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerParseExceptionMapperTest.java
new file mode 100644
index 0000000000..9d9883d94d
--- /dev/null
+++ 
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerParseExceptionMapperTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.server.core;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import jakarta.ws.rs.core.Response;
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.config.ExceptionReporting;
+import org.apache.tika.exception.TikaException;
+
+public class TikaServerParseExceptionMapperTest {
+
+    private static final String SECRET = "secret /path/to/file";
+
+    private static String body(ExceptionReporting reporting) {
+        Response r = new TikaServerParseExceptionMapper(reporting)
+                .toResponse(new TikaServerParseException(new 
TikaException(SECRET)));
+        assertEquals(422, r.getStatus());
+        return (String) r.getEntity();
+    }
+
+    @Test
+    public void fullByDefault() {
+        String body = body(null);
+        assertTrue(body.contains("org.apache.tika.exception.TikaException: " + 
SECRET), body);
+    }
+
+    @Test
+    public void messageRedacted() {
+        String body = body(new 
ExceptionReporting(ExceptionReporting.Level.MESSAGE_REDACTED, -1));
+        assertTrue(body.contains("org.apache.tika.exception.TikaException"), 
body);
+        assertTrue(body.contains("\tat "), body);
+        assertFalse(body.contains(SECRET), body);
+    }
+
+    @Test
+    public void maxLength() {
+        String body = body(new 
ExceptionReporting(ExceptionReporting.Level.FULL, 30));
+        assertTrue(body.endsWith("...[truncated]"), body);
+        assertFalse(body.contains(SECRET), body);
+    }
+}
diff --git 
a/tika-server/tika-server-core/src/test/resources/config-examples/server-basic.json
 
b/tika-server/tika-server-core/src/test/resources/config-examples/server-basic.json
index 044303b17c..fe35775d2e 100644
--- 
a/tika-server/tika-server-core/src/test/resources/config-examples/server-basic.json
+++ 
b/tika-server/tika-server-core/src/test/resources/config-examples/server-basic.json
@@ -9,6 +9,10 @@
     "timeout-limits": {
       "totalTaskTimeoutMillis": 3600000,
       "progressTimeoutMillis": 120000
+    },
+    "exception-reporting": {
+      "level": "MESSAGE_REDACTED",
+      "maxLength": 10000
     }
   },
   "parsers": [

Reply via email to