Handle MIME type parameter names case-insensitively

MIME type parameter names are case-insensitive, and MimeType already
stores them in a LinkedCaseInsensitiveMap. Several code paths, however,
still compared them with case-sensitive String.equals().

As a result, MimeType.hashCode() disagreed with MimeType.equals() for
parameter names that differ only in case, breaking the equals/hashCode
contract: text/plain;FOO=bar and text/plain;foo=bar are equal but hash
differently, so one is not found in a hash-based collection holding the
other. MimeType.compareTo() had the same blind spot for the charset
parameter.

MediaType was affected in two further ways: an out-of-range quality
value escaped validation when spelled Q=, and removeQualityValue() left
a Q= parameter in place.

Signed-off-by: Artyom Tsvirko <36863599+lArtiquel@users.noreply.github.com>
This commit is contained in:
Artyom Tsvirko
2026-08-31 17:30:39 +02:00
committed by Brian Clozel
parent ee19b96ee9
commit 2028eb3694
4 changed files with 51 additions and 11 deletions
@@ -503,7 +503,7 @@ public class MediaType extends MimeType implements Serializable {
@Override
protected void checkParameters(String parameter, String value) {
super.checkParameters(parameter, value);
if (PARAM_QUALITY_FACTOR.equals(parameter)) {
if (PARAM_QUALITY_FACTOR.equalsIgnoreCase(parameter)) {
String unquotedValue = unquote(value);
double d = Double.parseDouble(unquotedValue);
Assert.isTrue(d >= 0D && d <= 1D,
@@ -651,7 +651,7 @@ public class MediaType extends MimeType implements Serializable {
return this;
}
Map<String, String> params = new LinkedHashMap<>(getParameters());
params.remove(PARAM_QUALITY_FACTOR);
params.keySet().removeIf(PARAM_QUALITY_FACTOR::equalsIgnoreCase);
return new MediaType(this, params);
}
@@ -135,6 +135,12 @@ class MediaTypeTests {
MediaType.parseMediaType("audio/basic;q=1.1"));
}
@Test
void parseMediaTypeIllegalQualityFactorWithUpperCaseParameterName() {
assertThatExceptionOfType(InvalidMediaTypeException.class).isThrownBy(() ->
MediaType.parseMediaType("audio/basic;Q=1.1"));
}
@Test
void parseMediaTypeIllegalCharset() {
assertThatExceptionOfType(InvalidMediaTypeException.class).isThrownBy(() ->
@@ -293,6 +299,18 @@ class MediaTypeTests {
assertThat(new MediaType("text", "*").isConcrete()).as("text/* concrete").isFalse();
}
@Test
void removeQualityValue() {
assertThat(MediaType.parseMediaType("audio/basic;q=0.8").removeQualityValue())
.isEqualTo(MediaType.parseMediaType("audio/basic"));
}
@Test
void removeQualityValueWithUpperCaseParameterName() {
assertThat(MediaType.parseMediaType("audio/basic;Q=0.8").removeQualityValue())
.isEqualTo(MediaType.parseMediaType("audio/basic"));
}
@Test // gh-26127
void serialize() throws Exception {
MediaType original = new MediaType("text", "plain", StandardCharsets.UTF_8);