Skip to content

Commit 83ac44d

Browse files
committed
Fix issue with parsing media types
Invalid Content-Type or Accept header values previously resulted in the IllegalArgumentException getting propagated. After this change such errors are detected and generally treated as a "no match", which may for example result in a 406 in the case of the Accept header. Issue: SPR-9142 Backport-Issue: SPR-9148 Backport-Commit: ca8b98e
1 parent 0cf9371 commit 83ac44d

File tree

6 files changed

+103
-31
lines changed

6 files changed

+103
-31
lines changed

build-spring-framework/resources/changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Changes in version 3.1.2 (2012-06-??)
1515
* EhCacheFactoryBean applies listeners and enabled/disabled flags to existing cache regions as well
1616
* ServletContextResource's getFile implementation falls back to getRealPath for non-existent files
1717
* fixed StandardServletMultipartResolver compatibility with Resin (only deleting actual file parts)
18+
* fix issue with parsing invalid Content-Type or Accept headers
1819

1920

2021
Changes in version 3.1.1 (2012-02-16)

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractMediaTypeExpression.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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,19 +18,23 @@
1818

1919
import javax.servlet.http.HttpServletRequest;
2020

21+
import org.apache.commons.logging.Log;
22+
import org.apache.commons.logging.LogFactory;
2123
import org.springframework.http.MediaType;
2224
import org.springframework.web.bind.annotation.RequestMapping;
2325

2426
/**
2527
* Supports media type expressions as described in:
2628
* {@link RequestMapping#consumes()} and {@link RequestMapping#produces()}.
27-
*
29+
*
2830
* @author Arjen Poutsma
2931
* @author Rossen Stoyanchev
3032
* @since 3.1
3133
*/
3234
abstract class AbstractMediaTypeExpression implements Comparable<AbstractMediaTypeExpression>, MediaTypeExpression {
3335

36+
protected final Log logger = LogFactory.getLog(getClass());
37+
3438
private final MediaType mediaType;
3539

3640
private final boolean isNegated;
@@ -60,8 +64,16 @@ public boolean isNegated() {
6064
}
6165

6266
public final boolean match(HttpServletRequest request) {
63-
boolean match = matchMediaType(request);
64-
return !isNegated ? match : !match;
67+
try {
68+
boolean match = matchMediaType(request);
69+
return !isNegated ? match : !match;
70+
}
71+
catch (IllegalArgumentException ex) {
72+
if (logger.isDebugEnabled()) {
73+
logger.debug("Could not parse media type header: " + ex.getMessage());
74+
}
75+
return false;
76+
}
6577
}
6678

6779
protected abstract boolean matchMediaType(HttpServletRequest request);

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

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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,7 @@
4141
import org.springframework.web.servlet.HandlerMapping;
4242

4343
/**
44-
* Extends {@link AbstractMessageConverterMethodArgumentResolver} with the ability to handle method return
44+
* Extends {@link AbstractMessageConverterMethodArgumentResolver} with the ability to handle method return
4545
* values by writing to the response with {@link HttpMessageConverter}s.
4646
*
4747
* @author Arjen Poutsma
@@ -103,7 +103,7 @@ protected <T> void writeWithMessageConverters(T returnValue,
103103

104104
List<MediaType> acceptableMediaTypes = getAcceptableMediaTypes(inputMessage);
105105
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(inputMessage.getServletRequest(), returnValueClass);
106-
106+
107107
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
108108
for (MediaType a : acceptableMediaTypes) {
109109
for (MediaType p : producibleMediaTypes) {
@@ -115,10 +115,10 @@ protected <T> void writeWithMessageConverters(T returnValue,
115115
if (compatibleMediaTypes.isEmpty()) {
116116
throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
117117
}
118-
118+
119119
List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
120120
MediaType.sortBySpecificity(mediaTypes);
121-
121+
122122
MediaType selectedMediaType = null;
123123
for (MediaType mediaType : mediaTypes) {
124124
if (mediaType.isConcrete()) {
@@ -130,7 +130,7 @@ else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICAT
130130
break;
131131
}
132132
}
133-
133+
134134
if (selectedMediaType != null) {
135135
for (HttpMessageConverter<?> messageConverter : messageConverters) {
136136
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
@@ -166,7 +166,7 @@ else if (!allSupportedMediaTypes.isEmpty()) {
166166
if (converter.canWrite(returnValueClass, null)) {
167167
result.addAll(converter.getSupportedMediaTypes());
168168
}
169-
}
169+
}
170170
return result;
171171
}
172172
else {
@@ -175,8 +175,16 @@ else if (!allSupportedMediaTypes.isEmpty()) {
175175
}
176176

177177
private List<MediaType> getAcceptableMediaTypes(HttpInputMessage inputMessage) {
178-
List<MediaType> result = inputMessage.getHeaders().getAccept();
179-
return result.isEmpty() ? Collections.singletonList(MediaType.ALL) : result;
178+
try {
179+
List<MediaType> result = inputMessage.getHeaders().getAccept();
180+
return result.isEmpty() ? Collections.singletonList(MediaType.ALL) : result;
181+
}
182+
catch (IllegalArgumentException ex) {
183+
if (logger.isDebugEnabled()) {
184+
logger.debug("Could not parse Accept header: " + ex.getMessage());
185+
}
186+
return Collections.emptyList();
187+
}
180188
}
181189

182190
/**

org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestConditionTests.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -44,7 +44,7 @@ public void consumesMatch() {
4444

4545
assertNotNull(condition.getMatchingCondition(request));
4646
}
47-
47+
4848
@Test
4949
public void negatedConsumesMatch() {
5050
ConsumesRequestCondition condition = new ConsumesRequestCondition("!text/plain");
@@ -60,7 +60,7 @@ public void getConsumableMediaTypesNegatedExpression() {
6060
ConsumesRequestCondition condition = new ConsumesRequestCondition("!application/xml");
6161
assertEquals(Collections.emptySet(), condition.getConsumableMediaTypes());
6262
}
63-
63+
6464
@Test
6565
public void consumesWildcardMatch() {
6666
ConsumesRequestCondition condition = new ConsumesRequestCondition("text/*");
@@ -91,6 +91,26 @@ public void consumesSingleNoMatch() {
9191
assertNull(condition.getMatchingCondition(request));
9292
}
9393

94+
@Test
95+
public void consumesParseError() {
96+
ConsumesRequestCondition condition = new ConsumesRequestCondition("text/plain");
97+
98+
MockHttpServletRequest request = new MockHttpServletRequest();
99+
request.setContentType("01");
100+
101+
assertNull(condition.getMatchingCondition(request));
102+
}
103+
104+
@Test
105+
public void consumesParseErrorWithNegation() {
106+
ConsumesRequestCondition condition = new ConsumesRequestCondition("!text/plain");
107+
108+
MockHttpServletRequest request = new MockHttpServletRequest();
109+
request.setContentType("01");
110+
111+
assertNull(condition.getMatchingCondition(request));
112+
}
113+
94114
@Test
95115
public void compareToSingle() {
96116
MockHttpServletRequest request = new MockHttpServletRequest();
@@ -128,7 +148,7 @@ public void combine() {
128148
ConsumesRequestCondition result = condition1.combine(condition2);
129149
assertEquals(condition2, result);
130150
}
131-
151+
132152
@Test
133153
public void combineWithDefault() {
134154
ConsumesRequestCondition condition1 = new ConsumesRequestCondition("text/plain");

org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/condition/ProducesRequestConditionTests.java

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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,7 @@
3434
* @author Rossen Stoyanchev
3535
*/
3636
public class ProducesRequestConditionTests {
37-
37+
3838
@Test
3939
public void producesMatch() {
4040
ProducesRequestCondition condition = new ProducesRequestCondition("text/plain");
@@ -44,7 +44,7 @@ public void producesMatch() {
4444

4545
assertNotNull(condition.getMatchingCondition(request));
4646
}
47-
47+
4848
@Test
4949
public void negatedProducesMatch() {
5050
ProducesRequestCondition condition = new ProducesRequestCondition("!text/plain");
@@ -60,7 +60,7 @@ public void getProducibleMediaTypesNegatedExpression() {
6060
ProducesRequestCondition condition = new ProducesRequestCondition("!application/xml");
6161
assertEquals(Collections.emptySet(), condition.getProducibleMediaTypes());
6262
}
63-
63+
6464
@Test
6565
public void producesWildcardMatch() {
6666
ProducesRequestCondition condition = new ProducesRequestCondition("text/*");
@@ -91,6 +91,26 @@ public void producesSingleNoMatch() {
9191
assertNull(condition.getMatchingCondition(request));
9292
}
9393

94+
@Test
95+
public void producesParseError() {
96+
ProducesRequestCondition condition = new ProducesRequestCondition("text/plain");
97+
98+
MockHttpServletRequest request = new MockHttpServletRequest();
99+
request.addHeader("Accept", "bogus");
100+
101+
assertNull(condition.getMatchingCondition(request));
102+
}
103+
104+
@Test
105+
public void producesParseErrorWithNegation() {
106+
ProducesRequestCondition condition = new ProducesRequestCondition("!text/plain");
107+
108+
MockHttpServletRequest request = new MockHttpServletRequest();
109+
request.addHeader("Accept", "bogus");
110+
111+
assertNull(condition.getMatchingCondition(request));
112+
}
113+
94114
@Test
95115
public void compareTo() {
96116
ProducesRequestCondition html = new ProducesRequestCondition("text/html");
@@ -126,12 +146,12 @@ public void compareTo() {
126146
assertTrue(html.compareTo(xml, request) > 0);
127147
assertTrue(xml.compareTo(html, request) < 0);
128148
}
129-
149+
130150
@Test
131151
public void compareToWithSingleExpression() {
132152
MockHttpServletRequest request = new MockHttpServletRequest();
133153
request.addHeader("Accept", "text/plain");
134-
154+
135155
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/plain");
136156
ProducesRequestCondition condition2 = new ProducesRequestCondition("text/*");
137157

@@ -188,7 +208,7 @@ public void compareToMultipleExpressionsAndMultipeAcceptHeaderValues() {
188208
@Test
189209
public void compareToMediaTypeAll() {
190210
MockHttpServletRequest request = new MockHttpServletRequest();
191-
211+
192212
ProducesRequestCondition condition1 = new ProducesRequestCondition();
193213
ProducesRequestCondition condition2 = new ProducesRequestCondition("application/json");
194214

@@ -202,7 +222,7 @@ public void compareToMediaTypeAll() {
202222

203223
assertTrue(condition1.compareTo(condition2, request) < 0);
204224
assertTrue(condition2.compareTo(condition1, request) > 0);
205-
225+
206226
request.addHeader("Accept", "*/*");
207227

208228
condition1 = new ProducesRequestCondition();
@@ -255,7 +275,7 @@ public void combine() {
255275
ProducesRequestCondition result = condition1.combine(condition2);
256276
assertEquals(condition2, result);
257277
}
258-
278+
259279
@Test
260280
public void combineWithDefault() {
261281
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/plain");

org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -56,7 +56,7 @@
5656

5757
/**
5858
* Test fixture with {@link HttpEntityMethodProcessor} and mock {@link HttpMessageConverter}.
59-
*
59+
*
6060
* @author Arjen Poutsma
6161
* @author Rossen Stoyanchev
6262
*/
@@ -106,7 +106,7 @@ public void setUp() throws Exception {
106106
returnTypeResponseEntityProduces = new MethodParameter(getClass().getMethod("handle4"), -1);
107107

108108
mavContainer = new ModelAndViewContainer();
109-
109+
110110
servletRequest = new MockHttpServletRequest();
111111
servletResponse = new MockHttpServletResponse();
112112
webRequest = new ServletWebRequest(servletRequest, servletResponse);
@@ -219,7 +219,7 @@ public void handleReturnValueNotAcceptable() throws Exception {
219219

220220
fail("Expected exception");
221221
}
222-
222+
223223
@Test(expected = HttpMediaTypeNotAcceptableException.class)
224224
public void handleReturnValueNotAcceptableProduces() throws Exception {
225225
String body = "Foo";
@@ -238,6 +238,17 @@ public void handleReturnValueNotAcceptableProduces() throws Exception {
238238
fail("Expected exception");
239239
}
240240

241+
// SPR-9142
242+
243+
@Test(expected=HttpMediaTypeNotAcceptableException.class)
244+
public void handleReturnValueNotAcceptableParseError() throws Exception {
245+
ResponseEntity<String> returnValue = new ResponseEntity<String>("Body", HttpStatus.ACCEPTED);
246+
servletRequest.addHeader("Accept", "01");
247+
248+
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
249+
fail("Expected exception");
250+
}
251+
241252
@Test
242253
public void responseHeaderNoBody() throws Exception {
243254
HttpHeaders headers = new HttpHeaders();
@@ -269,7 +280,7 @@ public void responseHeaderAndBody() throws Exception {
269280
assertEquals("headerValue", outputMessage.getValue().getHeaders().get("header").get(0));
270281
verify(messageConverter);
271282
}
272-
283+
273284
public ResponseEntity<String> handle1(HttpEntity<String> httpEntity, ResponseEntity<String> responseEntity, int i) {
274285
return responseEntity;
275286
}

0 commit comments

Comments
 (0)