Skip to content

Commit a2979eb

Browse files
committed
Replace jakarta.activation with an internal MIME-type parser
1 parent 170b047 commit a2979eb

4 files changed

Lines changed: 145 additions & 21 deletions

File tree

client/src/main/java/org/asynchttpclient/request/body/multipart/FileLikePart.java

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@
1515
*/
1616
package org.asynchttpclient.request.body.multipart;
1717

18-
import jakarta.activation.MimetypesFileTypeMap;
19-
20-
import java.io.IOException;
21-
import java.io.InputStream;
2218
import java.nio.charset.Charset;
2319

2420
import static org.asynchttpclient.util.MiscUtils.withDefault;
@@ -28,16 +24,6 @@
2824
*/
2925
public abstract class FileLikePart extends PartBase {
3026

31-
private static final MimetypesFileTypeMap MIME_TYPES_FILE_TYPE_MAP;
32-
33-
static {
34-
try (InputStream is = FileLikePart.class.getResourceAsStream("ahc-mime.types")) {
35-
MIME_TYPES_FILE_TYPE_MAP = new MimetypesFileTypeMap(is);
36-
} catch (IOException e) {
37-
throw new ExceptionInInitializerError(e);
38-
}
39-
}
40-
4127
/**
4228
* Default content encoding of file attachments.
4329
*/
@@ -63,7 +49,7 @@ protected FileLikePart(String name, String contentType, Charset charset, String
6349
}
6450

6551
private static String computeContentType(String contentType, String fileName) {
66-
return contentType != null ? contentType : MIME_TYPES_FILE_TYPE_MAP.getContentType(withDefault(fileName, ""));
52+
return contentType != null ? contentType : MimeTypes.getContentType(withDefault(fileName, ""));
6753
}
6854

6955
public String getFileName() {
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright (c) 2024-2026 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.asynchttpclient.request.body.multipart;
17+
18+
import java.io.BufferedReader;
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.io.InputStreamReader;
22+
import java.nio.charset.StandardCharsets;
23+
import java.util.Collections;
24+
import java.util.HashMap;
25+
import java.util.Locale;
26+
import java.util.Map;
27+
28+
/**
29+
* Maps file extensions to content types using the bundled {@code ahc-mime.types} resource.
30+
*
31+
* <p>This is a self-contained replacement for {@code jakarta.activation.MimetypesFileTypeMap}, which was
32+
* previously used solely to resolve the content type of {@link FileLikePart}s. The lookup mirrors the
33+
* {@code MimetypesFileTypeMap} contract: the extension is the substring after the last {@code '.'}, and an
34+
* unknown or missing extension resolves to {@value #DEFAULT_CONTENT_TYPE}. Unlike the original, the lookup is
35+
* case-insensitive and relies exclusively on the bundled resource, so detection is deterministic across
36+
* machines and classpaths.
37+
*/
38+
final class MimeTypes {
39+
40+
static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
41+
42+
/**
43+
* Lower-cased file extension to content type. Built once at class load from {@code ahc-mime.types}.
44+
*/
45+
private static final Map<String, String> EXTENSION_TO_CONTENT_TYPE;
46+
47+
static {
48+
Map<String, String> map = new HashMap<>();
49+
// The MimetypesFileTypeMap format: '#' comments, blank lines, and whitespace-delimited
50+
// "type ext1 ext2 ..." entries. A later mapping for the same extension wins, matching the
51+
// original Hashtable-based implementation.
52+
try (InputStream is = MimeTypes.class.getResourceAsStream("ahc-mime.types");
53+
BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.ISO_8859_1))) {
54+
String line;
55+
while ((line = reader.readLine()) != null) {
56+
line = line.trim();
57+
if (line.isEmpty() || line.charAt(0) == '#') {
58+
continue;
59+
}
60+
String[] tokens = line.split("\\s+");
61+
for (int i = 1; i < tokens.length; i++) {
62+
map.put(tokens[i].toLowerCase(Locale.ROOT), tokens[0]);
63+
}
64+
}
65+
} catch (IOException e) {
66+
throw new ExceptionInInitializerError(e);
67+
}
68+
EXTENSION_TO_CONTENT_TYPE = Collections.unmodifiableMap(map);
69+
}
70+
71+
private MimeTypes() {
72+
}
73+
74+
/**
75+
* Resolves the content type for the given file name based on its extension.
76+
*
77+
* @param fileName the file name (may include a path; only the part after the last {@code '.'} is used)
78+
* @return the mapped content type, or {@value #DEFAULT_CONTENT_TYPE} if the extension is absent or unknown
79+
*/
80+
static String getContentType(String fileName) {
81+
int dot = fileName.lastIndexOf('.');
82+
if (dot < 0) {
83+
return DEFAULT_CONTENT_TYPE;
84+
}
85+
String extension = fileName.substring(dot + 1);
86+
if (extension.isEmpty()) {
87+
return DEFAULT_CONTENT_TYPE;
88+
}
89+
return EXTENSION_TO_CONTENT_TYPE.getOrDefault(extension.toLowerCase(Locale.ROOT), DEFAULT_CONTENT_TYPE);
90+
}
91+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright (c) 2024-2026 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.asynchttpclient.request.body.multipart;
17+
18+
import org.junit.jupiter.api.Test;
19+
20+
import static org.junit.jupiter.api.Assertions.assertEquals;
21+
22+
class MimeTypesTest {
23+
24+
@Test
25+
void resolvesKnownExtensions() {
26+
assertEquals("image/png", MimeTypes.getContentType("image.png"));
27+
assertEquals("text/html", MimeTypes.getContentType("data.html"));
28+
assertEquals("image/jpeg", MimeTypes.getContentType("photo.jpeg"));
29+
}
30+
31+
@Test
32+
void lookupIsCaseInsensitive() {
33+
assertEquals("image/png", MimeTypes.getContentType("IMAGE.PNG"));
34+
assertEquals("text/html", MimeTypes.getContentType("Data.Html"));
35+
}
36+
37+
@Test
38+
void usesExtensionAfterLastDot() {
39+
assertEquals("image/png", MimeTypes.getContentType("my.archive.png"));
40+
}
41+
42+
@Test
43+
void unknownExtensionFallsBackToDefault() {
44+
assertEquals(MimeTypes.DEFAULT_CONTENT_TYPE, MimeTypes.getContentType("file.zzz"));
45+
}
46+
47+
@Test
48+
void missingExtensionFallsBackToDefault() {
49+
assertEquals(MimeTypes.DEFAULT_CONTENT_TYPE, MimeTypes.getContentType("noextension"));
50+
assertEquals(MimeTypes.DEFAULT_CONTENT_TYPE, MimeTypes.getContentType("trailingdot."));
51+
assertEquals(MimeTypes.DEFAULT_CONTENT_TYPE, MimeTypes.getContentType(""));
52+
}
53+
}

pom.xml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
<brotli4j.version>1.23.0</brotli4j.version>
5050
<slf4j.version>2.0.18</slf4j.version>
5151
<zstd-jni.version>1.5.7-8</zstd-jni.version>
52-
<activation.version>2.0.1</activation.version>
5352
<logback.version>1.5.32</logback.version>
5453
<jetbrains-annotations.version>26.1.0</jetbrains-annotations.version>
5554
<testcontainers.version>2.0.5</testcontainers.version>
@@ -284,11 +283,6 @@
284283
<version>${slf4j.version}</version>
285284
</dependency>
286285

287-
<dependency>
288-
<groupId>com.sun.activation</groupId>
289-
<artifactId>jakarta.activation</artifactId>
290-
<version>${activation.version}</version>
291-
</dependency>
292286
<dependency>
293287
<groupId>org.jetbrains</groupId>
294288
<artifactId>annotations</artifactId>

0 commit comments

Comments
 (0)