-
Bug
-
Resolution: Done
-
Major
-
4.6.0.Final
-
None
-
-
Undefined
The org.jboss.resteasy.plugins.delegates.MediaTypeHeaderDelegate class had a MediaType cache introduced to it back in March 2019. The cache is implemented with two maps: Map<String, MediaType> map and Map<MediaType, String> reverseMap.
Generally speaking, these maps contain a collection of common media types. So, for instance there are matching entries in both maps for application/json:
map: "application/json" -> MediaType.APPLICATION_JSON reverseMap: MediaType.APPLICATION_JSON -> "application/json"
However, if this code ever encounters a malformed Content Type string from another server in the response, then it can be poisoned. The specific example we have encountered of this is:
HTTP/1.1 200 OK Date: Thu, 14 Jan 2021 00:44:56 GMT Content-Type: application/json; Content-Length: 2
[ Note the semi-colon at the end of the content type header. It should be noted that this is not a valid value, but that doesn't stop a downstream server pushing it. ]
A Resteasy client receiving this response will call MediaTypeHeaderDelegate.parse("application/json;"):
public static MediaType parse(String type) { MediaType result = map.get(type); if (result == null) { result = internalParse(type); final int size = map.size(); if (size >= MAX_MT_CACHE_SIZE) { map.clear(); reverseMap.clear(); } map.put(type, result); reverseMap.put(result, type); } return result; }
The "get" on line 1 of the method will not return a value since type includes the semi-colon. Hence, it will be parsed into a MediaType = "application/json" and the two maps will look like this:
map: "application/json" -> MediaType.APPLICATION_JSON map: "application/json;" -> MediaType.APPLICATION_JSON reverseMap: MediaType.APPLICATION_JSON -> "application/json;"
From this point on, any calls to toString on this class passing the MediaType "application/json" will get the String "application/json;".
Now, this is where this gets interesting if you are (like us) using both Resteasy clients and Resteasy services in the same machine. This poisoned cache is also used by the server side of the JAX-RS code. So, anywhere where you have used the MediaType "application/json" in the @Produces section of a resource will exhibit the same behaviour.
@GET @Produces(MediaType.APPLICATION_JSON) Optional<SomeResult> doSomthing();
So now that server is returning non-compliant Content-Type headers in responses, and will continue to do so until the server is restarted. Then, any server using Resteasy client that talks to the newly poisoned server will also have its cache poisoned.
For us this exhibited as a server that would start up fine and respond with the correct Content-Type until shortly after startup when it reached out to another server. At which point it would then start responding erroneously.