From e607f1c30fd99e146cdaf186c56d1f1275e3fbc1 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 20 Mar 2026 15:47:09 +0100 Subject: [PATCH] Share codings resolution in resource resolvers Prior to this commit, different resource resolvers would resolve accepted codings from the HTTP request sent by the client. This would be done with different implementations, which could lead to resolution errors and desynchronizations. This commit now introduced a new shared method in `EncodedResourceResolver` (Servlet and Reactive) to perform a consisten resolution. Fixes gh-36507 --- .../resource/CachingResourceResolver.java | 13 ++---- .../resource/EncodedResourceResolver.java | 40 ++++++++++++------- .../resource/CachingResourceResolver.java | 14 ++----- .../resource/EncodedResourceResolver.java | 38 ++++++++++++------ 4 files changed, 58 insertions(+), 47 deletions(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/CachingResourceResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/CachingResourceResolver.java index 93383608d51..87532cf32c4 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/CachingResourceResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/CachingResourceResolver.java @@ -17,10 +17,8 @@ package org.springframework.web.reactive.resource; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.stream.Collectors; import reactor.core.publisher.Mono; @@ -134,15 +132,12 @@ public class CachingResourceResolver extends AbstractResourceResolver { @Nullable private String getContentCodingKey(ServerWebExchange exchange) { - String header = exchange.getRequest().getHeaders().getFirst("Accept-Encoding"); - if (!StringUtils.hasText(header)) { + List acceptedCodings = EncodedResourceResolver.parseAcceptEncoding(exchange); + if (acceptedCodings.isEmpty()) { return null; } - return Arrays.stream(StringUtils.tokenizeToStringArray(header, ",")) - .map(token -> { - int index = token.indexOf(';'); - return (index >= 0 ? token.substring(0, index) : token).trim().toLowerCase(Locale.ROOT); - }) + return acceptedCodings + .stream() .filter(this.contentCodings::contains) .sorted() .collect(Collectors.joining(",")); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java index a0080f17c55..69e3b385b05 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java @@ -36,9 +36,9 @@ import reactor.core.publisher.Mono; import org.springframework.core.io.AbstractResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; -import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; /** @@ -141,23 +141,23 @@ public class EncodedResourceResolver extends AbstractResourceResolver { return resource; } - String acceptEncoding = getAcceptEncoding(exchange); - if (acceptEncoding == null) { + List acceptedCodings = parseAcceptEncoding(exchange); + if (acceptedCodings.isEmpty()) { return resource; } - for (String coding : this.contentCodings) { - if (acceptEncoding.contains(coding)) { + for (String acceptedCoding : acceptedCodings) { + if (this.contentCodings.contains(acceptedCoding)) { try { - String extension = getExtension(coding); - Resource encoded = new EncodedResource(resource, coding, extension); + String extension = getExtension(acceptedCoding); + Resource encoded = new EncodedResource(resource, acceptedCoding, extension); if (encoded.exists()) { return encoded; } } catch (IOException ex) { logger.trace(exchange.getLogPrefix() + - "No " + coding + " resource for [" + resource.getFilename() + "]", ex); + "No " + acceptedCoding + " resource for [" + resource.getFilename() + "]", ex); } } } @@ -166,13 +166,6 @@ public class EncodedResourceResolver extends AbstractResourceResolver { }); } - @Nullable - private String getAcceptEncoding(ServerWebExchange exchange) { - ServerHttpRequest request = exchange.getRequest(); - String header = request.getHeaders().getFirst(HttpHeaders.ACCEPT_ENCODING); - return (header != null ? header.toLowerCase(Locale.ROOT) : null); - } - private String getExtension(String coding) { String extension = this.extensions.get(coding); if (extension == null) { @@ -188,6 +181,23 @@ public class EncodedResourceResolver extends AbstractResourceResolver { return chain.resolveUrlPath(resourceUrlPath, locations); } + /** + * Parse the accepted encodings from the given HTTP exchange request. + */ + static List parseAcceptEncoding(ServerWebExchange exchange) { + String header = exchange.getRequest().getHeaders().getFirst("Accept-Encoding"); + if (!StringUtils.hasText(header)) { + return Collections.emptyList(); + } + header = header.toLowerCase(Locale.ROOT); + return Arrays.stream(StringUtils.tokenizeToStringArray(header, ",")) + .map(token -> { + int index = token.indexOf(';'); + return (index >= 0 ? token.substring(0, index) : token).trim(); + }) + .toList(); + } + /** * An encoded {@link HttpResource}. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CachingResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CachingResourceResolver.java index fb953dbdd99..a550fe585ef 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CachingResourceResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CachingResourceResolver.java @@ -17,10 +17,8 @@ package org.springframework.web.servlet.resource; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.stream.Collectors; import jakarta.servlet.http.HttpServletRequest; @@ -28,7 +26,6 @@ import jakarta.servlet.http.HttpServletRequest; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.core.io.Resource; -import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -141,15 +138,12 @@ public class CachingResourceResolver extends AbstractResourceResolver { @Nullable private String getContentCodingKey(HttpServletRequest request) { - String header = request.getHeader(HttpHeaders.ACCEPT_ENCODING); - if (!StringUtils.hasText(header)) { + List acceptedCodings = EncodedResourceResolver.parseAcceptEncoding(request); + if (acceptedCodings.isEmpty()) { return null; } - return Arrays.stream(StringUtils.tokenizeToStringArray(header, ",")) - .map(token -> { - int index = token.indexOf(';'); - return (index >= 0 ? token.substring(0, index) : token).trim().toLowerCase(Locale.ROOT); - }) + return acceptedCodings + .stream() .filter(this.contentCodings::contains) .sorted() .collect(Collectors.joining(",")); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/EncodedResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/EncodedResourceResolver.java index ecc3ca8db61..167b1e2f8f3 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/EncodedResourceResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/EncodedResourceResolver.java @@ -38,6 +38,7 @@ import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Resolver that delegates to the chain, and if a resource is found, it then @@ -139,23 +140,23 @@ public class EncodedResourceResolver extends AbstractResourceResolver { return resource; } - String acceptEncoding = getAcceptEncoding(request); - if (acceptEncoding == null) { + List acceptedCodings = parseAcceptEncoding(request); + if (acceptedCodings.isEmpty()) { return resource; } - for (String coding : this.contentCodings) { - if (acceptEncoding.contains(coding)) { + for (String acceptedCoding : acceptedCodings) { + if (this.contentCodings.contains(acceptedCoding)) { try { - String extension = getExtension(coding); - Resource encoded = new EncodedResource(resource, coding, extension); + String extension = getExtension(acceptedCoding); + Resource encoded = new EncodedResource(resource, acceptedCoding, extension); if (encoded.exists()) { return encoded; } } catch (IOException ex) { if (logger.isTraceEnabled()) { - logger.trace("No " + coding + " resource for [" + resource.getFilename() + "]", ex); + logger.trace("No " + acceptedCoding + " resource for [" + resource.getFilename() + "]", ex); } } } @@ -164,12 +165,6 @@ public class EncodedResourceResolver extends AbstractResourceResolver { return resource; } - @Nullable - private String getAcceptEncoding(HttpServletRequest request) { - String header = request.getHeader(HttpHeaders.ACCEPT_ENCODING); - return (header != null ? header.toLowerCase(Locale.ROOT) : null); - } - private String getExtension(String coding) { String extension = this.extensions.get(coding); if (extension == null) { @@ -186,6 +181,23 @@ public class EncodedResourceResolver extends AbstractResourceResolver { return chain.resolveUrlPath(resourceUrlPath, locations); } + /** + * Parse the accepted encodings from the given HTTP request. + */ + static List parseAcceptEncoding(HttpServletRequest request) { + String header = request.getHeader("Accept-Encoding"); + if (!StringUtils.hasText(header)) { + return Collections.emptyList(); + } + header = header.toLowerCase(Locale.ROOT); + return Arrays.stream(StringUtils.tokenizeToStringArray(header, ",")) + .map(token -> { + int index = token.indexOf(';'); + return (index >= 0 ? token.substring(0, index) : token).trim(); + }) + .toList(); + } + /** * An encoded {@link HttpResource}.