From 0d2aa51576d150ec95a81573e3b84d641366e214 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 1 May 2014 11:54:48 -0400 Subject: [PATCH] Update ContentNegotiationManager for unknown path exts This change refines the logic of "mapping" content negotiation strategies with regards to how to handle cases where no mapping is found. The request parameter strategy now treats request parameter values that do not match any mapped media type as 406 errors. The path extension strategy provides a new flag called "ignoreUnknownExtensions" (true by default) that when set to false also results in a 406. The same flag is also exposed through the ContentNegotiationManagerFactoryBean and the MVC Java config. Issue: SPR-10170 --- ...ractMappingContentNegotiationStrategy.java | 9 ++- .../ContentNegotiationManagerFactoryBean.java | 16 ++++- ...MappingMediaTypeFileExtensionResolver.java | 6 ++ .../ParameterContentNegotiationStrategy.java | 7 +- ...thExtensionContentNegotiationStrategy.java | 32 +++++++-- ...thExtensionContentNegotiationStrategy.java | 7 +- ...entNegotiationManagerFactoryBeanTests.java | 51 +++++++++++++-- ...appingContentNegotiationStrategyTests.java | 8 +-- ...ngMediaTypeFileExtensionResolverTests.java | 2 +- ...ensionContentNegotiationStrategyTests.java | 65 ++++++++++++++----- .../ContentNegotiationConfigurer.java | 14 +++- 11 files changed, 175 insertions(+), 42 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 a85e4b19b67..1133b060f58 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import java.util.Map; import org.springframework.http.MediaType; import org.springframework.util.StringUtils; +import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.context.request.NativeWebRequest; /** @@ -34,6 +35,7 @@ import org.springframework.web.context.request.NativeWebRequest; public abstract class AbstractMappingContentNegotiationStrategy extends MappingMediaTypeFileExtensionResolver implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver { + /** * Create an instance with the given extension-to-MediaType lookup. * @throws IllegalArgumentException if a media type string cannot be parsed @@ -42,8 +44,9 @@ public abstract class AbstractMappingContentNegotiationStrategy extends MappingM super(mediaTypes); } + @Override - public List resolveMediaTypes(NativeWebRequest webRequest) { + public List resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException { String key = getMediaTypeKey(webRequest); if (StringUtils.hasText(key)) { MediaType mediaType = lookupMediaType(key); @@ -76,7 +79,7 @@ public abstract class AbstractMappingContentNegotiationStrategy extends MappingM * Invoked when no matching media type is found in the lookup map. * Sub-classes can take further steps to determine the media type. */ - protected MediaType handleNoMatch(NativeWebRequest request, String mappingKey) { + protected MediaType handleNoMatch(NativeWebRequest request, String key) throws HttpMediaTypeNotAcceptableException { return null; } diff --git a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java index 23fc2c40178..689f55ae8c9 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java +++ b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,6 +55,8 @@ public class ContentNegotiationManagerFactoryBean private Map mediaTypes = new HashMap(); + private boolean ignoreUnknownPathExtensions = true; + private Boolean useJaf; private String parameterName = "format"; @@ -116,6 +118,17 @@ public class ContentNegotiationManagerFactoryBean } } + /** + * Whether to ignore requests that have a file extension that does not match + * any mapped media types. Setting this to {@code false} will result in a + * {@code HttpMediaTypeNotAcceptableException} when there is no match. + * + *

By default this is set to {@code true}. + */ + public void setIgnoreUnknownPathExtensions(boolean ignoreUnknownPathExtensions) { + this.ignoreUnknownPathExtensions = ignoreUnknownPathExtensions; + } + /** * Indicate whether to use the Java Activation Framework as a fallback option * to map from file extensions to media types. This is used only when @@ -191,6 +204,7 @@ public class ContentNegotiationManagerFactoryBean } else { strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes); } + strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions); if (this.useJaf != null) { strategy.setUseJaf(this.useJaf); } diff --git a/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java b/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java index 71e670d0fd6..3fee1520815 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java +++ b/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java @@ -16,6 +16,7 @@ package org.springframework.web.accept; +import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -59,6 +60,7 @@ public class MappingMediaTypeFileExtensionResolver implements MediaTypeFileExten } } + /** * Find the file extensions mapped to the given MediaType. * @return 0 or more extensions, never {@code null} @@ -74,6 +76,10 @@ public class MappingMediaTypeFileExtensionResolver implements MediaTypeFileExten return Collections.unmodifiableList(this.allFileExtensions); } + protected List getAllMediaTypes() { + return new ArrayList(this.mediaTypes.values()); + } + /** * Return the MediaType mapped to the given extension. * @return a MediaType for the key or {@code null} diff --git a/spring-web/src/main/java/org/springframework/web/accept/ParameterContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/ParameterContentNegotiationStrategy.java index c5fe9f1b4f7..4e95128bdbf 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/ParameterContentNegotiationStrategy.java +++ b/spring-web/src/main/java/org/springframework/web/accept/ParameterContentNegotiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.http.MediaType; import org.springframework.util.Assert; +import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.context.request.NativeWebRequest; /** @@ -68,4 +69,8 @@ public class ParameterContentNegotiationStrategy extends AbstractMappingContentN } } + @Override + protected MediaType handleNoMatch(NativeWebRequest request, String key) throws HttpMediaTypeNotAcceptableException { + throw new HttpMediaTypeNotAcceptableException(getAllMediaTypes()); + } } diff --git a/spring-web/src/main/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategy.java index c3c24fe85b8..8324bcf3948 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategy.java +++ b/spring-web/src/main/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; +import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.WebUtils; @@ -65,6 +66,8 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont private boolean useJaf = JAF_PRESENT; + private boolean ignoreUnknownExtensions = true; + /** * Create an instance with the given extension-to-MediaType lookup. @@ -82,14 +85,30 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont super(null); } + /** - * Indicate whether to use the Java Activation Framework to map from file extensions to media types. - *

Default is {@code true}, i.e. the Java Activation Framework is used (if available). + * Indicate whether to use the Java Activation Framework to map from file + * extensions to media types. + * + *

Default is {@code true}, i.e. the Java Activation Framework is used + * (if available). */ public void setUseJaf(boolean useJaf) { this.useJaf = useJaf; } + /** + * Whether to ignore requests that have a file extension that does not match + * any mapped media types. Setting this to {@code false} will result in a + * {@code HttpMediaTypeNotAcceptableException}. + * + *

By default this is set to {@code true}. + */ + public void setIgnoreUnknownExtensions(boolean ignoreUnknownExtensions) { + this.ignoreUnknownExtensions = ignoreUnknownExtensions; + } + + @Override protected String getMediaTypeKey(NativeWebRequest webRequest) { HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); @@ -108,13 +127,18 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont } @Override - protected MediaType handleNoMatch(NativeWebRequest webRequest, String extension) { + protected MediaType handleNoMatch(NativeWebRequest webRequest, String extension) + throws HttpMediaTypeNotAcceptableException { + if (this.useJaf) { MediaType jafMediaType = JafMediaTypeFactory.getMediaType("file." + extension); if (jafMediaType != null && !MediaType.APPLICATION_OCTET_STREAM.equals(jafMediaType)) { return jafMediaType; } } + if (!this.ignoreUnknownExtensions) { + throw new HttpMediaTypeNotAcceptableException(getAllMediaTypes()); + } return null; } diff --git a/spring-web/src/main/java/org/springframework/web/accept/ServletPathExtensionContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/ServletPathExtensionContentNegotiationStrategy.java index 4e8ad8a317a..86aa5ad75bd 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/ServletPathExtensionContentNegotiationStrategy.java +++ b/spring-web/src/main/java/org/springframework/web/accept/ServletPathExtensionContentNegotiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import javax.servlet.ServletContext; import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.context.request.NativeWebRequest; /** @@ -64,7 +65,9 @@ public class ServletPathExtensionContentNegotiationStrategy extends PathExtensio * and if that doesn't help, delegate to the parent implementation. */ @Override - protected MediaType handleNoMatch(NativeWebRequest webRequest, String extension) { + protected MediaType handleNoMatch(NativeWebRequest webRequest, String extension) + throws HttpMediaTypeNotAcceptableException { + MediaType mediaType = null; if (this.servletContext != null) { String mimeType = this.servletContext.getMimeType("file." + extension); diff --git a/spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java b/spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java index f7924b840ed..7a61577f00e 100644 --- a/spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java +++ b/spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.http.MediaType; import org.springframework.mock.web.test.MockHttpServletRequest; +import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; @@ -41,6 +42,7 @@ public class ContentNegotiationManagerFactoryBeanTests { private MockHttpServletRequest servletRequest; + @Before public void setup() { this.servletRequest = new MockHttpServletRequest(); @@ -50,6 +52,7 @@ public class ContentNegotiationManagerFactoryBeanTests { this.factoryBean.setServletContext(this.servletRequest.getServletContext()); } + @Test public void defaultSettings() throws Exception { this.factoryBean.afterPropertiesSet(); @@ -60,11 +63,16 @@ public class ContentNegotiationManagerFactoryBeanTests { assertEquals("Should be able to resolve file extensions by default", Arrays.asList(MediaType.IMAGE_GIF), manager.resolveMediaTypes(this.webRequest)); - this.servletRequest.setRequestURI("/flower?format=gif"); - this.servletRequest.addParameter("format", "gif"); + this.servletRequest.setRequestURI("/flower.xyz"); + + assertEquals("Should ignore unknown extensions by default", + Collections.emptyList(), manager.resolveMediaTypes(this.webRequest)); + + this.servletRequest.setRequestURI("/flower"); + this.servletRequest.setParameter("format", "gif"); assertEquals("Should not resolve request parameters by default", - Collections.emptyList(), manager.resolveMediaTypes(this.webRequest)); + Collections.emptyList(), manager.resolveMediaTypes(this.webRequest)); this.servletRequest.setRequestURI("/flower"); this.servletRequest.addHeader("Accept", MediaType.IMAGE_GIF_VALUE); @@ -75,7 +83,7 @@ public class ContentNegotiationManagerFactoryBeanTests { @Test public void addMediaTypes() throws Exception { - Map mediaTypes = new HashMap(); + Map mediaTypes = new HashMap<>(); mediaTypes.put("json", MediaType.APPLICATION_JSON); this.factoryBean.addMediaTypes(mediaTypes); @@ -86,11 +94,26 @@ public class ContentNegotiationManagerFactoryBeanTests { assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(this.webRequest)); } + // SPR-10170 + + @Test(expected = HttpMediaTypeNotAcceptableException.class) + public void favorPathExtensionWithUnknownMediaType() throws Exception { + this.factoryBean.setFavorPathExtension(true); + this.factoryBean.setIgnoreUnknownPathExtensions(false); + this.factoryBean.afterPropertiesSet(); + ContentNegotiationManager manager = this.factoryBean.getObject(); + + this.servletRequest.setRequestURI("/flower.xyz"); + this.servletRequest.addParameter("format", "json"); + + manager.resolveMediaTypes(this.webRequest); + } + @Test public void favorParameter() throws Exception { this.factoryBean.setFavorParameter(true); - Map mediaTypes = new HashMap(); + Map mediaTypes = new HashMap<>(); mediaTypes.put("json", MediaType.APPLICATION_JSON); this.factoryBean.addMediaTypes(mediaTypes); @@ -103,6 +126,20 @@ public class ContentNegotiationManagerFactoryBeanTests { assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(this.webRequest)); } + // SPR-10170 + + @Test(expected = HttpMediaTypeNotAcceptableException.class) + public void favorParameterWithUnknownMediaType() throws HttpMediaTypeNotAcceptableException { + this.factoryBean.setFavorParameter(true); + this.factoryBean.afterPropertiesSet(); + ContentNegotiationManager manager = this.factoryBean.getObject(); + + this.servletRequest.setRequestURI("/flower"); + this.servletRequest.setParameter("format", "xyz"); + + manager.resolveMediaTypes(this.webRequest); + } + @Test public void ignoreAcceptHeader() throws Exception { this.factoryBean.setIgnoreAcceptHeader(true); @@ -112,7 +149,7 @@ public class ContentNegotiationManagerFactoryBeanTests { this.servletRequest.setRequestURI("/flower"); this.servletRequest.addHeader("Accept", MediaType.IMAGE_GIF_VALUE); - assertEquals(Collections.emptyList(), manager.resolveMediaTypes(this.webRequest)); + assertEquals(Collections.emptyList(), manager.resolveMediaTypes(this.webRequest)); } @Test diff --git a/spring-web/src/test/java/org/springframework/web/accept/MappingContentNegotiationStrategyTests.java b/spring-web/src/test/java/org/springframework/web/accept/MappingContentNegotiationStrategyTests.java index f4b9f25bc9d..f6a72363676 100644 --- a/spring-web/src/test/java/org/springframework/web/accept/MappingContentNegotiationStrategyTests.java +++ b/spring-web/src/test/java/org/springframework/web/accept/MappingContentNegotiationStrategyTests.java @@ -35,7 +35,7 @@ import org.springframework.web.context.request.NativeWebRequest; public class MappingContentNegotiationStrategyTests { @Test - public void resolveMediaTypes() { + public void resolveMediaTypes() throws Exception { Map mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON); TestMappingContentNegotiationStrategy strategy = new TestMappingContentNegotiationStrategy("json", mapping); @@ -46,7 +46,7 @@ public class MappingContentNegotiationStrategyTests { } @Test - public void resolveMediaTypesNoMatch() { + public void resolveMediaTypesNoMatch() throws Exception { Map mapping = null; TestMappingContentNegotiationStrategy strategy = new TestMappingContentNegotiationStrategy("blah", mapping); @@ -56,7 +56,7 @@ public class MappingContentNegotiationStrategyTests { } @Test - public void resolveMediaTypesNoKey() { + public void resolveMediaTypesNoKey() throws Exception { Map mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON); TestMappingContentNegotiationStrategy strategy = new TestMappingContentNegotiationStrategy(null, mapping); @@ -66,7 +66,7 @@ public class MappingContentNegotiationStrategyTests { } @Test - public void resolveMediaTypesHandleNoMatch() { + public void resolveMediaTypesHandleNoMatch() throws Exception { Map mapping = null; TestMappingContentNegotiationStrategy strategy = new TestMappingContentNegotiationStrategy("xml", mapping); diff --git a/spring-web/src/test/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolverTests.java b/spring-web/src/test/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolverTests.java index b00b7c5d0af..ad7680d251f 100644 --- a/spring-web/src/test/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-web/src/test/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategyTests.java b/spring-web/src/test/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategyTests.java index b814c5926e3..effb8357d0b 100644 --- a/spring-web/src/test/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategyTests.java +++ b/spring-web/src/test/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import static org.junit.Assert.assertTrue; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import javax.servlet.ServletContext; @@ -28,6 +29,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.http.MediaType; import org.springframework.mock.web.test.MockHttpServletRequest; +import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; @@ -43,32 +45,37 @@ public class PathExtensionContentNegotiationStrategyTests { private MockHttpServletRequest servletRequest; + @Before public void setup() { this.servletRequest = new MockHttpServletRequest(); this.webRequest = new ServletWebRequest(servletRequest); } + @Test - public void resolveMediaTypesFromMapping() { + public void resolveMediaTypesFromMapping() throws Exception { + this.servletRequest.setRequestURI("test.html"); - PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(); + PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(); List mediaTypes = strategy.resolveMediaTypes(this.webRequest); assertEquals(Arrays.asList(new MediaType("text", "html")), mediaTypes); - strategy = new PathExtensionContentNegotiationStrategy(Collections.singletonMap("HTML", MediaType.APPLICATION_XHTML_XML)); + Map mapping = Collections.singletonMap("HTML", MediaType.APPLICATION_XHTML_XML); + strategy = new PathExtensionContentNegotiationStrategy(mapping); mediaTypes = strategy.resolveMediaTypes(this.webRequest); assertEquals(Arrays.asList(new MediaType("application", "xhtml+xml")), mediaTypes); } @Test - public void resolveMediaTypesFromJaf() { + public void resolveMediaTypesFromJaf() throws Exception { + this.servletRequest.setRequestURI("test.xls"); - PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(); + PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(); List mediaTypes = strategy.resolveMediaTypes(this.webRequest); assertEquals(Arrays.asList(new MediaType("application", "vnd.ms-excel")), mediaTypes); @@ -77,45 +84,67 @@ public class PathExtensionContentNegotiationStrategyTests { // SPR-10334 @Test - public void getMediaTypeFromFilenameNoJaf() { + public void getMediaTypeFromFilenameNoJaf() throws Exception { this.servletRequest.setRequestURI("test.json"); - ServletContext servletContext = this.servletRequest.getServletContext(); - PathExtensionContentNegotiationStrategy strategy = - new ServletPathExtensionContentNegotiationStrategy(servletContext); + ServletContext servletCxt = this.servletRequest.getServletContext(); + PathExtensionContentNegotiationStrategy strategy = new ServletPathExtensionContentNegotiationStrategy(servletCxt); strategy.setUseJaf(false); - List mediaTypes = strategy.resolveMediaTypes(this.webRequest); - assertEquals(Collections.emptyList(), mediaTypes); + assertEquals(Collections.emptyList(), mediaTypes); } // SPR-8678 @Test - public void getMediaTypeFilenameWithContextPath() { - this.servletRequest.setContextPath("/project-1.0.0.M3"); - this.servletRequest.setRequestURI("/project-1.0.0.M3/"); + public void getMediaTypeFilenameWithContextPath() throws Exception { + PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(); + this.servletRequest.setContextPath("/project-1.0.0.M3"); + this.servletRequest.setRequestURI("/project-1.0.0.M3/"); assertTrue("Context path should be excluded", strategy.resolveMediaTypes(webRequest).isEmpty()); this.servletRequest.setRequestURI("/project-1.0.0.M3"); - assertTrue("Context path should be excluded", strategy.resolveMediaTypes(webRequest).isEmpty()); } // SPR-9390 @Test - public void getMediaTypeFilenameWithEncodedURI() { + public void getMediaTypeFilenameWithEncodedURI() throws Exception { + this.servletRequest.setRequestURI("/quo%20vadis%3f.html"); - PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(); + PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(); List result = strategy.resolveMediaTypes(webRequest); assertEquals("Invalid content type", Collections.singletonList(new MediaType("text", "html")), result); } + // SPR-10170 + + @Test + public void resolveMediaTypesIgnoreUnknownExtension() throws Exception { + + this.servletRequest.setRequestURI("test.xyz"); + + PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(); + List mediaTypes = strategy.resolveMediaTypes(this.webRequest); + + assertEquals(Collections.emptyList(), mediaTypes); + } + + @Test(expected = HttpMediaTypeNotAcceptableException.class) + public void resolveMediaTypesDoNotIgnoreUnknownExtension() throws Exception { + + this.servletRequest.setRequestURI("test.xyz"); + + PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(); + strategy.setIgnoreUnknownExtensions(false); + strategy.resolveMediaTypes(this.webRequest); + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java index 7073e7122a3..880681ea6e3 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -94,6 +94,18 @@ public class ContentNegotiationConfigurer { return this; } + /** + * Whether to ignore requests that have a file extension that does not match + * any mapped media types. Setting this to {@code false} will result in a + * {@code HttpMediaTypeNotAcceptableException} when there is no match. + * + *

By default this is set to {@code true}. + */ + public ContentNegotiationConfigurer ignoreUnknownPathExtensions(boolean ignore) { + this.factoryBean.setIgnoreUnknownPathExtensions(ignore); + return this; + } + /** * Indicate whether to use the Java Activation Framework as a fallback option * to map from file extensions to media types. This is used only when