Skip to content

Commit b359710

Browse files
author
Dave Syer
committed
More careful masking for the HTTP status in non-error cases
The ErrorPageFilter exposes a wrapped response to the downstream chain and unless more care is taken the chain will be able to set the response status, but not inspect it. Fixes gh-2367
1 parent a51ad5c commit b359710

File tree

3 files changed

+205
-4
lines changed

3 files changed

+205
-4
lines changed

spring-boot/src/main/java/org/springframework/boot/context/web/ErrorPageFilter.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,11 +272,10 @@ private static class ErrorWrapperResponse extends HttpServletResponseWrapper {
272272

273273
private String message;
274274

275-
private boolean errorToSend;
275+
private boolean errorToSend = false;
276276

277277
public ErrorWrapperResponse(HttpServletResponse response) {
278278
super(response);
279-
this.status = response.getStatus();
280279
}
281280

282281
@Override
@@ -288,13 +287,20 @@ public void sendError(int status) throws IOException {
288287
public void sendError(int status, String message) throws IOException {
289288
this.status = status;
290289
this.message = message;
291-
292290
this.errorToSend = true;
291+
// Do not call super because the container may prevent us from handling the
292+
// error ourselves
293293
}
294294

295295
@Override
296296
public int getStatus() {
297-
return this.status;
297+
if (this.errorToSend) {
298+
return this.status;
299+
}
300+
else {
301+
// If there was no error we need to trust the wrapped response
302+
return super.getStatus();
303+
}
298304
}
299305

300306
@Override
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
* Copyright 2012-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.context.web;
18+
19+
import java.net.URI;
20+
import java.util.concurrent.CountDownLatch;
21+
import java.util.concurrent.TimeUnit;
22+
23+
import javax.servlet.http.HttpServletRequest;
24+
import javax.servlet.http.HttpServletResponse;
25+
26+
import org.junit.After;
27+
import org.junit.Test;
28+
import org.junit.runner.RunWith;
29+
import org.springframework.beans.factory.annotation.Autowired;
30+
import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
31+
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
32+
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
33+
import org.springframework.boot.context.web.ErrorPageFilterIntegrationTests.TomcatConfig;
34+
import org.springframework.boot.test.IntegrationTest;
35+
import org.springframework.boot.test.SpringApplicationConfiguration;
36+
import org.springframework.boot.test.TestRestTemplate;
37+
import org.springframework.context.annotation.Bean;
38+
import org.springframework.context.annotation.Configuration;
39+
import org.springframework.http.HttpStatus;
40+
import org.springframework.http.ResponseEntity;
41+
import org.springframework.stereotype.Controller;
42+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
43+
import org.springframework.test.context.web.WebAppConfiguration;
44+
import org.springframework.web.bind.annotation.RequestMapping;
45+
import org.springframework.web.bind.annotation.ResponseBody;
46+
import org.springframework.web.bind.annotation.ResponseStatus;
47+
import org.springframework.web.servlet.DispatcherServlet;
48+
import org.springframework.web.servlet.ModelAndView;
49+
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
50+
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
51+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
52+
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
53+
54+
import static org.hamcrest.Matchers.equalTo;
55+
import static org.junit.Assert.assertThat;
56+
57+
/**
58+
*
59+
* @author Dave Syer
60+
*/
61+
@RunWith(SpringJUnit4ClassRunner.class)
62+
@SpringApplicationConfiguration(classes = TomcatConfig.class)
63+
@IntegrationTest
64+
@WebAppConfiguration
65+
public class ErrorPageFilterIntegrationTests {
66+
67+
@Autowired
68+
private HelloWorldController controller;
69+
70+
@Autowired
71+
private AnnotationConfigEmbeddedWebApplicationContext context;
72+
73+
@After
74+
public void init() {
75+
this.controller.reset();
76+
}
77+
78+
@Test
79+
public void created() throws Exception {
80+
doTest(this.context, "/create", HttpStatus.CREATED);
81+
assertThat(this.controller.getStatus(), equalTo(201));
82+
}
83+
84+
@Test
85+
public void ok() throws Exception {
86+
doTest(this.context, "/hello", HttpStatus.OK);
87+
assertThat(this.controller.getStatus(), equalTo(200));
88+
}
89+
90+
private void doTest(AnnotationConfigEmbeddedWebApplicationContext context,
91+
String resourcePath, HttpStatus status) throws Exception {
92+
int port = context.getEmbeddedServletContainer().getPort();
93+
TestRestTemplate template = new TestRestTemplate();
94+
ResponseEntity<String> entity = template.getForEntity(new URI("http://localhost:"
95+
+ port + resourcePath), String.class);
96+
assertThat(entity.getBody(), equalTo("Hello World"));
97+
assertThat(entity.getStatusCode(), equalTo(status));
98+
}
99+
100+
@Configuration
101+
@EnableWebMvc
102+
public static class TomcatConfig {
103+
104+
@Bean
105+
public EmbeddedServletContainerFactory containerFactory() {
106+
return new TomcatEmbeddedServletContainerFactory(0);
107+
}
108+
109+
@Bean
110+
public ErrorPageFilter errorPageFilter() {
111+
return new ErrorPageFilter();
112+
}
113+
114+
@Bean
115+
public DispatcherServlet dispatcherServlet() {
116+
return new DispatcherServlet();
117+
}
118+
119+
@Bean
120+
public HelloWorldController helloWorldController() {
121+
return new HelloWorldController();
122+
}
123+
}
124+
125+
@Controller
126+
public static class HelloWorldController extends WebMvcConfigurerAdapter {
127+
128+
private int status;
129+
130+
private CountDownLatch latch = new CountDownLatch(1);
131+
132+
public int getStatus() throws InterruptedException {
133+
assertThat("Timed out waiting for latch",
134+
this.latch.await(1, TimeUnit.SECONDS), equalTo(true));
135+
return this.status;
136+
}
137+
138+
public void setStatus(int status) {
139+
this.status = status;
140+
}
141+
142+
public void reset() {
143+
this.status = 0;
144+
this.latch = new CountDownLatch(1);
145+
}
146+
147+
@Override
148+
public void addInterceptors(InterceptorRegistry registry) {
149+
registry.addInterceptor(new HandlerInterceptorAdapter() {
150+
@Override
151+
public void postHandle(HttpServletRequest request,
152+
HttpServletResponse response, Object handler,
153+
ModelAndView modelAndView) throws Exception {
154+
HelloWorldController.this.setStatus(response.getStatus());
155+
HelloWorldController.this.latch.countDown();
156+
}
157+
});
158+
}
159+
160+
@RequestMapping("/hello")
161+
@ResponseBody
162+
public String sayHello() {
163+
return "Hello World";
164+
}
165+
166+
@RequestMapping("/create")
167+
@ResponseBody
168+
@ResponseStatus(HttpStatus.CREATED)
169+
public String created() {
170+
return "Hello World";
171+
}
172+
173+
}
174+
175+
}

spring-boot/src/test/java/org/springframework/boot/context/web/ErrorPageFilterTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,26 @@ public void notAnError() throws Exception {
7777
assertThat(this.response.getForwardedUrl(), is(nullValue()));
7878
}
7979

80+
@Test
81+
public void notAnErrorButNotOK() throws Exception {
82+
this.chain = new MockFilterChain() {
83+
@Override
84+
public void doFilter(ServletRequest request, ServletResponse response)
85+
throws IOException, ServletException {
86+
((HttpServletResponse) response).setStatus(201);
87+
super.doFilter(request, response);
88+
response.flushBuffer();
89+
}
90+
};
91+
this.filter.doFilter(this.request, this.response, this.chain);
92+
assertThat(((HttpServletResponse) this.chain.getResponse()).getStatus(),
93+
equalTo(201));
94+
assertThat(
95+
((HttpServletResponse) ((HttpServletResponseWrapper) this.chain.getResponse())
96+
.getResponse()).getStatus(), equalTo(201));
97+
assertTrue(this.response.isCommitted());
98+
}
99+
80100
@Test
81101
public void unauthorizedWithErrorPath() throws Exception {
82102
this.filter.addErrorPages(new ErrorPage("/error"));

0 commit comments

Comments
 (0)