You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tika.apache.org by ta...@apache.org on 2026/09/01 17:35:14 UTC
(tika) 01/02: TIKA-4826: stop interning untrusted content types into the MimeTypes registry; deprecate forName
This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch TIKA-4826
in repository https://gitbox.apache.org/repos/asf/tika.git
commit cade330abe377a8d997a6ac25b38db9a196a30a7
Author: tallison <ta...@apache.org>
AuthorDate: Tue Sep 1 11:38:18 2026 -0400
TIKA-4826: stop interning untrusted content types into the MimeTypes registry; deprecate forName
---
CHANGES.txt | 9 +++++
.../java/org/apache/tika/io/FilenameUtils.java | 10 ++---
.../main/java/org/apache/tika/mime/MimeTypes.java | 43 ++++++++++++++++++++--
.../java/org/apache/tika/mime/MimeTypesReader.java | 2 +-
.../mime/ProbabilisticMimeDetectionSelector.java | 2 +-
.../java/org/apache/tika/io/FilenameUtilsTest.java | 14 +++++++
.../org/apache/tika/mime/MimeDetectionTest.java | 20 ++++++++++
.../org/apache/tika/eval/app/db/MimeBuffer.java | 6 ++-
.../apache/tika/example/ExtractEmbeddedFiles.java | 9 +++--
.../apache/tika/parser/mail/RFC822ParserTest.java | 15 ++++++++
.../testRFC822-unknown-content-type.eml | 21 +++++++++++
.../parser/microsoft/AbstractPOIFSExtractor.java | 9 +++--
12 files changed, 143 insertions(+), 17 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index ce1baa8f55..9165c4b9f0 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,14 @@
Release 4.1.0 - unreleased
+ * Content-type hints from untrusted input (request headers, document
+ metadata) are no longer interned into the shared MimeTypes registry
+ during detection and extension lookup, closing a slow unbounded
+ memory growth path. MimeTypes.forName is deprecated (unchanged
+ behavior) in favor of forNameAndRegister, which names the
+ registration side effect, and getRegisteredMimeType for
+ side-effect-free lookups; forName will be removed in Tika 5
+ (TIKA-4826).
+
* Add "exception-reporting" parse-context config to redact and bound
exception text in metadata, tika-server error bodies and pipes/grpc
messages; FileSystemEmitter writes atomically (TIKA-4848).
diff --git a/tika-core/src/main/java/org/apache/tika/io/FilenameUtils.java b/tika-core/src/main/java/org/apache/tika/io/FilenameUtils.java
index 53ac50e968..27b80340e1 100644
--- a/tika-core/src/main/java/org/apache/tika/io/FilenameUtils.java
+++ b/tika-core/src/main/java/org/apache/tika/io/FilenameUtils.java
@@ -27,6 +27,7 @@ import org.apache.tika.extractor.EmbeddedDocumentUtil;
import org.apache.tika.metadata.HttpHeaders;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.mime.MimeType;
import org.apache.tika.mime.MimeTypeException;
import org.apache.tika.mime.MimeTypes;
import org.apache.tika.utils.StringUtils;
@@ -405,11 +406,10 @@ public class FilenameUtils {
private static String lookupExtension(String mime) {
try {
- String ext = MIME_TYPES
- .forName(mime)
- .getExtension();
- if (!StringUtils.isBlank(ext)) {
- return ext;
+ // not forName: don't intern untrusted types into the registry (TIKA-4826)
+ MimeType mimeType = MIME_TYPES.getRegisteredMimeType(mime);
+ if (mimeType != null && !StringUtils.isBlank(mimeType.getExtension())) {
+ return mimeType.getExtension();
}
} catch (MimeTypeException e) {
//swallow
diff --git a/tika-core/src/main/java/org/apache/tika/mime/MimeTypes.java b/tika-core/src/main/java/org/apache/tika/mime/MimeTypes.java
index c178729cde..83222f9f66 100644
--- a/tika-core/src/main/java/org/apache/tika/mime/MimeTypes.java
+++ b/tika-core/src/main/java/org/apache/tika/mime/MimeTypes.java
@@ -206,7 +206,7 @@ public final class MimeTypes implements Detector, Serializable {
*/
@Deprecated
public MimeType getMimeType(File file) throws MimeTypeException, IOException {
- return forName(new Tika(this).detect(file));
+ return forNameAndRegister(new Tika(this).detect(file));
}
/**
@@ -296,7 +296,8 @@ public final class MimeTypes implements Detector, Serializable {
try {
TextDetector detector = new TextDetector(getMinLength());
try (TikaInputStream tis = TikaInputStream.get(data)) {
- MimeType type = forName(detector.detect(tis, new Metadata(), new ParseContext()).toString());
+ MimeType type = forNameAndRegister(
+ detector.detect(tis, new Metadata(), new ParseContext()).toString());
return Collections.singletonList(type);
}
} catch (Exception e) {
@@ -347,8 +348,27 @@ public final class MimeTypes implements Detector, Serializable {
* @param name media type name (case-insensitive)
* @return the registered media type with the given name or alias
* @throws MimeTypeException if the given media type name is invalid
+ * @deprecated since 4.1.0: the name hides the registration side effect.
+ * Use {@link #forNameAndRegister(String)}; on untrusted input,
+ * {@link #forNameWithoutRegistration(String)} (drop-in) or
+ * {@link #getRegisteredMimeType(String)} (null if unknown). Removal in Tika 5.
*/
+ @Deprecated
public MimeType forName(String name) throws MimeTypeException {
+ return forNameAndRegister(name);
+ }
+
+ /**
+ * Returns the registered media type with the given name (or alias),
+ * permanently registering it if valid but unknown. Never call with
+ * untrusted names — each unknown name grows the registry (TIKA-4826);
+ * use {@link #forNameWithoutRegistration(String)} instead.
+ *
+ * @param name media type name (case-insensitive)
+ * @return the registered media type with the given name or alias
+ * @throws MimeTypeException if the given media type name is invalid
+ */
+ public MimeType forNameAndRegister(String name) throws MimeTypeException {
MediaType type = MediaType.parse(name);
if (type == null) {
throw new MimeTypeException("Invalid media type name: " + name);
@@ -371,6 +391,23 @@ public final class MimeTypes implements Detector, Serializable {
return mime;
}
+ /**
+ * Like {@link #forNameAndRegister(String)}, but never modifies the
+ * registry: unknown names get a transient MimeType (TIKA-4826).
+ *
+ * @param name media type name (case-insensitive)
+ * @return the registered media type, or a transient one if unknown
+ * @throws MimeTypeException if the given media type name is invalid
+ */
+ public MimeType forNameWithoutRegistration(String name) throws MimeTypeException {
+ MediaType type = MediaType.parse(name);
+ if (type == null) {
+ throw new MimeTypeException("Invalid media type name: " + name);
+ }
+ MimeType mime = types.get(registry.normalize(type));
+ return mime != null ? mime : new MimeType(type);
+ }
+
/**
* Returns the registered, normalised media type with the given name (or alias).
*
@@ -574,7 +611,7 @@ public final class MimeTypes implements Detector, Serializable {
String typeName = metadata.get(HttpHeaders.CONTENT_TYPE);
if (typeName != null) {
try {
- MimeType hint = forName(typeName);
+ MimeType hint = forNameWithoutRegistration(typeName);
possibleTypes = applyHint(possibleTypes, hint);
} catch (MimeTypeException e) {
// Malformed type name, ignore
diff --git a/tika-core/src/main/java/org/apache/tika/mime/MimeTypesReader.java b/tika-core/src/main/java/org/apache/tika/mime/MimeTypesReader.java
index 04c780fa1a..b991e29edc 100644
--- a/tika-core/src/main/java/org/apache/tika/mime/MimeTypesReader.java
+++ b/tika-core/src/main/java/org/apache/tika/mime/MimeTypesReader.java
@@ -266,7 +266,7 @@ public class MimeTypesReader extends DefaultHandler implements MimeTypesReaderMe
String interpretedAttr = attributes.getValue(INTERPRETED_ATTR);
boolean interpreted = "true".equals(interpretedAttr);
try {
- type = types.forName(name);
+ type = types.forNameAndRegister(name);
type.setInterpreted(interpreted);
} catch (MimeTypeException e) {
handleMimeError(name, e, qName, attributes);
diff --git a/tika-core/src/main/java/org/apache/tika/mime/ProbabilisticMimeDetectionSelector.java b/tika-core/src/main/java/org/apache/tika/mime/ProbabilisticMimeDetectionSelector.java
index 6bb3cb02cc..d42f4577f1 100644
--- a/tika-core/src/main/java/org/apache/tika/mime/ProbabilisticMimeDetectionSelector.java
+++ b/tika-core/src/main/java/org/apache/tika/mime/ProbabilisticMimeDetectionSelector.java
@@ -176,7 +176,7 @@ public class ProbabilisticMimeDetectionSelector implements Detector {
if (typeName != null) {
try {
// MimeType hint = forName(typeName);
- metaHint = mimeTypes.forName(typeName);
+ metaHint = mimeTypes.forNameWithoutRegistration(typeName);
// possibleTypes = applyHint(possibleTypes, hint);
} catch (MimeTypeException e) {
// Malformed type name, ignore
diff --git a/tika-core/src/test/java/org/apache/tika/io/FilenameUtilsTest.java b/tika-core/src/test/java/org/apache/tika/io/FilenameUtilsTest.java
index 014096ea1b..1c4ec7675a 100644
--- a/tika-core/src/test/java/org/apache/tika/io/FilenameUtilsTest.java
+++ b/tika-core/src/test/java/org/apache/tika/io/FilenameUtilsTest.java
@@ -31,6 +31,7 @@ import org.junit.jupiter.api.Test;
import org.apache.tika.metadata.HttpHeaders;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.mime.MimeTypes;
import org.apache.tika.utils.StringUtils;
public class FilenameUtilsTest {
@@ -257,6 +258,19 @@ public class FilenameUtilsTest {
return FilenameUtils.getSanitizedEmbeddedFileName(getMetadata(name, mimeType), ".bin", 50);
}
+ @Test
+ public void testCalculateExtension() throws Exception {
+ Metadata metadata = new Metadata();
+ metadata.set(HttpHeaders.CONTENT_TYPE, "application/pdf");
+ assertEquals(".pdf", FilenameUtils.calculateExtension(metadata, ".tmp"));
+
+ // TIKA-4826: unknown types must not grow the shared registry
+ String unknown = "application/x-tika-4826-filenameutils";
+ metadata.set(HttpHeaders.CONTENT_TYPE, unknown);
+ assertEquals(".bin", FilenameUtils.calculateExtension(metadata, ".tmp"));
+ assertNull(MimeTypes.getDefaultMimeTypes().getRegisteredMimeType(unknown));
+ }
+
private String sanitizeFilename(String name) {
return FilenameUtils.getSanitizedEmbeddedFileName(getMetadata(name), ".bin", 50);
}
diff --git a/tika-core/src/test/java/org/apache/tika/mime/MimeDetectionTest.java b/tika-core/src/test/java/org/apache/tika/mime/MimeDetectionTest.java
index 5748f185c1..af928a9361 100644
--- a/tika-core/src/test/java/org/apache/tika/mime/MimeDetectionTest.java
+++ b/tika-core/src/test/java/org/apache/tika/mime/MimeDetectionTest.java
@@ -21,6 +21,7 @@ import static java.nio.charset.StandardCharsets.UTF_16LE;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
@@ -108,6 +109,25 @@ public class MimeDetectionTest {
testUrlWithoutContent("text/x-php", "ftp://test.com/test.php");
}
+ @Test
+ public void testUnknownContentTypeHintNotInterned() throws Exception {
+ // TIKA-4826: unknown content-type hints must not grow the registry
+ MimeTypes mimeTypes = MimeTypesFactory.create("tika-mimetypes.xml");
+ String unknown = "application/x-tika-4826-unknown";
+ Metadata metadata = new Metadata();
+ metadata.set(HttpHeaders.CONTENT_TYPE, unknown);
+ assertEquals(unknown, mimeTypes.detect(null, metadata, new ParseContext()).toString());
+ assertNull(mimeTypes.getRegisteredMimeType(unknown));
+
+ ProbabilisticMimeDetectionSelector selector =
+ new ProbabilisticMimeDetectionSelector(mimeTypes);
+ String unknown2 = "application/x-tika-4826-unknown2";
+ metadata = new Metadata();
+ metadata.set(HttpHeaders.CONTENT_TYPE, unknown2);
+ selector.detect(null, metadata, new ParseContext());
+ assertNull(mimeTypes.getRegisteredMimeType(unknown2));
+ }
+
@Test
public void testByteOrderMark() throws Exception {
try (TikaInputStream tis = TikaInputStream.get("\ufefftest".getBytes(UTF_16LE))) {
diff --git a/tika-eval/tika-eval-app/src/main/java/org/apache/tika/eval/app/db/MimeBuffer.java b/tika-eval/tika-eval-app/src/main/java/org/apache/tika/eval/app/db/MimeBuffer.java
index 9541020541..39d741fd9d 100644
--- a/tika-eval/tika-eval-app/src/main/java/org/apache/tika/eval/app/db/MimeBuffer.java
+++ b/tika-eval/tika-eval-app/src/main/java/org/apache/tika/eval/app/db/MimeBuffer.java
@@ -101,7 +101,11 @@ public class MimeBuffer extends AbstractDBBuffer {
* @throws MimeTypeException thrown if MimeTypes can't parse the contentType
*/
public static String getExtension(String contentType, MimeTypes mimeTypes) throws MimeTypeException {
- MimeType mime = mimeTypes.forName(contentType);
+ // not forName: don't intern untrusted types into the registry (TIKA-4826)
+ MimeType mime = mimeTypes.getRegisteredMimeType(contentType);
+ if (mime == null) {
+ return tryTextyTypes(MediaType.parse(contentType));
+ }
return getExtension(mime);
}
diff --git a/tika-example/src/main/java/org/apache/tika/example/ExtractEmbeddedFiles.java b/tika-example/src/main/java/org/apache/tika/example/ExtractEmbeddedFiles.java
index 6467483b04..fb9b0745c5 100644
--- a/tika-example/src/main/java/org/apache/tika/example/ExtractEmbeddedFiles.java
+++ b/tika-example/src/main/java/org/apache/tika/example/ExtractEmbeddedFiles.java
@@ -100,10 +100,13 @@ public class ExtractEmbeddedFiles {
if (name.indexOf('.') == -1 && contentType != null) {
try {
- name += tikaLoader
+ // not forName: don't intern untrusted types into the registry (TIKA-4826)
+ MimeType mimeType = tikaLoader
.getMimeTypes()
- .forName(contentType.toString())
- .getExtension();
+ .getRegisteredMimeType(contentType.toString());
+ if (mimeType != null) {
+ name += mimeType.getExtension();
+ }
} catch (MimeTypeException e) {
e.printStackTrace();
}
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/test/java/org/apache/tika/parser/mail/RFC822ParserTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/test/java/org/apache/tika/parser/mail/RFC822ParserTest.java
index 17412fed55..8f9e69a7fe 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/test/java/org/apache/tika/parser/mail/RFC822ParserTest.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/test/java/org/apache/tika/parser/mail/RFC822ParserTest.java
@@ -20,6 +20,7 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
@@ -51,6 +52,7 @@ import org.apache.tika.metadata.Message;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.mime.MediaType;
+import org.apache.tika.mime.MimeTypes;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
@@ -479,6 +481,19 @@ public class RFC822ParserTest extends TikaTest {
metadataList.get(1).get(TikaCoreProperties.EMBEDDED_RESOURCE_PATH));
}
+ @Test
+ public void testUnknownContentTypesNotInterned() throws Exception {
+ // TIKA-4826 tripwire: a full parse must not grow the shared registry
+ MimeTypes mimeTypes = MimeTypes.getDefaultMimeTypes();
+ int before = mimeTypes.getMediaTypeRegistry().getTypes().size();
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testRFC822-unknown-content-type.eml");
+ assertEquals(3, metadataList.size());
+ assertNull(mimeTypes.getRegisteredMimeType("application/x-tika-4826-eml-unknown"));
+ assertNull(mimeTypes.getRegisteredMimeType("application/x-tika-4826-eml-unknown-noname"));
+ assertEquals(before, mimeTypes.getMediaTypeRegistry().getTypes().size());
+ }
+
@Test
public void testMixedRelatedMultipart() throws Exception {
/*
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/test/resources/test-documents/testRFC822-unknown-content-type.eml b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/test/resources/test-documents/testRFC822-unknown-content-type.eml
new file mode 100644
index 0000000000..552430ea7d
--- /dev/null
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/test/resources/test-documents/testRFC822-unknown-content-type.eml
@@ -0,0 +1,21 @@
+From: sender@example.com
+To: recipient@example.com
+Subject: TIKA-4826 unknown content types
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="tika4826boundary"
+
+--tika4826boundary
+Content-Type: text/plain; charset=us-ascii
+
+Body text.
+--tika4826boundary
+Content-Type: application/x-tika-4826-eml-unknown
+Content-Disposition: attachment; filename="payload.dat"
+
+not really binary, but an unknown declared content type
+--tika4826boundary
+Content-Type: application/x-tika-4826-eml-unknown-noname
+Content-Disposition: attachment
+
+a second attachment with no filename
+--tika4826boundary--
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/AbstractPOIFSExtractor.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/AbstractPOIFSExtractor.java
index 28dbffa236..88d9652783 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/AbstractPOIFSExtractor.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/AbstractPOIFSExtractor.java
@@ -314,9 +314,12 @@ abstract class AbstractPOIFSExtractor {
MediaType mediaType = getDetector().detect(tis, metadata, context);
String extension = type.getExtension();
try {
- MimeType mimeType =
- EmbeddedDocumentUtil.getMimeTypes(context).forName(mediaType.toString());
- extension = mimeType.getExtension();
+ // not forName: don't intern untrusted types into the registry (TIKA-4826)
+ MimeType mimeType = EmbeddedDocumentUtil.getMimeTypes(context)
+ .getRegisteredMimeType(mediaType.toString());
+ if (mimeType != null) {
+ extension = mimeType.getExtension();
+ }
} catch (MimeTypeException mte) {
// No details on this type are known
}