This is an automated email from the ASF dual-hosted git repository. tballison pushed a commit to branch TIKA-4808-extension in repository https://gitbox.apache.org/repos/asf/tika.git
commit df4b9af87057df8036c63b9bfc187d1a31d8cfe9 Author: tallison <[email protected]> AuthorDate: Fri Aug 7 17:09:07 2026 -0400 TIKA-4808 -- fix extension calculations for embedded files --- .../tika/extractor/EmbeddedDocumentUtil.java | 58 +++++----- .../EmbeddedDocumentUtilExtensionTest.java | 120 +++++++++++++++++++++ 2 files changed, 151 insertions(+), 27 deletions(-) diff --git a/tika-core/src/main/java/org/apache/tika/extractor/EmbeddedDocumentUtil.java b/tika-core/src/main/java/org/apache/tika/extractor/EmbeddedDocumentUtil.java index 324840ce84..f233d4e0b9 100644 --- a/tika-core/src/main/java/org/apache/tika/extractor/EmbeddedDocumentUtil.java +++ b/tika-core/src/main/java/org/apache/tika/extractor/EmbeddedDocumentUtil.java @@ -149,40 +149,24 @@ public class EmbeddedDocumentUtil implements Serializable { MimeTypes localMimeTypes = getMimeTypes(); MimeType mimeType = null; - boolean detected = false; if (mimeString != null) { - try { - mimeType = localMimeTypes.forName(mimeString); - } catch (MimeTypeException e) { - //swallow - } + mimeType = getRegisteredMimeType(localMimeTypes, mimeString); } if (mimeType == null) { try { MediaType mediaType = getDetector().detect(is, metadata, context); - mimeType = localMimeTypes.forName(mediaType.toString()); - detected = true; is.reset(); - } catch (IOException | MimeTypeException e) { + //set or correct the mime type. Record what was detected, not the + //registry match, which may have fallen back to the base type. + metadata.set(Metadata.CONTENT_TYPE, mediaType.toString()); + mimeType = getRegisteredMimeType(localMimeTypes, mediaType.toString()); + } catch (IOException e) { //swallow } } - if (mimeType != null) { - if (detected) { - //set or correct the mime type - metadata.set(Metadata.CONTENT_TYPE, mimeType.toString()); - } - return mimeType.getExtension(); - } - return ".bin"; + return mimeType == null ? ".bin" : mimeType.getExtension(); } - /** - * Looks up the file extension for a given media type string. - * - * @param mediaType the media type string (e.g., "image/png") - * @return the extension including the dot (e.g., ".png"), or empty string if unknown - */ /** * Normalizes internal OCR routing media types (e.g., {@code image/ocr-png}) * back to standard media types (e.g., {@code image/png}). @@ -198,16 +182,36 @@ public class EmbeddedDocumentUtil implements Serializable { return mediaType; } + /** + * Looks up the file extension for a given media type string. + * + * @param mediaType the media type string (e.g., "image/png"), parameters allowed + * @return the extension including the dot (e.g., ".png"), or empty string if unknown + */ public static String getExtensionForMediaType(String mediaType) { if (mediaType == null) { return ""; } - mediaType = normalizeMediaType(mediaType); + MimeType mimeType = + getRegisteredMimeType(MimeTypes.getDefaultMimeTypes(), + normalizeMediaType(mediaType)); + return mimeType == null ? "" : mimeType.getExtension(); + } + + /** + * Not {@link MimeTypes#forName(String)}: that registers a new, glob-less type for + * any name it doesn't recognize, so <code>text/plain; charset=UTF-8</code> would + * lose its extension and add a registry entry per charset seen. This prefers an + * exact parameterized match (<code>application/dita+xml;format=map</code> is real) + * and otherwise falls back to the base type. + * + * @return the registered type, or null if unknown or invalid + */ + private static MimeType getRegisteredMimeType(MimeTypes mimeTypes, String name) { try { - MimeType mimeType = MimeTypes.getDefaultMimeTypes().forName(mediaType); - return mimeType.getExtension(); + return mimeTypes.getRegisteredMimeType(name); } catch (MimeTypeException e) { - return ""; + return null; } } diff --git a/tika-core/src/test/java/org/apache/tika/extractor/EmbeddedDocumentUtilExtensionTest.java b/tika-core/src/test/java/org/apache/tika/extractor/EmbeddedDocumentUtilExtensionTest.java new file mode 100644 index 0000000000..4c9449e1ce --- /dev/null +++ b/tika-core/src/test/java/org/apache/tika/extractor/EmbeddedDocumentUtilExtensionTest.java @@ -0,0 +1,120 @@ +/* + * 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.extractor; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import org.apache.tika.mime.MediaType; +import org.apache.tika.mime.MimeTypes; + +/** + * TIKA-4808 -- a media type carrying parameters must still resolve to the + * extension of its base type. + */ +public class EmbeddedDocumentUtilExtensionTest { + + @Test + public void testParametersDoNotSuppressExtension() { + //the corpus regression: Pkcs7Parser refines the coarse family label to the + //exact smime-type, which turned /embedded-2.p7s into /embedded-2 + assertEquals(".p7m", + EmbeddedDocumentUtil.getExtensionForMediaType( + "application/pkcs7-mime; smime-type=signed-data")); + assertEquals(".txt", + EmbeddedDocumentUtil.getExtensionForMediaType("text/plain; charset=UTF-8")); + assertEquals(".js", + EmbeddedDocumentUtil.getExtensionForMediaType( + "text/javascript; charset=UTF-8")); + assertEquals(".css", + EmbeddedDocumentUtil.getExtensionForMediaType( + "text/css; charset=ISO-2022-JP")); + assertEquals(".html", + EmbeddedDocumentUtil.getExtensionForMediaType( + "text/html; charset=windows-1252")); + } + + @Test + public void testUnparameterizedStillWorks() { + assertEquals(".p7s", + EmbeddedDocumentUtil.getExtensionForMediaType("application/pkcs7-signature")); + assertEquals(".png", EmbeddedDocumentUtil.getExtensionForMediaType("image/png")); + assertEquals(".txt", EmbeddedDocumentUtil.getExtensionForMediaType("text/plain")); + } + + /** + * A registered type that genuinely has parameters must win over its base type. + */ + @Test + public void testRegisteredParameterizedTypeWinsOverBaseType() { + assertEquals(".ditamap", + EmbeddedDocumentUtil.getExtensionForMediaType( + "application/dita+xml;format=map")); + assertEquals(".dita", + EmbeddedDocumentUtil.getExtensionForMediaType( + "application/dita+xml;format=topic")); + } + + @Test + public void testUnknownAndNull() { + assertEquals("", EmbeddedDocumentUtil.getExtensionForMediaType(null)); + assertEquals("", + EmbeddedDocumentUtil.getExtensionForMediaType("application/tika-bogus-xyz")); + } + + @Test + public void testOcrRoutingTypeIsNormalized() { + assertEquals(".png", EmbeddedDocumentUtil.getExtensionForMediaType("image/ocr-png")); + } + + /** + * The lookup must not register anything: forName() would add one glob-less entry + * per distinct parameter value seen, which on a large crawl grows without bound. + */ + @Test + public void testLookupDoesNotPolluteRegistry() { + MimeTypes mimeTypes = MimeTypes.getDefaultMimeTypes(); + int before = mimeTypes.getMediaTypeRegistry().getTypes().size(); + for (int i = 0; i < 50; i++) { + EmbeddedDocumentUtil.getExtensionForMediaType("text/plain; charset=made-up-" + i); + } + assertEquals(before, mimeTypes.getMediaTypeRegistry().getTypes().size()); + assertEquals(".txt", + EmbeddedDocumentUtil.getExtensionForMediaType("text/plain; charset=made-up-0")); + } + + @Test + public void testGeneratedResourceNameKeepsExtension() { + assertEquals("embedded-2.p7m", EmbeddedDocumentUtil.generateResourceName( + EmbeddedDocumentUtil.EmbeddedResourcePrefix.EMBEDDED, 2, + "application/pkcs7-mime; smime-type=signed-data")); + assertEquals("image-0.png", EmbeddedDocumentUtil.generateResourceName( + EmbeddedDocumentUtil.EmbeddedResourcePrefix.IMAGE, 0, "image/png")); + } + + /** + * Guard the assumption the fix rests on: normalize() deliberately preserves + * parameters, which is why forName() misses the registry for parameterized names. + */ + @Test + public void testNormalizePreservesParameters() { + MediaType withParams = MediaType.parse("text/plain; charset=UTF-8"); + assertEquals(withParams, + MimeTypes.getDefaultMimeTypes().getMediaTypeRegistry().normalize(withParams)); + } +}
