CVE-2022-21700 (published 2022-01-18T23:15Z)

Micronaut is a JVM-based, full stack Java framework designed for building JVM web applications with support for Java, Kotlin and the Groovy language. In affected versions sending an invalid Content Type header leads to memory leak in DefaultArgumentConversionContext as this type is erroneously used in static state. ### Impact Sending an invalid Content Type header leads to memory leak in `DefaultArgumentConversionContext` as this type is erroneously used in static state. ### Patches The problem is patched in Micronaut 3.2.7 and above. ### Workarounds The default content type binder can be replaced in an existing Micronaut application to mitigate the issue: ```java package example; import java.util.List; import io.micronaut.context.annotation.Replaces; import io.micronaut.core.convert.ConversionService; import io.micronaut.http.MediaType; import io.micronaut.http.bind.DefaultRequestBinderRegistry; import io.micronaut.http.bind.binders.RequestArgumentBinder; import jakarta.inject.Singleton; @Singleton @Replaces(DefaultRequestBinderRegistry.class) class FixedRequestBinderRegistry extends DefaultRequestBinderRegistry { public FixedRequestBinderRegistry(ConversionService conversionService, List<RequestArgumentBinder> binders) { super(conversionService, binders); } @Override protected void registerDefaultConverters(ConversionService<?> conversionService) { super.registerDefaultConverters(conversionService); conversionService.addConverter(CharSequence.class, MediaType.class, charSequence -> { try { return MediaType.of(charSequence); } catch (IllegalArgumentException e) { return null; } }); } } ``` ### References Commit that introduced the vulnerability https://github.com/micronaut-projects/micronaut-core/commit/b8ec32c311689667c69ae7d9f9c3b3a8abc96fe3 ### For more information If you have any questions or comments about this advisory: * Open an issue in [Micronaut Core](https://github.com/micronaut-projects/micronaut-core/issues) * Email us at [info@micronaut.io](mailto:info@micronaut.io)

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:N/I:N/A:P)
CVSS v3 base score: 5.3, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L)

Weakness information:
cwe_id: CWE-400
cwe_name: Uncontrolled Resource Consumption
description: The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
extended_description: 
            Limited resources include memory, file system storage, database connection pool entries, and CPU. If an attacker can trigger the allocation of these limited resources, but the number or size of the resources is not controlled, then the attacker could cause a denial of service that consumes all available resources. This would prevent valid users from accessing the product, and it could potentially have an impact on the surrounding environment. For example, a memory exhaustion attack against an application could slow down the application as well as its host operating system.
            There are at least three distinct scenarios which can commonly lead to resource exhaustion:
               
                  Lack of throttling for the number of allocated resources
                  Losing all references to a resource before reaching the shutdown stage
                  Not closing/returning a resource after processing
               
            Resource exhaustion problems are often result due to an incorrect implementation of the following situations:
               
                  Error conditions and other exceptional circumstances.
                  Confusion over which part of the program is responsible for releasing the resource.
               
         
         

Fix information:
repo_url: https://github.com/micronaut-projects/micronaut-core
commit_details: repo_url: https://github.com/micronaut-projects/micronaut-core
author: Fabien Renaud
author_date: 2019-11-17 23:23:08-08:00
msg: Use ConversionContext constants where possible instead of class (#2356)

Changes
-------
 * Added ArgumentConversionContext constants in ConversionContext
 * Replaced Argument.of and use of argument classes with
ConversionContext constants where possible
 * Added getFirst method in ConvertibleMultiValues that accepts
ArgumentConversionContent parameter

Partially addresses issue #2355
repository: repo_url: https://github.com/micronaut-projects/micronaut-core
repo_name: micronaut-projects/micronaut-core
description: Micronaut Application Framework
repo_language: Java
owner: micronaut-projects
file_changes: filename: ConversionContext.java
change_type: ModificationType.MODIFY
diff: @@ -41,6 +41,31 @@ public interface ConversionContext extends AnnotationMetadataProvider, TypeVaria
     ConversionContext DEFAULT = new ConversionContext() {
     };
 
+    /**
+     * Constant for Boolean argument.
+     */
+    ArgumentConversionContext<Boolean> BOOLEAN = ConversionContext.of(Argument.BOOLEAN);
+
+    /**
+     * Constant for Integer argument.
+     */
+    ArgumentConversionContext<Integer> INT = ConversionContext.of(Argument.INT);
+
+    /**
+     * Constant for Long argument.
+     */
+    ArgumentConversionContext<Long> LONG = ConversionContext.of(Argument.LONG);
+
+    /**
+     * Constant for String argument.
+     */
+    ArgumentConversionContext<String> STRING = ConversionContext.of(Argument.STRING);
+
+    /**
+     * Constant for List<String> argument.
+     */
+    ArgumentConversionContext<List<String>> LIST_OF_STRING = ConversionContext.of(Argument.LIST_OF_STRING);
+
     /**
      * In the case where the type to be converted contains generic type arguments this map will return
      * the concrete types of those arguments. For example for the {@link Map} type two keys will be present

programming_language: Java
filename: DefaultArgumentConversionContext.java
change_type: ModificationType.MODIFY
diff: @@ -19,13 +19,7 @@ import io.micronaut.core.annotation.Internal;
 import io.micronaut.core.type.Argument;
 
 import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
 
 /**
  * Default implementation of the {@link ConversionContext} interface.
@@ -114,6 +108,25 @@ class DefaultArgumentConversionContext<T> implements ArgumentConversionContext<T
         return argument;
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        DefaultArgumentConversionContext<?> that = (DefaultArgumentConversionContext<?>) o;
+        return Objects.equals(getArgument(), that.getArgument()) &&
+            Objects.equals(finalLocale, that.finalLocale) &&
+            Objects.equals(finalCharset, that.finalCharset);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(argument, finalLocale, finalCharset);
+    }
+
     @Override
     public String toString() {
         return argument.toString();

programming_language: Java
filename: ConvertibleMultiValues.java
change_type: ModificationType.MODIFY
diff: @@ -15,6 +15,7 @@
  */
 package io.micronaut.core.convert.value;
 
+import io.micronaut.core.convert.ArgumentConversionContext;
 import io.micronaut.core.convert.ConversionContext;
 import io.micronaut.core.convert.ConversionService;
 import io.micronaut.core.reflect.GenericTypeUtils;
@@ -170,6 +171,22 @@ public interface ConvertibleMultiValues<V> extends ConvertibleValues<List<V>> {
         return Optional.empty();
     }
 
+    /**
+     * Find a header and convert it to the given type.
+     *
+     * @param name              The name of the header
+     * @param conversionContext The conversion context
+     * @param <T>               The generic type
+     * @return If the header is presented and can be converted an optional of the value otherwise {@link Optional#empty()}
+     */
+    default <T> Optional<T> getFirst(CharSequence name, ArgumentConversionContext<T> conversionContext) {
+        V v = get(name);
+        if (v != null) {
+            return ConversionService.SHARED.convert(v, conversionContext);
+        }
+        return Optional.empty();
+    }
+
     /**
      * Find a header and convert it to the given type.
      *

programming_language: Java
filename: ConvertibleValues.java
change_type: ModificationType.MODIFY
diff: @@ -70,7 +70,7 @@ public interface ConvertibleValues<V> extends ValueResolver<CharSequence>, Itera
      * @return True if it is
      */
     default boolean contains(String name) {
-        return get(name, Object.class).isPresent();
+        return get(name, Argument.OBJECT_ARGUMENT).isPresent();
     }
 
     /**

programming_language: Java
filename: Argument.java
change_type: ModificationType.MODIFY
diff: @@ -40,59 +40,64 @@ import java.util.stream.Collectors;
  */
 public interface Argument<T> extends TypeVariableResolver, AnnotatedElement, Type {
 
+    /**
+     * Constant for string argument.
+     */
+    Argument<String> STRING = Argument.of(String.class);
+
     /**
      * Constant for int argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument INT = Argument.of(int.class);
+    Argument<Integer> INT = Argument.of(int.class);
 
     /**
      * Constant for long argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument LONG = Argument.of(long.class);
+    Argument<Long> LONG = Argument.of(long.class);
 
     /**
      * Constant for float argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument FLOAT = Argument.of(float.class);
+    Argument<Float> FLOAT = Argument.of(float.class);
 
     /**
      * Constant for double argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument DOUBLE = Argument.of(double.class);
+    Argument<Double> DOUBLE = Argument.of(double.class);
 
     /**
      * Constant for void argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument VOID = Argument.of(void.class);
+    Argument<Void> VOID = Argument.of(void.class);
 
     /**
      * Constant for byte argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument BYTE = Argument.of(byte.class);
+    Argument<Byte> BYTE = Argument.of(byte.class);
 
     /**
      * Constant for boolean argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument BOOLEAN = Argument.of(boolean.class);
+    Argument<Boolean> BOOLEAN = Argument.of(boolean.class);
 
     /**
      * Constant char argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument CHAR = Argument.of(char.class);
+    Argument<Character> CHAR = Argument.of(char.class);
 
     /**
      * Constant short argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument SHORT = Argument.of(short.class);
+    Argument<Short> SHORT = Argument.of(short.class);
 
     /**
      * Constant representing zero arguments. Used by generated code, do not remove.
@@ -107,6 +112,11 @@ public interface Argument<T> extends TypeVariableResolver, AnnotatedElement, Typ
     @SuppressWarnings("unused")
     Argument<Object> OBJECT_ARGUMENT = of(Object.class);
 
+    /**
+     * Constant for List<String> argument.
+     */
+    Argument<List<String>> LIST_OF_STRING = Argument.listOf(String.class);
+
     /**
      * @return The name of the argument
      */

programming_language: Java
filename: DefaultArgument.java
change_type: ModificationType.MODIFY
diff: @@ -47,11 +47,7 @@ public class DefaultArgument<T> implements Argument<T> {
      * @param genericTypes       The generic types
      */
     public DefaultArgument(Class<T> type, String name, AnnotationMetadata annotationMetadata, Argument... genericTypes) {
-        this.type = type;
-        this.name = name;
-        this.annotationMetadata = annotationMetadata != null ? annotationMetadata : AnnotationMetadata.EMPTY_METADATA;
-        this.typeParameters = initializeTypeParameters(genericTypes);
-        this.typeParameterArray = genericTypes;
+        this(type, name, annotationMetadata, initializeTypeParameters(genericTypes), genericTypes);
     }
 
     /**
@@ -184,7 +180,7 @@ public class DefaultArgument<T> implements Argument<T> {
         return Objects.hash(type, name, typeParameters);
     }
 
-    private Map<String, Argument<?>> initializeTypeParameters(Argument[] genericTypes) {
+    private static Map<String, Argument<?>> initializeTypeParameters(Argument[] genericTypes) {
         Map<String, Argument<?>> typeParameters;
         if (genericTypes != null && genericTypes.length > 0) {
             typeParameters = new LinkedHashMap<>(genericTypes.length);

programming_language: Java
filename: HttpClientErrorDecoder.java
change_type: ModificationType.MODIFY
diff: @@ -71,7 +71,7 @@ public interface HttpClientErrorDecoder {
         } else if (mediaType.equals(MediaType.APPLICATION_VND_ERROR_TYPE)) {
             return Argument.of(VndError.class);
         } else {
-            return Argument.of(String.class);
+            return Argument.STRING;
         }
     }
 }

programming_language: Java
filename: RoutingInBoundHandler.java
change_type: ModificationType.MODIFY
diff: @@ -141,6 +141,7 @@ class RoutingInBoundHandler extends SimpleChannelInboundHandler<io.micronaut.htt
     private static final Logger LOG = LoggerFactory.getLogger(RoutingInBoundHandler.class);
     private static final Pattern IGNORABLE_ERROR_MESSAGE = Pattern.compile(
             "^.*(?:connection.*(?:reset|closed|abort|broken)|broken.*pipe).*$", Pattern.CASE_INSENSITIVE);
+    private static final Argument ARGUMENT_PART_DATA = Argument.of(PartData.class);
 
     private final Router router;
     private final ExecutorSelector executorSelector;
@@ -769,7 +770,7 @@ class RoutingInBoundHandler extends SimpleChannelInboundHandler<io.micronaut.htt
                                 Argument typeVariable;
 
                                 if (StreamingFileUpload.class.isAssignableFrom(argument.getType())) {
-                                    typeVariable = Argument.of(PartData.class);
+                                    typeVariable = ARGUMENT_PART_DATA;
                                 } else {
                                     typeVariable = argument.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
                                 }
@@ -784,7 +785,7 @@ class RoutingInBoundHandler extends SimpleChannelInboundHandler<io.micronaut.htt
                                 if (Publishers.isConvertibleToPublisher(typeVariableType)) {
                                     boolean streamingFileUpload = StreamingFileUpload.class.isAssignableFrom(typeVariableType);
                                     if (streamingFileUpload) {
-                                        typeVariable = Argument.of(PartData.class);
+                                        typeVariable = ARGUMENT_PART_DATA;
                                     } else {
                                         typeVariable = typeVariable.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
                                     }

programming_language: Java
filename: CompletableFutureBodyBinder.java
change_type: ModificationType.MODIFY
diff: @@ -49,6 +49,8 @@ import java.util.concurrent.CompletableFuture;
 public class CompletableFutureBodyBinder extends DefaultBodyAnnotationBinder<CompletableFuture>
     implements NonBlockingBodyArgumentBinder<CompletableFuture> {
 
+    private static final Argument<CompletableFuture> TYPE = Argument.of(CompletableFuture.class);
+
     private final BeanLocator beanLocator;
     private final HttpServerConfiguration httpServerConfiguration;
 
@@ -65,7 +67,7 @@ public class CompletableFutureBodyBinder extends DefaultBodyAnnotationBinder<Com
 
     @Override
     public Argument<CompletableFuture> argumentType() {
-        return Argument.of(CompletableFuture.class);
+        return TYPE;
     }
 
     @Override

programming_language: Java
filename: PublisherBodyBinder.java
change_type: ModificationType.MODIFY
diff: @@ -54,6 +54,7 @@ import java.util.Optional;
 public class PublisherBodyBinder extends DefaultBodyAnnotationBinder<Publisher> implements NonBlockingBodyArgumentBinder<Publisher> {
 
     private static final Logger LOG = LoggerFactory.getLogger(NettyHttpServer.class);
+    private static final Argument<Publisher> TYPE = Argument.of(Publisher.class);
 
     private final BeanLocator beanLocator;
     private final HttpServerConfiguration httpServerConfiguration;
@@ -71,7 +72,7 @@ public class PublisherBodyBinder extends DefaultBodyAnnotationBinder<Publisher>
 
     @Override
     public Argument<Publisher> argumentType() {
-        return Argument.of(Publisher.class);
+        return TYPE;
     }
 
     @Override

programming_language: Java
filename: CorsFilterSpec.groovy
change_type: ModificationType.MODIFY
diff: @@ -15,6 +15,7 @@
  */
 package io.micronaut.http.server.netty.cors
 
+import io.micronaut.core.convert.ConversionContext
 import io.micronaut.core.type.Argument
 import io.micronaut.http.HttpHeaders
 import io.micronaut.http.HttpMethod
@@ -146,7 +147,7 @@ class CorsFilterSpec extends Specification {
         2 * headers.getOrigin() >> Optional.of('http://www.foo.com')
         1 * request.getMethod() >> HttpMethod.GET
         !result.isPresent()
-        0 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List,String))
+        0 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.of(Argument.of(List,String)))
     }
 
     void "test preflight handleRequest with disallowed header"() {
@@ -170,8 +171,8 @@ class CorsFilterSpec extends Specification {
 
         then: "the request is rejected because bar is not allowed"
         2 * headers.getOrigin() >> Optional.of('http://www.foo.com')
-        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class) >> Optional.of(HttpMethod.GET)
-        1 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List,String)) >> ['foo', 'bar']
+        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, ConversionContext.of(HttpMethod.class)) >> Optional.of(HttpMethod.GET)
+        1 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.of(Argument.of(List,String))) >> ['foo', 'bar']
         result.get().status == HttpStatus.FORBIDDEN
     }
 
@@ -196,8 +197,8 @@ class CorsFilterSpec extends Specification {
 
         then: "the request is successful"
         4 * headers.getOrigin() >> Optional.of('http://www.foo.com')
-        2 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class) >> Optional.of(HttpMethod.GET)
-        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List,String)) >> Optional.of(['foo'])
+        2 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, ConversionContext.of(HttpMethod.class)) >> Optional.of(HttpMethod.GET)
+        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.of(Argument.of(List,String))) >> Optional.of(['foo'])
         result.get().status == HttpStatus.OK
     }
 
@@ -274,8 +275,8 @@ class CorsFilterSpec extends Specification {
         HttpResponse response = corsHandler.handleRequest(request).get()
 
         then: "the response is not modified"
-        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List,String)) >> Optional.of(['X-Header', 'Y-Header'])
-        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class) >> Optional.of(HttpMethod.GET)
+        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.of(Argument.of(List,String))) >> Optional.of(['X-Header', 'Y-Header'])
+        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, ConversionContext.of(HttpMethod.class)) >> Optional.of(HttpMethod.GET)
         response.getHeaders().get(ACCESS_CONTROL_ALLOW_METHODS) == 'GET'
         response.getHeaders().get(ACCESS_CONTROL_ALLOW_ORIGIN) == 'http://www.foo.com' // The origin is echo'd
         response.getHeaders().get(VARY) == 'Origin' // The vary header is set
@@ -305,8 +306,8 @@ class CorsFilterSpec extends Specification {
         HttpResponse response = corsHandler.handleRequest(request).get()
 
         then: "the response is not modified"
-        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List,String)) >> Optional.of(['X-Header', 'Y-Header'])
-        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class) >> Optional.of(HttpMethod.GET)
+        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.of(Argument.of(List,String))) >> Optional.of(['X-Header', 'Y-Header'])
+        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, ConversionContext.of(HttpMethod.class)) >> Optional.of(HttpMethod.GET)
         response.getHeaders().get(ACCESS_CONTROL_ALLOW_METHODS) == 'GET'
         response.getHeaders().get(ACCESS_CONTROL_ALLOW_ORIGIN) == 'http://www.foo.com' // The origin is echo'd
         response.getHeaders().get(VARY) == 'Origin' // The vary header is set

programming_language: Scala
filename: CorsFilter.java
change_type: ModificationType.MODIFY
diff: @@ -27,8 +27,9 @@ import static io.micronaut.http.HttpHeaders.ORIGIN;
 import static io.micronaut.http.HttpHeaders.VARY;
 
 import io.micronaut.core.async.publisher.Publishers;
+import io.micronaut.core.convert.ArgumentConversionContext;
+import io.micronaut.core.convert.ConversionContext;
 import io.micronaut.core.order.Ordered;
-import io.micronaut.core.type.Argument;
 import io.micronaut.core.util.StringUtils;
 import io.micronaut.http.HttpHeaders;
 import io.micronaut.http.HttpMethod;
@@ -59,6 +60,8 @@ import java.util.stream.Collectors;
 @Filter("/**")
 public class CorsFilter implements HttpServerFilter {
 
+    private static final ArgumentConversionContext<HttpMethod> CONVERSION_CONTEXT_HTTP_METHOD = ConversionContext.of(HttpMethod.class);
+
     protected final HttpServerConfiguration.CorsConfiguration corsConfiguration;
 
     /**
@@ -108,9 +111,9 @@ public class CorsFilter implements HttpServerFilter {
                 CorsOriginConfiguration config = optionalConfig.get();
 
                 if (CorsUtil.isPreflightRequest(request)) {
-                    Optional<HttpMethod> result = headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class);
+                    Optional<HttpMethod> result = headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, CONVERSION_CONTEXT_HTTP_METHOD);
                     setAllowMethods(result.get(), response);
-                    Optional<List> allowedHeaders = headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List.class, String.class));
+                    Optional<List<String>> allowedHeaders = headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.LIST_OF_STRING);
                     allowedHeaders.ifPresent(val ->
                         setAllowHeaders(val, response)
                     );
@@ -150,14 +153,14 @@ public class CorsFilter implements HttpServerFilter {
                 List<HttpMethod> allowedMethods = config.getAllowedMethods();
 
                 if (!isAnyMethod(allowedMethods)) {
-                    HttpMethod methodToMatch = preflight ? headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class).orElse(requestMethod) : requestMethod;
+                    HttpMethod methodToMatch = preflight ? headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, CONVERSION_CONTEXT_HTTP_METHOD).orElse(requestMethod) : requestMethod;
                     if (allowedMethods.stream().noneMatch(method -> method.equals(methodToMatch))) {
                         return Optional.of(HttpResponse.status(HttpStatus.FORBIDDEN));
                     }
                 }
 
                 if (preflight) {
-                    Optional<List<String>> accessControlHeaders = headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.listOf(String.class));
+                    Optional<List<String>> accessControlHeaders = headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.LIST_OF_STRING);
 
                     List<String> allowedHeaders = config.getAllowedHeaders();
 

programming_language: Java
filename: CorsOriginConverter.java
change_type: ModificationType.MODIFY
diff: @@ -15,6 +15,7 @@
  */
 package io.micronaut.http.server.cors;
 
+import io.micronaut.core.convert.ArgumentConversionContext;
 import io.micronaut.core.convert.ConversionContext;
 import io.micronaut.core.convert.TypeConverter;
 import io.micronaut.core.convert.value.ConvertibleValues;
@@ -23,6 +24,7 @@ import io.micronaut.core.type.Argument;
 import io.micronaut.http.HttpMethod;
 
 import javax.inject.Singleton;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
@@ -43,6 +45,8 @@ public class CorsOriginConverter implements TypeConverter<Object, CorsOriginConf
     private static final String ALLOW_CREDENTIALS = "allowCredentials";
     private static final String MAX_AGE = "maxAge";
 
+    private static final ArgumentConversionContext<List<HttpMethod>> CONVERSION_CONTEXT_LIST_OF_HTTP_METHOD = ConversionContext.of(Argument.listOf(HttpMethod.class));
+
     @Override
     public Optional<CorsOriginConfiguration> convert(Object object, Class<CorsOriginConfiguration> targetType, ConversionContext context) {
         CorsOriginConfiguration configuration = new CorsOriginConfiguration();
@@ -51,27 +55,27 @@ public class CorsOriginConverter implements TypeConverter<Object, CorsOriginConf
             ConvertibleValues<Object> convertibleValues = new ConvertibleValuesMap<>(mapConfig);
 
             convertibleValues
-                .get(ALLOWED_ORIGINS, Argument.listOf(String.class))
+                .get(ALLOWED_ORIGINS, ConversionContext.LIST_OF_STRING)
                 .ifPresent(configuration::setAllowedOrigins);
 
             convertibleValues
-                .get(ALLOWED_METHODS, Argument.listOf(HttpMethod.class))
+                .get(ALLOWED_METHODS, CONVERSION_CONTEXT_LIST_OF_HTTP_METHOD)
                 .ifPresent(configuration::setAllowedMethods);
 
             convertibleValues
-                .get(ALLOWED_HEADERS, Argument.listOf(String.class))
+                .get(ALLOWED_HEADERS, ConversionContext.LIST_OF_STRING)
                 .ifPresent(configuration::setAllowedHeaders);
 
             convertibleValues
-                .get(EXPOSED_HEADERS, Argument.listOf(String.class))
+                .get(EXPOSED_HEADERS, ConversionContext.LIST_OF_STRING)
                 .ifPresent(configuration::setExposedHeaders);
 
             convertibleValues
-                .get(ALLOW_CREDENTIALS, Boolean.class)
+                .get(ALLOW_CREDENTIALS, ConversionContext.BOOLEAN)
                 .ifPresent(configuration::setAllowCredentials);
 
             convertibleValues
-                .get(MAX_AGE, Long.class)
+                .get(MAX_AGE, ConversionContext.LONG)
                 .ifPresent(configuration::setMaxAge);
         }
         return Optional.of(configuration);

programming_language: Java
filename: HttpHeaders.java
change_type: ModificationType.MODIFY
diff: @@ -15,6 +15,7 @@
  */
 package io.micronaut.http;
 
+import io.micronaut.core.convert.ConversionContext;
 import io.micronaut.core.convert.ConversionService;
 import io.micronaut.core.type.Headers;
 
@@ -470,7 +471,7 @@ public interface HttpHeaders extends Headers {
      * @return An {@link Optional} of {@link Integer}
      */
     default Optional<Integer> findInt(CharSequence name) {
-        return get(name, Integer.class);
+        return get(name, ConversionContext.INT);
     }
 
     /**
@@ -480,7 +481,7 @@ public interface HttpHeaders extends Headers {
      * @return The first value or null if it is present
      */
     default Optional<String> findFirst(CharSequence name) {
-        return getFirst(name, String.class);
+        return getFirst(name, ConversionContext.STRING);
     }
 
     /**
@@ -489,7 +490,7 @@ public interface HttpHeaders extends Headers {
      * @return The content type
      */
     default Optional<MediaType> contentType() {
-        return getFirst(HttpHeaders.CONTENT_TYPE, MediaType.class);
+        return getFirst(HttpHeaders.CONTENT_TYPE, MediaType.CONVERSION_CONTEXT);
     }
 
     /**
@@ -498,7 +499,7 @@ public interface HttpHeaders extends Headers {
      * @return The content type
      */
     default OptionalLong contentLength() {
-        Optional<Long> optional = getFirst(HttpHeaders.CONTENT_LENGTH, Long.class);
+        Optional<Long> optional = getFirst(HttpHeaders.CONTENT_LENGTH, ConversionContext.LONG);
         return optional.map(OptionalLong::of).orElseGet(OptionalLong::empty);
     }
 
@@ -511,7 +512,7 @@ public interface HttpHeaders extends Headers {
         return getAll(HttpHeaders.ACCEPT)
             .stream()
             .flatMap(x -> Arrays.stream(x.split(",")))
-            .flatMap(s -> ConversionService.SHARED.convert(s, MediaType.class).map(Stream::of).orElse(Stream.empty()))
+            .flatMap(s -> ConversionService.SHARED.convert(s, MediaType.CONVERSION_CONTEXT).map(Stream::of).orElse(Stream.empty()))
             .distinct()
             .collect(Collectors.toList());
     }
@@ -520,7 +521,7 @@ public interface HttpHeaders extends Headers {
      * @return Whether the {@link HttpHeaders#CONNECTION} header is set to Keep-Alive
      */
     default boolean isKeepAlive() {
-        return getFirst(CONNECTION, String.class).map(val -> val.equalsIgnoreCase("keep-alive")).orElse(false);
+        return getFirst(CONNECTION, ConversionContext.STRING).map(val -> val.equalsIgnoreCase("keep-alive")).orElse(false);
     }
 
     /**

programming_language: Java
filename: MediaType.java
change_type: ModificationType.MODIFY
diff: @@ -15,9 +15,13 @@
  */
 package io.micronaut.http;
 
+import io.micronaut.core.annotation.Internal;
 import io.micronaut.core.annotation.TypeHint;
+import io.micronaut.core.convert.ArgumentConversionContext;
+import io.micronaut.core.convert.ConversionContext;
 import io.micronaut.core.convert.ConversionService;
 import io.micronaut.core.naming.NameUtils;
+import io.micronaut.core.type.Argument;
 import io.micronaut.core.util.StringUtils;
 import io.micronaut.core.value.OptionalValues;
 import io.micronaut.http.annotation.Produces;
@@ -318,6 +322,12 @@ public class MediaType implements CharSequence {
      */
     public static final String V_PARAMETER = "v";
 
+    @Internal
+    static final Argument<MediaType> ARGUMENT = Argument.of(MediaType.class);
+
+    @Internal
+    static final ArgumentConversionContext<MediaType> CONVERSION_CONTEXT = ConversionContext.of(ARGUMENT);
+
     private static final BigDecimal QUALITY_RATING_NUMBER = new BigDecimal("1.0");
     private static final String QUALITY_RATING = "1.0";
     private static final String SEMICOLON = ";";

programming_language: Java
filename: CacheInterceptor.java
change_type: ModificationType.MODIFY
diff: @@ -259,7 +259,7 @@ public class CacheInterceptor implements MethodInterceptor<Object, Object> {
             Object[] params = resolveParams(context, cacheable.get(MEMBER_PARAMETERS, String[].class, StringUtils.EMPTY_STRING_ARRAY));
             Object key = keyGenerator.generateKey(context, params);
             CompletableFuture<Object> thisFuture = new CompletableFuture<>();
-            Argument<?> firstTypeVariable = returnTypeObject.getFirstTypeVariable().orElse(Argument.of(Object.class));
+            Argument<?> firstTypeVariable = returnTypeObject.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
             asyncCache.get(key, firstTypeVariable).whenComplete((BiConsumer<Optional<?>, Throwable>) (o, throwable) -> {
                 if (throwable == null && o.isPresent()) {
                     // cache hit, return result
@@ -503,7 +503,7 @@ public class CacheInterceptor implements MethodInterceptor<Object, Object> {
         CacheKeyGenerator keyGenerator = resolveKeyGenerator(cacheOperation.defaultKeyGenerator, cacheable);
         Object[] params = resolveParams(context, cacheable.get(MEMBER_PARAMETERS, String[].class, StringUtils.EMPTY_STRING_ARRAY));
         Object key = keyGenerator.generateKey(context, params);
-        Argument<?> firstTypeVariable = returnTypeObject.getFirstTypeVariable().orElse(Argument.of(Object.class));
+        Argument<?> firstTypeVariable = returnTypeObject.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
 
         Maybe<Object> maybe = Maybe.create(emitter -> {
             asyncCache.get(key, firstTypeVariable).whenComplete((opt, throwable) -> {

programming_language: Java
filename: FileWatchCondition.java
change_type: ModificationType.MODIFY
diff: @@ -20,7 +20,7 @@ import io.micronaut.context.BeanContext;
 import io.micronaut.context.condition.Condition;
 import io.micronaut.context.condition.ConditionContext;
 import io.micronaut.core.annotation.Introspected;
-import io.micronaut.core.type.Argument;
+import io.micronaut.core.convert.ConversionContext;
 import io.micronaut.core.util.CollectionUtils;
 
 import java.io.File;
@@ -40,7 +40,7 @@ public class FileWatchCondition implements Condition {
         if (beanContext instanceof ApplicationContext) {
             List<String> paths = ((ApplicationContext) beanContext)
                     .getEnvironment()
-                    .getProperty(FileWatchConfiguration.PATHS, Argument.listOf(String.class))
+                    .getProperty(FileWatchConfiguration.PATHS, ConversionContext.LIST_OF_STRING)
                     .orElse(null);
 
             if (CollectionUtils.isNotEmpty(paths)) {

programming_language: Java
filename: SessionArgumentBinder.java
change_type: ModificationType.MODIFY
diff: @@ -42,6 +42,8 @@ import java.util.Optional;
 @Requires(classes = HttpServerConfiguration.class)
 public class SessionArgumentBinder implements TypedRequestArgumentBinder<Session> {
 
+    private static final Argument<Session> TYPE = Argument.of(Session.class);
+
     private final SessionStore<Session> sessionStore;
 
     /**
@@ -55,7 +57,7 @@ public class SessionArgumentBinder implements TypedRequestArgumentBinder<Session
 
     @Override
     public Argument<Session> argumentType() {
-        return Argument.of(Session.class);
+        return TYPE;
     }
 
     @Override

programming_language: Java