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 508e9021890f6b20293c89a0ff5a3f59156d91c8 Merge: 9c9e675553 0b84d33943 Author: tallison <[email protected]> AuthorDate: Mon Aug 10 21:16:33 2026 -0400 Merge remote-tracking branch 'origin/main' into TIKA-4809-stage-9 .../migration-to-4x/migrating-tika-server-4x.adoc | 69 ++++++++ .../pages/migration-to-4x/migrating-to-4x.adoc | 2 +- docs/modules/ROOT/pages/pipes/cpu-sizing.adoc | 2 +- .../ROOT/pages/using-tika/server/index.adoc | 33 +--- .../java/org/apache/tika/config/ServiceLoader.java | 9 +- .../charsoup/CharSoupMetadataFilter.java | 8 + .../metadatafilter/OpenNLPMetadataFilter.java | 8 + .../metadatafilter/OptimaizeMetadataFilter.java | 8 + .../org/apache/tika/parser/mp3/ID3v2Frame.java | 16 +- .../java/org/apache/tika/parser/mp4/MP4Parser.java | 30 +++- .../org/apache/tika/parser/mp4/TikaMp4Reader.java | 149 +++++++++++++++++ .../tika/parser/mp4/TikaMp4SoundHandler.java | 15 +- .../tika/parser/mp4/boxes/TikaUserDataBox.java | 167 +++++++++---------- .../org/apache/tika/parser/video/FLVParser.java | 53 ++++-- .../org/apache/tika/parser/mp3/ID3v2FrameTest.java | 13 ++ .../org/apache/tika/parser/mp3/Mp3ParserTest.java | 22 +++ .../org/apache/tika/parser/mp4/MP4ParserTest.java | 109 ++++++++++++ .../tika/parser/mp4/boxes/TikaUserDataBoxTest.java | 61 +++++++ .../apache/tika/parser/video/FLVParserTest.java | 22 +++ .../tika/serialization/ParseContextUtils.java | 10 +- .../TestParseContextSerialization.java | 16 ++ .../org/apache/tika/server/core/TikaServerCli.java | 4 +- .../apache/tika/server/core/TikaServerConfig.java | 64 +++---- .../apache/tika/server/core/TikaServerProcess.java | 107 +++++++----- .../core/resource/RecursiveMetadataResource.java | 43 +---- .../server/core/resource/ServerHandlerConfig.java | 37 ----- .../tika/server/core/resource/TikaResource.java | 115 +++---------- .../main/resources/tika-server-config-default.xml | 55 ------- .../server/core/RecursiveMetadataResourceTest.java | 61 ------- .../apache/tika/server/core/TikaResourceTest.java | 30 ---- .../server/core/benchmark/TikaServerBenchmark.java | 1 - .../configs/cxf-unpack-test-template.json | 10 +- .../standard/RecursiveMetadataResourceTest.java | 183 +++++---------------- .../tika/server/standard/TikaResourceTest.java | 59 ------- 34 files changed, 836 insertions(+), 755 deletions(-) diff --cc docs/modules/ROOT/pages/using-tika/server/index.adoc index ee7502c34c,8d054258f8..0329241559 --- a/docs/modules/ROOT/pages/using-tika/server/index.adoc +++ b/docs/modules/ROOT/pages/using-tika/server/index.adoc @@@ -343,14 -304,10 +329,6 @@@ Server behavior beyond host/port is con |`""` (off) |`*` to allow any origin, or an explicit origin string. Empty disables CORS. - |`digest` - |`""` (off) - |Compute a digest of the parsed bytes. Comma-separated algorithm names: `md5`, `sha1`, `sha256`, `sha384`, `sha512`. - - |`digestMarkLimit` - |`20971520` (20 MiB) - |Max bytes buffered for digest computation. -|`returnStackTrace` -|`false` -|Include parser stack traces in error responses. Useful in dev, dangerous in production (leaks internals). -- |`logLevel` |_inherited_ |`debug` or `info` to override the runtime log level. diff --cc tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerConfig.java index d334359007,f17dbaa0e4..57fe4cef23 --- a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerConfig.java +++ b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerConfig.java @@@ -70,7 -68,8 +68,7 @@@ private long forkedProcessShutdownMilli private boolean allowPipes = false; private boolean allowPerRequestConfig = false; private String cors = ""; - private String idBase = UUID - private boolean returnStackTrace = false; + private String id = UUID .randomUUID() .toString(); private int port = DEFAULT_PORT; @@@ -244,29 -240,13 +239,6 @@@ this.configPath = Paths.get(path); } - public int getDigestMarkLimit() { - return digestMarkLimit; - } - - public void setDigestMarkLimit(int digestMarkLimit) { - this.digestMarkLimit = digestMarkLimit; - } - - /** - * digest configuration string, e.g. md5 or sha256, alternately w 16 or 32 encoding, - * e.g. md5:32,sha256:16 would result in two digests per file - * - * @return - */ - public String getDigest() { - return digest; - public boolean isReturnStackTrace() { - return returnStackTrace; -- } -- - public void setDigest(String digest) { - LOG.info("As of Tika 2.5.0, you can set the digester via the AutoDetectParserConfig in " + "tika-config.xml. We plan to remove this commandline option in 2.8.0"); - this.digest = digest; - public void setReturnStackTrace(boolean returnStackTrace) { - this.returnStackTrace = returnStackTrace; -- } - public TlsConfig getTlsConfig() { return tlsConfig; diff --cc tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java index 5cf042ae90,b5c39f2ad3..ae60613222 --- 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 -101,10 +101,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", ++ "unpack", "detect", "language", "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 -162,35 +162,31 @@@ 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(); @@@ -369,32 -397,38 +392,36 @@@ // 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 ("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); } } } diff --cc tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaResourceTest.java index d41cd8143b,8e89a38980..d6f7f581a9 --- a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaResourceTest.java +++ b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaResourceTest.java @@@ -110,32 -109,13 +110,14 @@@ public class TikaResourceTest extends C assertEquals("Nikolai Lobachevsky", metadata.get("author")); assertEquals("application/mock+xml", metadata.get(Metadata.CONTENT_TYPE)); assertContains("some content", metadata.get(TikaCoreProperties.TIKA_CONTENT)); - // returnStackTrace defaults to false here, so CONTAINER_EXCEPTION is trimmed to - // the caught exception's own class + message -- the NPE detail underneath it is - // intentionally not exposed by default. + // Exception detail is reported in full. Tika does not redact it: any scheme that + // strips the message has to parse the rendered trace, and it would in any case be + // undone by the same detail travelling in tk:exception:* on other responses. Filter + // metadata before forwarding it somewhere less trusted. assertContains("TikaException", metadata.get(TikaCoreProperties.CONTAINER_EXCEPTION)); - assertNotFound("null pointer message", metadata.get(TikaCoreProperties.CONTAINER_EXCEPTION)); + assertContains("null pointer message", metadata.get(TikaCoreProperties.CONTAINER_EXCEPTION)); } - @Test - public void testJsonWriteLimit() throws Exception { - Response response = WebClient - .create(endPoint + TIKA_PATH + "/json") - .header("writeLimit", "100") - .put(ClassLoader.getSystemResourceAsStream(TEST_HELLO_WORLD_LONG)); - Metadata metadata = JsonMetadata.fromJson(new InputStreamReader(((InputStream) response.getEntity()), StandardCharsets.UTF_8)); - - assertEquals("Nikolai Lobachevsky", metadata.get("author")); - assertEquals("application/mock+xml", metadata.get(Metadata.CONTENT_TYPE)); - assertContains("Hello world", metadata.get(TikaCoreProperties.TIKA_CONTENT)); - assertNotFound("dissolve", metadata.get(TikaCoreProperties.TIKA_CONTENT)); - assertTrue(metadata - .get(TikaCoreProperties.CONTAINER_EXCEPTION) - .startsWith("org.apache.tika.exception.WriteLimitReachedException")); - assertEquals("true", metadata.get(TikaCoreProperties.WRITE_LIMIT_REACHED)); - } - @Test public void testJsonHandlerType() throws Exception { // Default /tika/json uses text handler
