Skip to content

Commit 77517d6

Browse files
committed
Merge path extension related deprecation changes
Closes gh-24179
2 parents 54669c5 + c69703f commit 77517d6

19 files changed

+484
-189
lines changed

spring-test/src/main/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -310,7 +310,11 @@ public StandaloneMockMvcBuilder setFlashMapManager(FlashMapManager flashMapManag
310310
* Whether to use suffix pattern match (".*") when matching patterns to
311311
* requests. If enabled a method mapped to "/users" also matches to "/users.*".
312312
* <p>The default value is {@code true}.
313+
* @deprecated as of 5.2.4. See class-level note in
314+
* {@link RequestMappingHandlerMapping} on the deprecation of path extension
315+
* config options.
313316
*/
317+
@Deprecated
314318
public StandaloneMockMvcBuilder setUseSuffixPatternMatch(boolean useSuffixPatternMatch) {
315319
this.useSuffixPatternMatch = useSuffixPatternMatch;
316320
return this;
@@ -442,6 +446,7 @@ protected Map<String, Object> extendMvcSingletons(@Nullable ServletContext servl
442446
/** Using the MVC Java configuration as the starting point for the "standalone" setup. */
443447
private class StandaloneConfiguration extends WebMvcConfigurationSupport {
444448

449+
@SuppressWarnings("deprecation")
445450
public RequestMappingHandlerMapping getHandlerMapping(
446451
FormattingConversionService mvcConversionService,
447452
ResourceUrlProvider mvcResourceUrlProvider) {

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

+42-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,13 +20,17 @@
2020
import java.util.Arrays;
2121
import java.util.Collection;
2222
import java.util.Collections;
23+
import java.util.HashMap;
2324
import java.util.LinkedHashSet;
2425
import java.util.List;
26+
import java.util.Map;
2527
import java.util.Set;
28+
import java.util.function.Function;
2629

2730
import org.springframework.http.MediaType;
2831
import org.springframework.lang.Nullable;
2932
import org.springframework.util.Assert;
33+
import org.springframework.util.CollectionUtils;
3034
import org.springframework.web.HttpMediaTypeNotAcceptableException;
3135
import org.springframework.web.context.request.NativeWebRequest;
3236

@@ -132,11 +136,7 @@ public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMe
132136

133137
@Override
134138
public List<String> resolveFileExtensions(MediaType mediaType) {
135-
Set<String> result = new LinkedHashSet<>();
136-
for (MediaTypeFileExtensionResolver resolver : this.resolvers) {
137-
result.addAll(resolver.resolveFileExtensions(mediaType));
138-
}
139-
return new ArrayList<>(result);
139+
return doResolveExtensions(resolver -> resolver.resolveFileExtensions(mediaType));
140140
}
141141

142142
/**
@@ -152,11 +152,44 @@ public List<String> resolveFileExtensions(MediaType mediaType) {
152152
*/
153153
@Override
154154
public List<String> getAllFileExtensions() {
155-
Set<String> result = new LinkedHashSet<>();
155+
return doResolveExtensions(MediaTypeFileExtensionResolver::getAllFileExtensions);
156+
}
157+
158+
private List<String> doResolveExtensions(Function<MediaTypeFileExtensionResolver, List<String>> extractor) {
159+
List<String> result = null;
160+
for (MediaTypeFileExtensionResolver resolver : this.resolvers) {
161+
List<String> extensions = extractor.apply(resolver);
162+
if (CollectionUtils.isEmpty(extensions)) {
163+
continue;
164+
}
165+
result = (result != null ? result : new ArrayList<>(4));
166+
for (String extension : extensions) {
167+
if (!result.contains(extension)) {
168+
result.add(extension);
169+
}
170+
}
171+
}
172+
return (result != null ? result : Collections.emptyList());
173+
}
174+
175+
/**
176+
* Return all registered lookup key to media type mappings by iterating
177+
* {@link MediaTypeFileExtensionResolver}s.
178+
* @since 5.2.4
179+
*/
180+
public Map<String, MediaType> getMediaTypeMappings() {
181+
Map<String, MediaType> result = null;
156182
for (MediaTypeFileExtensionResolver resolver : this.resolvers) {
157-
result.addAll(resolver.getAllFileExtensions());
183+
if (resolver instanceof MappingMediaTypeFileExtensionResolver) {
184+
Map<String, MediaType> map = ((MappingMediaTypeFileExtensionResolver) resolver).getMediaTypes();
185+
if (CollectionUtils.isEmpty(map)) {
186+
continue;
187+
}
188+
result = (result != null ? result : new HashMap<>(4));
189+
result.putAll(map);
190+
}
158191
}
159-
return new ArrayList<>(result);
192+
return (result != null ? result : Collections.emptyMap());
160193
}
161194

162195
}

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

+56-34
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -81,15 +81,18 @@
8181
* </tr>
8282
* </table>
8383
*
84-
* <p>As of 5.0 you can set the exact strategies to use via
84+
* <p>Alternatively you can avoid use of the above convenience builder
85+
* methods and set the exact strategies to use via
8586
* {@link #setStrategies(List)}.
8687
*
87-
* <p><strong>Note:</strong> if you must use URL-based content type resolution,
88-
* the use of a query parameter is simpler and preferable to the use of a path
89-
* extension since the latter can cause issues with URI variables, path
90-
* parameters, and URI decoding. Consider setting {@link #setFavorPathExtension}
91-
* to {@literal false} or otherwise set the strategies to use explicitly via
92-
* {@link #setStrategies(List)}.
88+
* <p><strong>Note:</strong> As of 5.2.4,
89+
* {@link #setFavorPathExtension(boolean) favorPathExtension} and
90+
* {@link #setIgnoreUnknownPathExtensions(boolean) ignoreUnknownPathExtensions}
91+
* are deprecated in order to discourage use of path extensions for content
92+
* negotiation as well as for request mapping (with similar deprecations in
93+
* {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
94+
* RequestMappingHandlerMapping}). For further context, please read issue
95+
* <a href="https://github.com/spring-projects/spring-framework/issues/24179">#24719</a>.
9396
*
9497
* @author Rossen Stoyanchev
9598
* @author Brian Clozel
@@ -145,52 +148,61 @@ public void setStrategies(@Nullable List<ContentNegotiationStrategy> strategies)
145148
* <p>By default this is set to {@code true} in which case a request
146149
* for {@code /hotels.pdf} will be interpreted as a request for
147150
* {@code "application/pdf"} regardless of the 'Accept' header.
151+
* @deprecated as of 5.2.4. See class-level note on the deprecation of path
152+
* extension config options.
148153
*/
154+
@Deprecated
149155
public void setFavorPathExtension(boolean favorPathExtension) {
150156
this.favorPathExtension = favorPathExtension;
151157
}
152158

153159
/**
154-
* Add a mapping from a key, extracted from a path extension or a query
155-
* parameter, to a MediaType. This is required in order for the parameter
156-
* strategy to work. Any extensions explicitly registered here are also
157-
* whitelisted for the purpose of Reflected File Download attack detection
158-
* (see Spring Framework reference documentation for more details on RFD
159-
* attack protection).
160-
* <p>The path extension strategy will also try to use
160+
* Add a mapping from a key to a MediaType where the key are normalized to
161+
* lowercase and may have been extracted from a path extension, a filename
162+
* extension, or passed as a query parameter.
163+
* <p>The {@link #setFavorParameter(boolean) parameter strategy} requires
164+
* such mappings in order to work while the {@link #setFavorPathExtension(boolean)
165+
* path extension strategy} can fall back on lookups via
161166
* {@link ServletContext#getMimeType} and
162-
* {@link org.springframework.http.MediaTypeFactory} to resolve path extensions.
167+
* {@link org.springframework.http.MediaTypeFactory}.
168+
* <p><strong>Note:</strong> Mappings registered here may be accessed via
169+
* {@link ContentNegotiationManager#getMediaTypeMappings()} and may be used
170+
* not only in the parameter and path extension strategies. For example,
171+
* with the Spring MVC config, e.g. {@code @EnableWebMvc} or
172+
* {@code <mvc:annotation-driven>}, the media type mappings are also plugged
173+
* in to:
174+
* <ul>
175+
* <li>Determine the media type of static resources served with
176+
* {@code ResourceHttpRequestHandler}.
177+
* <li>Determine the media type of views rendered with
178+
* {@code ContentNegotiatingViewResolver}.
179+
* <li>Whitelist extensions for RFD attack detection (check the Spring
180+
* Framework reference docs for details).
181+
* </ul>
163182
* @param mediaTypes media type mappings
164183
* @see #addMediaType(String, MediaType)
165184
* @see #addMediaTypes(Map)
166185
*/
167186
public void setMediaTypes(Properties mediaTypes) {
168187
if (!CollectionUtils.isEmpty(mediaTypes)) {
169-
mediaTypes.forEach((key, value) -> {
170-
String extension = ((String) key).toLowerCase(Locale.ENGLISH);
171-
MediaType mediaType = MediaType.valueOf((String) value);
172-
this.mediaTypes.put(extension, mediaType);
173-
});
188+
mediaTypes.forEach((key, value) ->
189+
addMediaType((String) key, MediaType.valueOf((String) value)));
174190
}
175191
}
176192

177193
/**
178-
* An alternative to {@link #setMediaTypes} for use in Java code.
179-
* @see #setMediaTypes
180-
* @see #addMediaTypes
194+
* An alternative to {@link #setMediaTypes} for programmatic registrations.
181195
*/
182-
public void addMediaType(String fileExtension, MediaType mediaType) {
183-
this.mediaTypes.put(fileExtension, mediaType);
196+
public void addMediaType(String key, MediaType mediaType) {
197+
this.mediaTypes.put(key.toLowerCase(Locale.ENGLISH), mediaType);
184198
}
185199

186200
/**
187-
* An alternative to {@link #setMediaTypes} for use in Java code.
188-
* @see #setMediaTypes
189-
* @see #addMediaType
201+
* An alternative to {@link #setMediaTypes} for programmatic registrations.
190202
*/
191203
public void addMediaTypes(@Nullable Map<String, MediaType> mediaTypes) {
192204
if (mediaTypes != null) {
193-
this.mediaTypes.putAll(mediaTypes);
205+
mediaTypes.forEach(this::addMediaType);
194206
}
195207
}
196208

@@ -199,7 +211,10 @@ public void addMediaTypes(@Nullable Map<String, MediaType> mediaTypes) {
199211
* to any media type. Setting this to {@code false} will result in an
200212
* {@code HttpMediaTypeNotAcceptableException} if there is no match.
201213
* <p>By default this is set to {@code true}.
214+
* @deprecated as of 5.2.4. See class-level note on the deprecation of path
215+
* extension config options.
202216
*/
217+
@Deprecated
203218
public void setIgnoreUnknownPathExtensions(boolean ignore) {
204219
this.ignoreUnknownPathExtensions = ignore;
205220
}
@@ -303,9 +318,10 @@ public void afterPropertiesSet() {
303318
}
304319

305320
/**
306-
* Actually build the {@link ContentNegotiationManager}.
321+
* Create and initialize a {@link ContentNegotiationManager} instance.
307322
* @since 5.0
308323
*/
324+
@SuppressWarnings("deprecation")
309325
public ContentNegotiationManager build() {
310326
List<ContentNegotiationStrategy> strategies = new ArrayList<>();
311327

@@ -327,7 +343,6 @@ public ContentNegotiationManager build() {
327343
}
328344
strategies.add(strategy);
329345
}
330-
331346
if (this.favorParameter) {
332347
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
333348
strategy.setParameterName(this.parameterName);
@@ -339,17 +354,24 @@ public ContentNegotiationManager build() {
339354
}
340355
strategies.add(strategy);
341356
}
342-
343357
if (!this.ignoreAcceptHeader) {
344358
strategies.add(new HeaderContentNegotiationStrategy());
345359
}
346-
347360
if (this.defaultNegotiationStrategy != null) {
348361
strategies.add(this.defaultNegotiationStrategy);
349362
}
350363
}
351364

352365
this.contentNegotiationManager = new ContentNegotiationManager(strategies);
366+
367+
// Ensure media type mappings are available via ContentNegotiationManager#getMediaTypeMappings()
368+
// independent of path extension or parameter strategies.
369+
370+
if (!CollectionUtils.isEmpty(this.mediaTypes) && !this.favorPathExtension && !this.favorParameter) {
371+
this.contentNegotiationManager.addFileExtensionResolvers(
372+
new MappingMediaTypeFileExtensionResolver(this.mediaTypes));
373+
}
374+
353375
return this.contentNegotiationManager;
354376
}
355377

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,9 +18,11 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.Collections;
21+
import java.util.HashSet;
2122
import java.util.List;
2223
import java.util.Locale;
2324
import java.util.Map;
25+
import java.util.Set;
2426
import java.util.concurrent.ConcurrentHashMap;
2527
import java.util.concurrent.ConcurrentMap;
2628
import java.util.concurrent.CopyOnWriteArrayList;
@@ -53,7 +55,7 @@ public class MappingMediaTypeFileExtensionResolver implements MediaTypeFileExten
5355
*/
5456
public MappingMediaTypeFileExtensionResolver(@Nullable Map<String, MediaType> mediaTypes) {
5557
if (mediaTypes != null) {
56-
List<String> allFileExtensions = new ArrayList<>();
58+
Set<String> allFileExtensions = new HashSet<>(mediaTypes.size());
5759
mediaTypes.forEach((extension, mediaType) -> {
5860
String lowerCaseExtension = extension.toLowerCase(Locale.ENGLISH);
5961
this.mediaTypes.put(lowerCaseExtension, mediaType);

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -41,7 +41,11 @@
4141
*
4242
* @author Rossen Stoyanchev
4343
* @since 3.2
44+
* @deprecated as of 5.2.4. See class-level note in
45+
* {@link ContentNegotiationManagerFactoryBean} on the deprecation of path
46+
* extension config options.
4447
*/
48+
@Deprecated
4549
public class PathExtensionContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
4650

4751
private UrlPathHelper urlPathHelper = new UrlPathHelper();

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,7 +34,11 @@
3434
*
3535
* @author Rossen Stoyanchev
3636
* @since 3.2
37+
* @deprecated as of 5.2.4. See class-level note in
38+
* {@link ContentNegotiationManagerFactoryBean} on the deprecation of path
39+
* extension config options.
3740
*/
41+
@Deprecated
3842
public class ServletPathExtensionContentNegotiationStrategy extends PathExtensionContentNegotiationStrategy {
3943

4044
private final ServletContext servletContext;

0 commit comments

Comments
 (0)