From 26914893810619091315cfecde03bc86c7548ff1 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 20 Feb 2026 18:21:45 +0100 Subject: [PATCH] Fix InvalidMimeTypeException for compatible media types The `AbstractMessageConverterMethodProcessor` is in charge of handling controller method return values and to write those as HTTP response messages. The content negotiation process is an important part. The `MimeTypeUtils#sortBySpecificity` is in charge of sorting inbound "Accept" media types by their specificity and reject them if the list is too large, in order to protect the application from ddos attacks. Prior to this commit, the content negotiation process would first get the sorted "Accept" media types, the producible media types as advertized by message converters - and collect the intersection of both in a new list (also sorted by specificity). If the "Accept" list is large enough (but under the limit), the list of compatible media types could exceed that limit because duplicates could be introduced in that list: several converters can produce the same content type. This commit ensures that compatible media types are collected in a set to avoid duplicates. Without that, exceeding the limit at this point will throw an `InvalidMimeTypeException` that's not handled by the processor and result in a server error. Fixes gh-36300 --- ...stractMessageConverterMethodProcessor.java | 13 ++++++------ ...questResponseBodyMethodProcessorTests.java | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java index d0e17b20f4d..4e8498df97f 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java @@ -272,12 +272,11 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe "No converter found for return value of type: " + valueType); } - List compatibleMediaTypes = new ArrayList<>(); - determineCompatibleMediaTypes(acceptableTypes, producibleTypes, compatibleMediaTypes); + List compatibleMediaTypes = determineCompatibleMediaTypes(acceptableTypes, producibleTypes); // For ProblemDetail, fall back on RFC 9457 format if (compatibleMediaTypes.isEmpty() && ProblemDetail.class.isAssignableFrom(valueType)) { - determineCompatibleMediaTypes(PROBLEM_MEDIA_TYPES, producibleTypes, compatibleMediaTypes); + compatibleMediaTypes = determineCompatibleMediaTypes(PROBLEM_MEDIA_TYPES, producibleTypes); } if (compatibleMediaTypes.isEmpty()) { @@ -452,16 +451,18 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request)); } - private void determineCompatibleMediaTypes( - List acceptableTypes, List producibleTypes, List mediaTypesToUse) { + private List determineCompatibleMediaTypes( + List acceptableTypes, List producibleTypes) { + Set compatibleTypes = new LinkedHashSet<>(); for (MediaType requestedType : acceptableTypes) { for (MediaType producibleType : producibleTypes) { if (requestedType.isCompatibleWith(producibleType)) { - mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType)); + compatibleTypes.add(getMostSpecificMediaType(requestedType, producibleType)); } } } + return new ArrayList<>(compatibleTypes); } /** diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java index 81bdb288d2a..0b321d5a6aa 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java @@ -24,6 +24,8 @@ import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; @@ -1022,6 +1024,24 @@ class RequestResponseBodyMethodProcessorTests { assertThat(value).isEqualTo("foo"); } + @Test // gh-36300 + void shouldNotDuplicateInCompatibleMediaTypes() throws Exception { + Method method = TestRestController.class.getMethod("handle"); + MethodParameter returnType = new MethodParameter(method, -1); + + List> converters = List.of(new StringHttpMessageConverter(), new JacksonJsonHttpMessageConverter()); + RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters); + + String accept = Stream.iterate(1, i -> i + 1) + .limit(48).map(i -> "application/" + i) + .collect(Collectors.joining(",")); + accept = accept + ", application/json"; + this.servletRequest.addHeader("Accept", accept); + + processor.writeWithMessageConverters("spring framework", returnType, this.request); + } + + private void assertContentDisposition(RequestResponseBodyMethodProcessor processor, boolean expectContentDisposition, String requestURI, String comment) throws Exception {