Skip to content

Handle MIME type parameter names case-insensitively - #37192

Open
lArtiquel wants to merge 1 commit into
spring-projects:mainfrom
lArtiquel:mimetype-parameter-name-case
Open

Handle MIME type parameter names case-insensitively#37192
lArtiquel wants to merge 1 commit into
spring-projects:mainfrom
lArtiquel:mimetype-parameter-name-case

Conversation

@lArtiquel

@lArtiquel lArtiquel commented Aug 22, 2026

Copy link
Copy Markdown

MimeType keeps parameters in a LinkedCaseInsensitiveMap, so equals() matches names case-insensitively. parametersHashCode() hashes the raw key, so it doesn't — which breaks the equals/hashCode contract:

MimeType a = MimeType.valueOf("text/plain;FOO=bar");
MimeType b = MimeType.valueOf("text/plain;foo=bar");

a.equals(b);                            // true
a.hashCode() == b.hashCode();           // false
new HashSet<>(List.of(a)).contains(b);  // false

The charset branch in equals, hashCode and compareTo keys off PARAM_CHARSET.equals(key), so a parameter spelled Charset skips it and is compared as an opaque string, even though the constructor already resolved it through the same case-insensitive map:

MimeType.valueOf("text/plain;charset=UTF-8").equals(MimeType.valueOf("text/plain;charset=utf-8")); // true
MimeType.valueOf("text/plain;Charset=UTF-8").equals(MimeType.valueOf("text/plain;Charset=utf-8")); // false

MediaType has the same assumption in two more places: an out-of-range quality value escapes validation when spelled Q=1.1, and removeQualityValue() leaves a Q= parameter in place.

Fix is six lines — equalsIgnoreCase in the four comparisons, and a normalized key before hashing.

Two behavior changes, both bringing the uppercase spelling in line with the lowercase one: Charset=UTF-8 now equals Charset=utf-8, and Q=1.1 is now rejected at parse time. Four tests added, each failing before the change; spring-core, -web, -webmvc, -webflux, -messaging and -test suites pass.

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>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Aug 22, 2026
@bclozel

bclozel commented Aug 23, 2026

Copy link
Copy Markdown
Member

Duplicates #37008?

@lArtiquel

Copy link
Copy Markdown
Author

No. #37008 rejects duplicate params within one header string; this is equals/hashCode across two instances. I applied it on main and the case is unchanged:

MimeType a = MimeType.valueOf("text/plain;FOO=bar");
MimeType b = MimeType.valueOf("text/plain;foo=bar");
a.equals(b);                   // true
a.hashCode() == b.hashCode();  // false

parametersAreEqual resolves the key through the LinkedCaseInsensitiveMap, parametersHashCode hashes it verbatim.

They only overlap in MimeTypeTests, so one needs a rebase. Can split the MediaType part out if you'd rather.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged or decided on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants