From f5f57e95441166b82241f755b09a58743fe429f8 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 11 Nov 2015 17:46:36 -0500 Subject: [PATCH] Expand range of whitelisted extensions by media type This commit expands the range of whitelisted extensions by checking if an extension can be resolved to image/*, audio/*, video/*, as well as any content type that ends with +xml. Issue: SPR-13643 --- ...ractMappingContentNegotiationStrategy.java | 12 ++++- .../web/accept/ContentNegotiationManager.java | 8 ++++ ...stractMessageConverterMethodProcessor.java | 44 ++++++++++++++++++- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/accept/AbstractMappingContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/AbstractMappingContentNegotiationStrategy.java index 1133b060f5..c4524cae58 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/AbstractMappingContentNegotiationStrategy.java +++ b/spring-web/src/main/java/org/springframework/web/accept/AbstractMappingContentNegotiationStrategy.java @@ -47,7 +47,17 @@ public abstract class AbstractMappingContentNegotiationStrategy extends MappingM @Override public List resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException { - String key = getMediaTypeKey(webRequest); + return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest)); + } + + /** + * An alternative to {@link #resolveMediaTypes(NativeWebRequest)} that accepts + * an already extracted key. + * @since 3.2.16 + */ + public List resolveMediaTypeKey(NativeWebRequest webRequest, String key) + throws HttpMediaTypeNotAcceptableException { + if (StringUtils.hasText(key)) { MediaType mediaType = lookupMediaType(key); if (mediaType != null) { diff --git a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java index 9120a52fae..8dd94ebadd 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java +++ b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java @@ -99,6 +99,14 @@ public class ContentNegotiationManager implements ContentNegotiationStrategy, Me } + /** + * Return the configured content negotiation strategies. + * @since 3.2.16 + */ + public List getStrategies() { + return this.contentNegotiationStrategies; + } + /** * Add MediaTypeFileExtensionResolver instances. *

Note that some {@link ContentNegotiationStrategy} implementations also 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 2a8d7fd5d0..aa1ab5695a 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 @@ -39,6 +39,8 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.accept.ContentNegotiationManager; +import org.springframework.web.accept.ContentNegotiationStrategy; +import org.springframework.web.accept.PathExtensionContentNegotiationStrategy; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; @@ -73,8 +75,14 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe "json", "xml", "atom", "rss", "png", "jpe", "jpeg", "jpg", "gif", "wbmp", "bmp")); + private static final Set WHITELISTED_MEDIA_BASE_TYPES = new HashSet( + Arrays.asList("audio", "image", "video")); + + private final ContentNegotiationManager contentNegotiationManager; + private final PathExtensionContentNegotiationStrategy pathStrategy; + private final ResponseBodyAdviceChain adviceChain; private final Set safeExtensions = new HashSet(); @@ -94,11 +102,21 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe super(messageConverters); this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager()); + this.pathStrategy = initPathStrategy(this.contentNegotiationManager); this.adviceChain = new ResponseBodyAdviceChain(responseBodyAdvice); this.safeExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions()); this.safeExtensions.addAll(WHITELISTED_EXTENSIONS); } + private static PathExtensionContentNegotiationStrategy initPathStrategy(ContentNegotiationManager manager) { + for (ContentNegotiationStrategy strategy : manager.getStrategies()) { + if (strategy instanceof PathExtensionContentNegotiationStrategy) { + return (PathExtensionContentNegotiationStrategy) strategy; + } + } + return new PathExtensionContentNegotiationStrategy(); + } + protected ResponseBodyAdviceChain getAdviceChain() { return this.adviceChain; @@ -322,7 +340,31 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe return true; } } - return false; + return safeMediaTypesForExtension(extension); + } + + private boolean safeMediaTypesForExtension(String extension) { + List mediaTypes = null; + try { + mediaTypes = this.pathStrategy.resolveMediaTypeKey(null, extension); + } + catch (HttpMediaTypeNotAcceptableException e) { + // Ignore + } + if (CollectionUtils.isEmpty(mediaTypes)) { + return false; + } + for (MediaType mediaType : mediaTypes) { + if (!safeMediaType(mediaType)) { + return false; + } + } + return true; + } + + private boolean safeMediaType(MediaType mediaType) { + return (WHITELISTED_MEDIA_BASE_TYPES.contains(mediaType.getType()) || + mediaType.getSubtype().endsWith("+xml")); } }