Skip to content

Commit f5f57e9

Browse files
committed
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
1 parent cde4431 commit f5f57e9

File tree

3 files changed

+62
-2
lines changed

3 files changed

+62
-2
lines changed

spring-web/src/main/java/org/springframework/web/accept/AbstractMappingContentNegotiationStrategy.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,17 @@ public AbstractMappingContentNegotiationStrategy(Map<String, MediaType> mediaTyp
4747

4848
@Override
4949
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
50-
String key = getMediaTypeKey(webRequest);
50+
return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest));
51+
}
52+
53+
/**
54+
* An alternative to {@link #resolveMediaTypes(NativeWebRequest)} that accepts
55+
* an already extracted key.
56+
* @since 3.2.16
57+
*/
58+
public List<MediaType> resolveMediaTypeKey(NativeWebRequest webRequest, String key)
59+
throws HttpMediaTypeNotAcceptableException {
60+
5161
if (StringUtils.hasText(key)) {
5262
MediaType mediaType = lookupMediaType(key);
5363
if (mediaType != null) {

spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ public ContentNegotiationManager() {
9999
}
100100

101101

102+
/**
103+
* Return the configured content negotiation strategies.
104+
* @since 3.2.16
105+
*/
106+
public List<ContentNegotiationStrategy> getStrategies() {
107+
return this.contentNegotiationStrategies;
108+
}
109+
102110
/**
103111
* Add MediaTypeFileExtensionResolver instances.
104112
* <p>Note that some {@link ContentNegotiationStrategy} implementations also

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import org.springframework.util.StringUtils;
4040
import org.springframework.web.HttpMediaTypeNotAcceptableException;
4141
import org.springframework.web.accept.ContentNegotiationManager;
42+
import org.springframework.web.accept.ContentNegotiationStrategy;
43+
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
4244
import org.springframework.web.context.request.NativeWebRequest;
4345
import org.springframework.web.context.request.ServletWebRequest;
4446
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
@@ -73,8 +75,14 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
7375
"json", "xml", "atom", "rss",
7476
"png", "jpe", "jpeg", "jpg", "gif", "wbmp", "bmp"));
7577

78+
private static final Set<String> WHITELISTED_MEDIA_BASE_TYPES = new HashSet<String>(
79+
Arrays.asList("audio", "image", "video"));
80+
81+
7682
private final ContentNegotiationManager contentNegotiationManager;
7783

84+
private final PathExtensionContentNegotiationStrategy pathStrategy;
85+
7886
private final ResponseBodyAdviceChain adviceChain;
7987

8088
private final Set<String> safeExtensions = new HashSet<String>();
@@ -94,11 +102,21 @@ protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>>
94102

95103
super(messageConverters);
96104
this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
105+
this.pathStrategy = initPathStrategy(this.contentNegotiationManager);
97106
this.adviceChain = new ResponseBodyAdviceChain(responseBodyAdvice);
98107
this.safeExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions());
99108
this.safeExtensions.addAll(WHITELISTED_EXTENSIONS);
100109
}
101110

111+
private static PathExtensionContentNegotiationStrategy initPathStrategy(ContentNegotiationManager manager) {
112+
for (ContentNegotiationStrategy strategy : manager.getStrategies()) {
113+
if (strategy instanceof PathExtensionContentNegotiationStrategy) {
114+
return (PathExtensionContentNegotiationStrategy) strategy;
115+
}
116+
}
117+
return new PathExtensionContentNegotiationStrategy();
118+
}
119+
102120

103121
protected ResponseBodyAdviceChain getAdviceChain() {
104122
return this.adviceChain;
@@ -322,7 +340,31 @@ private boolean safeExtension(HttpServletRequest request, String extension) {
322340
return true;
323341
}
324342
}
325-
return false;
343+
return safeMediaTypesForExtension(extension);
344+
}
345+
346+
private boolean safeMediaTypesForExtension(String extension) {
347+
List<MediaType> mediaTypes = null;
348+
try {
349+
mediaTypes = this.pathStrategy.resolveMediaTypeKey(null, extension);
350+
}
351+
catch (HttpMediaTypeNotAcceptableException e) {
352+
// Ignore
353+
}
354+
if (CollectionUtils.isEmpty(mediaTypes)) {
355+
return false;
356+
}
357+
for (MediaType mediaType : mediaTypes) {
358+
if (!safeMediaType(mediaType)) {
359+
return false;
360+
}
361+
}
362+
return true;
363+
}
364+
365+
private boolean safeMediaType(MediaType mediaType) {
366+
return (WHITELISTED_MEDIA_BASE_TYPES.contains(mediaType.getType()) ||
367+
mediaType.getSubtype().endsWith("+xml"));
326368
}
327369

328370
}

0 commit comments

Comments
 (0)