Skip to content

Commit 4facb2f

Browse files
committed
ContentCachingResponseWrapper defensively applies content length in case of sendError/sendRedirect
Issue: SPR-13004
1 parent 49aabd5 commit 4facb2f

File tree

2 files changed

+59
-18
lines changed

2 files changed

+59
-18
lines changed

spring-web/src/main/java/org/springframework/web/util/ContentCachingResponseWrapper.java

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
4848

4949
private int statusCode = HttpServletResponse.SC_OK;
5050

51+
private Integer contentLength;
52+
5153

5254
/**
5355
* Create a new ContentCachingResponseWrapper for the given servlet response.
@@ -73,21 +75,34 @@ public void setStatus(int sc, String sm) {
7375

7476
@Override
7577
public void sendError(int sc) throws IOException {
76-
copyBodyToResponse();
77-
super.sendError(sc);
78+
copyBodyToResponse(false);
79+
try {
80+
super.sendError(sc);
81+
}
82+
catch (IllegalStateException ex) {
83+
// Possibly on Tomcat when called too late: fall back to silent setStatus
84+
super.setStatus(sc);
85+
}
7886
this.statusCode = sc;
7987
}
8088

8189
@Override
90+
@SuppressWarnings("deprecation")
8291
public void sendError(int sc, String msg) throws IOException {
83-
copyBodyToResponse();
84-
super.sendError(sc, msg);
92+
copyBodyToResponse(false);
93+
try {
94+
super.sendError(sc, msg);
95+
}
96+
catch (IllegalStateException ex) {
97+
// Possibly on Tomcat when called too late: fall back to silent setStatus
98+
super.setStatus(sc, msg);
99+
}
85100
this.statusCode = sc;
86101
}
87102

88103
@Override
89104
public void sendRedirect(String location) throws IOException {
90-
copyBodyToResponse();
105+
copyBodyToResponse(false);
91106
super.sendRedirect(location);
92107
}
93108

@@ -109,6 +124,7 @@ public PrintWriter getWriter() throws IOException {
109124
@Override
110125
public void setContentLength(int len) {
111126
this.content.resize(len);
127+
this.contentLength = len;
112128
}
113129

114130
// Overrides Servlet 3.1 setContentLengthLong(long) at runtime
@@ -117,7 +133,9 @@ public void setContentLengthLong(long len) {
117133
throw new IllegalArgumentException("Content-Length exceeds ShallowEtagHeaderFilter's maximum (" +
118134
Integer.MAX_VALUE + "): " + len);
119135
}
120-
this.content.resize((int) len);
136+
int lenInt = (int) len;
137+
this.content.resize(lenInt);
138+
this.contentLength = lenInt;
121139
}
122140

123141
@Override
@@ -150,24 +168,44 @@ public byte[] getContentAsByteArray() {
150168
return this.content.toByteArray();
151169
}
152170

171+
/**
172+
* Return an {@link InputStream} to the cached content.
173+
*/
174+
public InputStream getContentInputStream(){
175+
return this.content.getInputStream();
176+
}
177+
178+
/**
179+
* Return the current size of the cached content.
180+
*/
181+
public int getContentSize(){
182+
return this.content.size();
183+
}
184+
185+
/**
186+
* Copy the complete cached body content to the response.
187+
*/
153188
public void copyBodyToResponse() throws IOException {
189+
copyBodyToResponse(true);
190+
}
191+
192+
/**
193+
* Copy the cached body content to the response.
194+
* @param complete whether to set a corresponding content length
195+
* for the complete cached body content
196+
*/
197+
protected void copyBodyToResponse(boolean complete) throws IOException {
154198
if (this.content.size() > 0) {
155199
HttpServletResponse rawResponse = (HttpServletResponse) getResponse();
156-
if(! rawResponse.isCommitted()){
157-
rawResponse.setContentLength(this.content.size());
200+
if ((complete || this.contentLength != null) && !rawResponse.isCommitted()){
201+
rawResponse.setContentLength(complete ? this.content.size() : this.contentLength);
202+
this.contentLength = null;
158203
}
159204
this.content.writeTo(rawResponse.getOutputStream());
160205
this.content.reset();
161206
}
162207
}
163208

164-
public int getContentSize(){
165-
return this.content.size();
166-
}
167-
168-
public InputStream getContentInputStream(){
169-
return this.content.getInputStream();
170-
}
171209

172210
private class ResponseServletOutputStream extends ServletOutputStream {
173211

spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,14 +150,15 @@ public void filterSendError() throws Exception {
150150
final byte[] responseBody = "Hello World".getBytes("UTF-8");
151151
FilterChain filterChain = (filterRequest, filterResponse) -> {
152152
assertEquals("Invalid request passed", request, filterRequest);
153+
response.setContentLength(100);
153154
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
154155
((HttpServletResponse) filterResponse).sendError(HttpServletResponse.SC_FORBIDDEN);
155156
};
156157
filter.doFilter(request, response, filterChain);
157158

158159
assertEquals("Invalid status", 403, response.getStatus());
159160
assertNull("Invalid ETag header", response.getHeader("ETag"));
160-
assertTrue("Invalid Content-Length header", response.getContentLength() > 0);
161+
assertEquals("Invalid Content-Length header", 100, response.getContentLength());
161162
assertArrayEquals("Invalid content", responseBody, response.getContentAsByteArray());
162163
}
163164

@@ -169,14 +170,15 @@ public void filterSendErrorMessage() throws Exception {
169170
final byte[] responseBody = "Hello World".getBytes("UTF-8");
170171
FilterChain filterChain = (filterRequest, filterResponse) -> {
171172
assertEquals("Invalid request passed", request, filterRequest);
173+
response.setContentLength(100);
172174
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
173175
((HttpServletResponse) filterResponse).sendError(HttpServletResponse.SC_FORBIDDEN, "ERROR");
174176
};
175177
filter.doFilter(request, response, filterChain);
176178

177179
assertEquals("Invalid status", 403, response.getStatus());
178180
assertNull("Invalid ETag header", response.getHeader("ETag"));
179-
assertTrue("Invalid Content-Length header", response.getContentLength() > 0);
181+
assertEquals("Invalid Content-Length header", 100, response.getContentLength());
180182
assertArrayEquals("Invalid content", responseBody, response.getContentAsByteArray());
181183
assertEquals("Invalid error message", "ERROR", response.getErrorMessage());
182184
}
@@ -189,14 +191,15 @@ public void filterSendRedirect() throws Exception {
189191
final byte[] responseBody = "Hello World".getBytes("UTF-8");
190192
FilterChain filterChain = (filterRequest, filterResponse) -> {
191193
assertEquals("Invalid request passed", request, filterRequest);
194+
response.setContentLength(100);
192195
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
193196
((HttpServletResponse) filterResponse).sendRedirect("http://www.google.com");
194197
};
195198
filter.doFilter(request, response, filterChain);
196199

197200
assertEquals("Invalid status", 302, response.getStatus());
198201
assertNull("Invalid ETag header", response.getHeader("ETag"));
199-
assertTrue("Invalid Content-Length header", response.getContentLength() > 0);
202+
assertEquals("Invalid Content-Length header", 100, response.getContentLength());
200203
assertArrayEquals("Invalid content", responseBody, response.getContentAsByteArray());
201204
assertEquals("Invalid redirect URL", "http://www.google.com", response.getRedirectedUrl());
202205
}

0 commit comments

Comments
 (0)