Skip to content

Commit 7f4da52

Browse files
committed
Remove Spring MVC path extension content negotiation
See gh-34036
1 parent e994693 commit 7f4da52

21 files changed

+21
-783
lines changed

framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc

-30
Original file line numberDiff line numberDiff line change
@@ -248,36 +248,6 @@ specific than other pattern that do not have double wildcards.
248248
For the full details, follow the above links to the pattern Comparators.
249249

250250

251-
[[mvc-ann-requestmapping-suffix-pattern-match]]
252-
== Suffix Match
253-
254-
Starting in 5.3, by default Spring MVC no longer performs `.{asterisk}` suffix pattern
255-
matching where a controller mapped to `/person` is also implicitly mapped to
256-
`/person.{asterisk}`. As a consequence path extensions are no longer used to interpret
257-
the requested content type for the response -- for example, `/person.pdf`, `/person.xml`,
258-
and so on.
259-
260-
Using file extensions in this way was necessary when browsers used to send `Accept` headers
261-
that were hard to interpret consistently. At present, that is no longer a necessity and
262-
using the `Accept` header should be the preferred choice.
263-
264-
Over time, the use of file name extensions has proven problematic in a variety of ways.
265-
It can cause ambiguity when overlain with the use of URI variables, path parameters, and
266-
URI encoding. Reasoning about URL-based authorization
267-
and security (see next section for more details) also becomes more difficult.
268-
269-
To completely disable the use of path extensions in versions prior to 5.3, set the following:
270-
271-
* `useSuffixPatternMatching(false)`, see xref:web/webmvc/mvc-config/path-matching.adoc[PathMatchConfigurer]
272-
* `favorPathExtension(false)`, see xref:web/webmvc/mvc-config/content-negotiation.adoc[ContentNegotiationConfigurer]
273-
274-
Having a way to request content types other than through the `"Accept"` header can still
275-
be useful, for example, when typing a URL in a browser. A safe alternative to path extensions is
276-
to use the query parameter strategy. If you must use file extensions, consider restricting
277-
them to a list of explicitly registered extensions through the `mediaTypes` property of
278-
xref:web/webmvc/mvc-config/content-negotiation.adoc[ContentNegotiationConfigurer].
279-
280-
281251
[[mvc-ann-requestmapping-rfd]]
282252
== Suffix Match and RFD
283253

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -83,8 +83,7 @@ public boolean isUseRegisteredExtensionsOnly() {
8383
/**
8484
* Whether to ignore requests with unknown file extension. Setting this to
8585
* {@code false} results in {@code HttpMediaTypeNotAcceptableException}.
86-
* <p>By default this is set to {@literal false} but is overridden in
87-
* {@link PathExtensionContentNegotiationStrategy} to {@literal true}.
86+
* <p>By default, this is set to {@literal false}.
8887
*/
8988
public void setIgnoreUnknownExtensions(boolean ignoreUnknownExtensions) {
9089
this.ignoreUnknownExtensions = ignoreUnknownExtensions;

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

-11
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,6 @@ public List<String> resolveFileExtensions(MediaType mediaType) {
139139
return doResolveExtensions(resolver -> resolver.resolveFileExtensions(mediaType));
140140
}
141141

142-
/**
143-
* {@inheritDoc}
144-
* <p>At startup this method returns extensions explicitly registered with
145-
* either {@link PathExtensionContentNegotiationStrategy} or
146-
* {@link ParameterContentNegotiationStrategy}. At runtime if there is a
147-
* "path extension" strategy and its
148-
* {@link PathExtensionContentNegotiationStrategy#setUseRegisteredExtensionsOnly(boolean)
149-
* useRegisteredExtensionsOnly} property is set to "false", the list of extensions may
150-
* increase as file extensions are resolved via
151-
* {@link org.springframework.http.MediaTypeFactory} and cached.
152-
*/
153142
@Override
154143
public List<String> getAllFileExtensions() {
155144
return doResolveExtensions(MediaTypeFileExtensionResolver::getAllFileExtensions);

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

+5-101
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,13 @@
2323
import java.util.Map;
2424
import java.util.Properties;
2525

26-
import jakarta.servlet.ServletContext;
27-
2826
import org.springframework.beans.factory.FactoryBean;
2927
import org.springframework.beans.factory.InitializingBean;
3028
import org.springframework.http.MediaType;
3129
import org.springframework.http.MediaTypeFactory;
3230
import org.springframework.lang.Nullable;
3331
import org.springframework.util.Assert;
3432
import org.springframework.util.CollectionUtils;
35-
import org.springframework.web.context.ServletContextAware;
3633

3734
/**
3835
* Factory to create a {@code ContentNegotiationManager} and configure it with
@@ -56,12 +53,6 @@
5653
* <td>Off</td>
5754
* </tr>
5855
* <tr>
59-
* <td>{@link #setFavorPathExtension favorPathExtension}</td>
60-
* <td>false (as of 5.3)</td>
61-
* <td>{@link PathExtensionContentNegotiationStrategy}</td>
62-
* <td>Off</td>
63-
* </tr>
64-
* <tr>
6556
* <td>{@link #setIgnoreAcceptHeader ignoreAcceptHeader}</td>
6657
* <td>false</td>
6758
* <td>{@link HeaderContentNegotiationStrategy}</td>
@@ -85,20 +76,11 @@
8576
* methods and set the exact strategies to use via
8677
* {@link #setStrategies(List)}.
8778
*
88-
* <p><strong>Deprecation 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 using path extensions for content
92-
* negotiation and for request mapping with similar deprecations on
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>.
9679
* @author Rossen Stoyanchev
9780
* @author Brian Clozel
9881
* @since 3.2
9982
*/
100-
public class ContentNegotiationManagerFactoryBean
101-
implements FactoryBean<ContentNegotiationManager>, ServletContextAware, InitializingBean {
83+
public class ContentNegotiationManagerFactoryBean implements FactoryBean<ContentNegotiationManager>, InitializingBean {
10284

10385
@Nullable
10486
private List<ContentNegotiationStrategy> strategies;
@@ -108,12 +90,8 @@ public class ContentNegotiationManagerFactoryBean
10890

10991
private String parameterName = "format";
11092

111-
private boolean favorPathExtension = false;
112-
11393
private final Map<String, MediaType> mediaTypes = new HashMap<>();
11494

115-
private boolean ignoreUnknownPathExtensions = true;
116-
11795
@Nullable
11896
private Boolean useRegisteredExtensionsOnly;
11997

@@ -125,9 +103,6 @@ public class ContentNegotiationManagerFactoryBean
125103
@Nullable
126104
private ContentNegotiationManager contentNegotiationManager;
127105

128-
@Nullable
129-
private ServletContext servletContext;
130-
131106

132107
/**
133108
* Set the exact list of strategies to use.
@@ -161,30 +136,12 @@ public void setParameterName(String parameterName) {
161136
this.parameterName = parameterName;
162137
}
163138

164-
/**
165-
* Whether the path extension in the URL path should be used to determine
166-
* the requested media type.
167-
* <p>By default this is set to {@code false} in which case path extensions
168-
* have no impact on content negotiation.
169-
* @deprecated as of 5.2.4. See class-level note on the deprecation of path
170-
* extension config options. As there is no replacement for this method,
171-
* in 5.2.x it is necessary to set it to {@code false}. In 5.3 the default
172-
* changes to {@code false} and use of this property becomes unnecessary.
173-
*/
174-
@Deprecated
175-
public void setFavorPathExtension(boolean favorPathExtension) {
176-
this.favorPathExtension = favorPathExtension;
177-
}
178-
179139
/**
180140
* Add a mapping from a key to a MediaType where the key are normalized to
181141
* lowercase and may have been extracted from a path extension, a filename
182142
* extension, or passed as a query parameter.
183143
* <p>The {@link #setFavorParameter(boolean) parameter strategy} requires
184-
* such mappings in order to work while the {@link #setFavorPathExtension(boolean)
185-
* path extension strategy} can fall back on lookups via
186-
* {@link ServletContext#getMimeType} and
187-
* {@link org.springframework.http.MediaTypeFactory}.
144+
* such mappings in order to work.
188145
* <p><strong>Note:</strong> Mappings registered here may be accessed via
189146
* {@link ContentNegotiationManager#getMediaTypeMappings()} and may be used
190147
* not only in the parameter and path extension strategies. For example,
@@ -227,35 +184,10 @@ public void addMediaTypes(@Nullable Map<String, MediaType> mediaTypes) {
227184
}
228185

229186
/**
230-
* Whether to ignore requests with path extension that cannot be resolved
231-
* to any media type. Setting this to {@code false} will result in an
232-
* {@code HttpMediaTypeNotAcceptableException} if there is no match.
233-
* <p>By default this is set to {@code true}.
234-
* @deprecated as of 5.2.4. See class-level note on the deprecation of path
235-
* extension config options.
236-
*/
237-
@Deprecated
238-
public void setIgnoreUnknownPathExtensions(boolean ignore) {
239-
this.ignoreUnknownPathExtensions = ignore;
240-
}
241-
242-
/**
243-
* Indicate whether to use the Java Activation Framework as a fallback option
244-
* to map from file extensions to media types.
245-
* @deprecated as of 5.0, in favor of {@link #setUseRegisteredExtensionsOnly(boolean)},
246-
* which has reverse behavior.
247-
*/
248-
@Deprecated
249-
public void setUseJaf(boolean useJaf) {
250-
setUseRegisteredExtensionsOnly(!useJaf);
251-
}
252-
253-
/**
254-
* When {@link #setFavorPathExtension favorPathExtension} or
255-
* {@link #setFavorParameter(boolean)} is set, this property determines
187+
* When {@link #setFavorParameter(boolean)} is set, this property determines
256188
* whether to use only registered {@code MediaType} mappings or to allow
257189
* dynamic resolution, for example, via {@link MediaTypeFactory}.
258-
* <p>By default this is not set in which case dynamic resolution is on.
190+
* <p>By default, this is not set in which case dynamic resolution is on.
259191
*/
260192
public void setUseRegisteredExtensionsOnly(boolean useRegisteredExtensionsOnly) {
261193
this.useRegisteredExtensionsOnly = useRegisteredExtensionsOnly;
@@ -303,14 +235,6 @@ public void setDefaultContentTypeStrategy(ContentNegotiationStrategy strategy) {
303235
this.defaultNegotiationStrategy = strategy;
304236
}
305237

306-
/**
307-
* Invoked by Spring to inject the ServletContext.
308-
*/
309-
@Override
310-
public void setServletContext(ServletContext servletContext) {
311-
this.servletContext = servletContext;
312-
}
313-
314238

315239
@Override
316240
public void afterPropertiesSet() {
@@ -321,28 +245,13 @@ public void afterPropertiesSet() {
321245
* Create and initialize a {@link ContentNegotiationManager} instance.
322246
* @since 5.0
323247
*/
324-
@SuppressWarnings("deprecation")
325248
public ContentNegotiationManager build() {
326249
List<ContentNegotiationStrategy> strategies = new ArrayList<>();
327250

328251
if (this.strategies != null) {
329252
strategies.addAll(this.strategies);
330253
}
331254
else {
332-
if (this.favorPathExtension) {
333-
PathExtensionContentNegotiationStrategy strategy;
334-
if (this.servletContext != null && !useRegisteredExtensionsOnly()) {
335-
strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes);
336-
}
337-
else {
338-
strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
339-
}
340-
strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions);
341-
if (this.useRegisteredExtensionsOnly != null) {
342-
strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
343-
}
344-
strategies.add(strategy);
345-
}
346255
if (this.favorParameter) {
347256
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
348257
strategy.setParameterName(this.parameterName);
@@ -367,7 +276,7 @@ public ContentNegotiationManager build() {
367276
// Ensure media type mappings are available via ContentNegotiationManager#getMediaTypeMappings()
368277
// independent of path extension or parameter strategies.
369278

370-
if (!CollectionUtils.isEmpty(this.mediaTypes) && !this.favorPathExtension && !this.favorParameter) {
279+
if (!CollectionUtils.isEmpty(this.mediaTypes) && !this.favorParameter) {
371280
this.contentNegotiationManager.addFileExtensionResolvers(
372281
new MappingMediaTypeFileExtensionResolver(this.mediaTypes));
373282
}
@@ -387,9 +296,4 @@ public Class<?> getObjectType() {
387296
return ContentNegotiationManager.class;
388297
}
389298

390-
@Override
391-
public boolean isSingleton() {
392-
return true;
393-
}
394-
395299
}

0 commit comments

Comments
 (0)