Skip to content

Reject MIME type parameters differing only in case - #37008

Open
junhyeong9812 wants to merge 1 commit into
spring-projects:mainfrom
junhyeong9812:fix/mimetype-duplicate-parameter-case
Open

Reject MIME type parameters differing only in case#37008
junhyeong9812 wants to merge 1 commit into
spring-projects:mainfrom
junhyeong9812:fix/mimetype-duplicate-parameter-case

Conversation

@junhyeong9812

@junhyeong9812 junhyeong9812 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Overview

MimeTypeUtils rejects duplicate MIME type parameters (gh-36841), but the check is case-sensitive while MIME parameter names are case-insensitive. As a result, duplicates that differ only in case (for example charset and CHARSET) slip through and are silently collapsed to the last value. This aligns the duplicate check with the case-insensitive nature of parameter names.

Since this PR was first opened, the parser was rewritten as a state machine (MimeTypeParser, commit 0799920); this PR has been reworked against the new parser accordingly.

Problem

When parsing parameters, MimeTypeParser.putParameter(...) accumulates them in a LinkedHashMap and rejects duplicates by throwing when Map#put returns a previous value:

private void putParameter(String name, String value) {
    if (this.parameters == null) {
        this.parameters = new LinkedHashMap<>(4);
    }
    if (this.parameters.put(name, value) != null) {
        throw new InvalidMimeTypeException(this.input, "duplicate parameter '" + name + "=" + value + "'");
    }
}

Because a LinkedHashMap treats charset and CHARSET as distinct keys, a case-variant duplicate never triggers the check:

Input Before
text/plain;dupe="1";dupe="2" rejected (InvalidMimeTypeException)
text/plain;dupe="1";DUPE="2" accepted, silently keeps "2"

This is inconsistent: MIME parameter names are case-insensitive (RFC 2045), and MimeType itself stores parameters in a LinkedCaseInsensitiveMap so that getParameter("CHARSET") and getParameter("charset") resolve to the same value. RFC 6838 section 4.3 (cited by gh-36841) treats duplicate parameters as an error, so a case-only variant is logically the same duplicate.

Fix

Accumulate parameters in a LinkedCaseInsensitiveMap so that a case-variant duplicate maps to the same key, causing put to return the previous value and the existing check to reject it:

// Parameter names are case-insensitive, so use a case-insensitive
// map in order to reject duplicates that differ only in case.
this.parameters = new LinkedCaseInsensitiveMap<>(4, Locale.ROOT);

Locale.ROOT matches how MimeType builds its own parameter map, keeping case-insensitive comparison locale-independent. The final MimeType is unaffected: its constructor re-copies the parameters into its own LinkedCaseInsensitiveMap, and for non-duplicate input the accumulating map behaves the same as before (same keys, values, and iteration order). Only the duplicate-detection sensitivity changes.

Note on impact

This is a parsing behavior change: Content-Type/Accept-style strings that repeat a parameter with different casing now throw InvalidMimeTypeException instead of parsing successfully. This matches the intent of gh-36841 and the case-insensitive contract of parameter names; valid MIME types are unaffected.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jul 6, 2026
@sbrannen sbrannen added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Jul 7, 2026
@sbrannen
sbrannen requested a review from bclozel July 7, 2026 09:21
@yashsiwacha

Copy link
Copy Markdown

Hi @junhyeong9812!

Since this PR was opened, the MIME type parser was rewritten as a state machine parser (MimeTypeParser) in commit 079992021cf9985050d761792677020046c31109 ("Improve MimeType parser for RFC compliance").

As a result, your changes to the old parser method will conflict. To resolve this and make the check case-insensitive, the accumulator map instantiation in MimeTypeParser.putParameter should be updated:

private void putParameter(String name, String value) {
    if (this.parameters == null) {
        this.parameters = new LinkedCaseInsensitiveMap<>(4, Locale.ROOT);
    }
    if (this.parameters.put(name, value) != null) {
        throw new InvalidMimeTypeException(this.input, "duplicate parameter '" + name + "=" + value + "'");
    }
}

MIME type parameter names are case-insensitive, but MimeTypeParser
accumulates parameters in a case-sensitive LinkedHashMap. As a result,
duplicate parameters differing only in case (such as "charset" and
"CHARSET") were not rejected and were silently collapsed to the last
value by the case-insensitive parameter map of MimeType.

Accumulate parameters in a LinkedCaseInsensitiveMap so that duplicates
differing only in case map to the same key and are rejected consistently
with exact duplicates.

Signed-off-by: junhyeong9812 <pickjog@gmail.com>
Co-authored-by: Yash <190389954+yashsiwacha@users.noreply.github.com>
@junhyeong9812
junhyeong9812 force-pushed the fix/mimetype-duplicate-parameter-case branch from 0c01e41 to 019c822 Compare August 13, 2026 06:29
@junhyeong9812

Copy link
Copy Markdown
Contributor Author

Thanks for the heads-up @yashsiwacha! I've rebased the branch onto the latest main and reworked the fix against the new state machine parser as you suggested: the accumulator map in MimeTypeParser.putParameter is now a LinkedCaseInsensitiveMap, so duplicates that differ only in case are rejected by the existing duplicate check. I've also credited you as a co-author on the commit.

@bclozel bclozel self-assigned this Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in: web Issues in web modules (web, webmvc, webflux, websocket) 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.

5 participants