Merge pull request #3759 from rwinch/gh-2953

Cache Control only written if not set
This commit is contained in:
Rob Winch
2016-03-15 13:03:58 -05:00
7 changed files with 407 additions and 152 deletions

View File

@@ -17,10 +17,9 @@ package org.springframework.security.web.context;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.OnCommittedResponseWrapper;
/** /**
* Base class for response wrappers which encapsulate the logic for storing a security * Base class for response wrappers which encapsulate the logic for storing a security
@@ -40,10 +39,8 @@ import org.springframework.security.core.context.SecurityContextHolder;
* @author Rob Winch * @author Rob Winch
* @since 3.0 * @since 3.0
*/ */
public abstract class SaveContextOnUpdateOrErrorResponseWrapper extends public abstract class SaveContextOnUpdateOrErrorResponseWrapper
OnCommittedResponseWrapper { extends OnCommittedResponseWrapper {
private final Log logger = LogFactory.getLog(getClass());
private boolean contextSaved = false; private boolean contextSaved = false;
/* See SEC-1052 */ /* See SEC-1052 */
@@ -86,12 +83,12 @@ public abstract class SaveContextOnUpdateOrErrorResponseWrapper extends
@Override @Override
protected void onResponseCommitted() { protected void onResponseCommitted() {
saveContext(SecurityContextHolder.getContext()); saveContext(SecurityContextHolder.getContext());
contextSaved = true; this.contextSaved = true;
} }
@Override @Override
public final String encodeRedirectUrl(String url) { public final String encodeRedirectUrl(String url) {
if (disableUrlRewriting) { if (this.disableUrlRewriting) {
return url; return url;
} }
return super.encodeRedirectUrl(url); return super.encodeRedirectUrl(url);
@@ -99,7 +96,7 @@ public abstract class SaveContextOnUpdateOrErrorResponseWrapper extends
@Override @Override
public final String encodeRedirectURL(String url) { public final String encodeRedirectURL(String url) {
if (disableUrlRewriting) { if (this.disableUrlRewriting) {
return url; return url;
} }
return super.encodeRedirectURL(url); return super.encodeRedirectURL(url);
@@ -107,7 +104,7 @@ public abstract class SaveContextOnUpdateOrErrorResponseWrapper extends
@Override @Override
public final String encodeUrl(String url) { public final String encodeUrl(String url) {
if (disableUrlRewriting) { if (this.disableUrlRewriting) {
return url; return url;
} }
return super.encodeUrl(url); return super.encodeUrl(url);
@@ -115,7 +112,7 @@ public abstract class SaveContextOnUpdateOrErrorResponseWrapper extends
@Override @Override
public final String encodeURL(String url) { public final String encodeURL(String url) {
if (disableUrlRewriting) { if (this.disableUrlRewriting) {
return url; return url;
} }
return super.encodeURL(url); return super.encodeURL(url);
@@ -126,6 +123,6 @@ public abstract class SaveContextOnUpdateOrErrorResponseWrapper extends
* wrapper. * wrapper.
*/ */
public final boolean isContextSaved() { public final boolean isContextSaved() {
return contextSaved; return this.contextSaved;
} }
} }

View File

@@ -15,15 +15,17 @@
*/ */
package org.springframework.security.web.header; package org.springframework.security.web.header;
import org.springframework.util.Assert; import java.io.IOException;
import org.springframework.web.filter.OncePerRequestFilter; import java.util.List;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*; import org.springframework.security.web.util.OnCommittedResponseWrapper;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;
/** /**
* Filter implementation to add headers to the current request. Can be useful to add * Filter implementation to add headers to the current request. Can be useful to add
@@ -56,12 +58,52 @@ public class HeaderWriterFilter extends OncePerRequestFilter {
@Override @Override
protected void doFilterInternal(HttpServletRequest request, protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException { throws ServletException, IOException {
for (HeaderWriter headerWriter : headerWriters) { HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request,
headerWriter.writeHeaders(request, response); response, this.headerWriters);
try {
filterChain.doFilter(request, headerWriterResponse);
}
finally {
headerWriterResponse.writeHeaders();
} }
filterChain.doFilter(request, response);
} }
static class HeaderWriterResponse extends OnCommittedResponseWrapper {
private final HttpServletRequest request;
private final List<HeaderWriter> headerWriters;
HeaderWriterResponse(HttpServletRequest request, HttpServletResponse response,
List<HeaderWriter> headerWriters) {
super(response);
this.request = request;
this.headerWriters = headerWriters;
}
/*
* (non-Javadoc)
*
* @see org.springframework.security.web.util.OnCommittedResponseWrapper#
* onResponseCommitted()
*/
@Override
protected void onResponseCommitted() {
writeHeaders();
this.disableOnResponseCommitted();
}
protected void writeHeaders() {
if (isDisableOnResponseCommitted()) {
return;
}
for (HeaderWriter headerWriter : this.headerWriters) {
headerWriter.writeHeaders(this.request, getHttpResponse());
}
}
private HttpServletResponse getHttpResponse() {
return (HttpServletResponse) getResponse();
}
}
} }

View File

@@ -15,14 +15,20 @@
*/ */
package org.springframework.security.web.header.writers; package org.springframework.security.web.header.writers;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.web.header.Header; import org.springframework.security.web.header.Header;
import org.springframework.security.web.header.HeaderWriter;
import org.springframework.util.ReflectionUtils;
/** /**
* A {@link StaticHeadersWriter} that inserts headers to prevent caching. Specifically it * Inserts headers to prevent caching if no cache control headers have been specified.
* adds the following headers: * Specifically it adds the following headers:
* <ul> * <ul>
* <li>Cache-Control: no-cache, no-store, max-age=0, must-revalidate</li> * <li>Cache-Control: no-cache, no-store, max-age=0, must-revalidate</li>
* <li>Pragma: no-cache</li> * <li>Pragma: no-cache</li>
@@ -32,21 +38,47 @@ import org.springframework.security.web.header.Header;
* @author Rob Winch * @author Rob Winch
* @since 3.2 * @since 3.2
*/ */
public final class CacheControlHeadersWriter extends StaticHeadersWriter { public final class CacheControlHeadersWriter implements HeaderWriter {
private static final String EXPIRES = "Expires";
private static final String PRAGMA = "Pragma";
private static final String CACHE_CONTROL = "Cache-Control";
private final Method getHeaderMethod;
private final HeaderWriter delegate;
/** /**
* Creates a new instance * Creates a new instance
*/ */
public CacheControlHeadersWriter() { public CacheControlHeadersWriter() {
super(createHeaders()); this.delegate = new StaticHeadersWriter(createHeaders());
this.getHeaderMethod = ReflectionUtils.findMethod(HttpServletResponse.class,
"getHeader", String.class);
}
@Override
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
if (hasHeader(response, CACHE_CONTROL) || hasHeader(response, EXPIRES)
|| hasHeader(response, PRAGMA)) {
return;
}
this.delegate.writeHeaders(request, response);
}
private boolean hasHeader(HttpServletResponse response, String headerName) {
if (this.getHeaderMethod == null) {
return false;
}
return ReflectionUtils.invokeMethod(this.getHeaderMethod, response,
headerName) != null;
} }
private static List<Header> createHeaders() { private static List<Header> createHeaders() {
List<Header> headers = new ArrayList<Header>(2); List<Header> headers = new ArrayList<Header>(2);
headers.add(new Header("Cache-Control", headers.add(new Header(CACHE_CONTROL,
"no-cache, no-store, max-age=0, must-revalidate")); "no-cache, no-store, max-age=0, must-revalidate"));
headers.add(new Header("Pragma", "no-cache")); headers.add(new Header(PRAGMA, "no-cache"));
headers.add(new Header("Expires", "0")); headers.add(new Header(EXPIRES, "0"));
return headers; return headers;
} }
} }

View File

@@ -13,33 +13,31 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.security.web.context; package org.springframework.security.web.util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Locale; import java.util.Locale;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
/** /**
* Base class for response wrappers which encapsulate the logic for handling an event when the * Base class for response wrappers which encapsulate the logic for handling an event when
* {@link javax.servlet.http.HttpServletResponse} is committed. * the {@link javax.servlet.http.HttpServletResponse} is committed.
* *
* @since 4.0.2 * @since 4.0.2
* @author Rob Winch * @author Rob Winch
*/ */
abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper { public abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
private final Log logger = LogFactory.getLog(getClass());
private boolean disableOnCommitted; private boolean disableOnCommitted;
/** /**
* The Content-Length response header. If this is greater than 0, then once {@link #contentWritten} is larger than * The Content-Length response header. If this is greater than 0, then once
* or equal the response is considered committed. * {@link #contentWritten} is larger than or equal the response is considered
* committed.
*/ */
private long contentLength; private long contentLength;
@@ -57,7 +55,7 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
@Override @Override
public void addHeader(String name, String value) { public void addHeader(String name, String value) {
if("Content-Length".equalsIgnoreCase(name)) { if ("Content-Length".equalsIgnoreCase(name)) {
setContentLength(Long.parseLong(value)); setContentLength(Long.parseLong(value));
} }
super.addHeader(name, value); super.addHeader(name, value);
@@ -75,22 +73,33 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
} }
/** /**
* Invoke this method to disable invoking {@link OnCommittedResponseWrapper#onResponseCommitted()} when the {@link javax.servlet.http.HttpServletResponse} is * Invoke this method to disable invoking
* committed. This can be useful in the event that Async Web Requests are * {@link OnCommittedResponseWrapper#onResponseCommitted()} when the
* made. * {@link javax.servlet.http.HttpServletResponse} is committed. This can be useful in
* the event that Async Web Requests are made.
*/ */
public void disableOnResponseCommitted() { protected void disableOnResponseCommitted() {
this.disableOnCommitted = true; this.disableOnCommitted = true;
} }
/** /**
* Implement the logic for handling the {@link javax.servlet.http.HttpServletResponse} being committed * Returns true if {@link #onResponseCommitted()} will be invoked when the response is
* committed, else false.
* @return if {@link #onResponseCommitted()} is enabled
*/
protected boolean isDisableOnResponseCommitted() {
return this.disableOnCommitted;
}
/**
* Implement the logic for handling the {@link javax.servlet.http.HttpServletResponse}
* being committed
*/ */
protected abstract void onResponseCommitted(); protected abstract void onResponseCommitted();
/** /**
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before calling the * Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
* superclass <code>sendError()</code> * before calling the superclass <code>sendError()</code>
*/ */
@Override @Override
public final void sendError(int sc) throws IOException { public final void sendError(int sc) throws IOException {
@@ -99,8 +108,8 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
} }
/** /**
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before calling the * Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
* superclass <code>sendError()</code> * before calling the superclass <code>sendError()</code>
*/ */
@Override @Override
public final void sendError(int sc, String msg) throws IOException { public final void sendError(int sc, String msg) throws IOException {
@@ -109,8 +118,8 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
} }
/** /**
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before calling the * Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
* superclass <code>sendRedirect()</code> * before calling the superclass <code>sendRedirect()</code>
*/ */
@Override @Override
public final void sendRedirect(String location) throws IOException { public final void sendRedirect(String location) throws IOException {
@@ -119,8 +128,9 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
} }
/** /**
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before calling the calling * Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
* <code>getOutputStream().close()</code> or <code>getOutputStream().flush()</code> * before calling the calling <code>getOutputStream().close()</code> or
* <code>getOutputStream().flush()</code>
*/ */
@Override @Override
public ServletOutputStream getOutputStream() throws IOException { public ServletOutputStream getOutputStream() throws IOException {
@@ -128,8 +138,9 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
} }
/** /**
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before calling the * Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
* <code>getWriter().close()</code> or <code>getWriter().flush()</code> * before calling the <code>getWriter().close()</code> or
* <code>getWriter().flush()</code>
*/ */
@Override @Override
public PrintWriter getWriter() throws IOException { public PrintWriter getWriter() throws IOException {
@@ -137,8 +148,8 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
} }
/** /**
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before calling the * Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
* superclass <code>flushBuffer()</code> * before calling the superclass <code>flushBuffer()</code>
*/ */
@Override @Override
public void flushBuffer() throws IOException { public void flushBuffer() throws IOException {
@@ -187,36 +198,38 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
} }
/** /**
* Adds the contentLengthToWrite to the total contentWritten size and checks to see if the response should be * Adds the contentLengthToWrite to the total contentWritten size and checks to see if
* written. * the response should be written.
* *
* @param contentLengthToWrite the size of the content that is about to be written. * @param contentLengthToWrite the size of the content that is about to be written.
*/ */
private void checkContentLength(long contentLengthToWrite) { private void checkContentLength(long contentLengthToWrite) {
contentWritten += contentLengthToWrite; this.contentWritten += contentLengthToWrite;
boolean isBodyFullyWritten = contentLength > 0 && contentWritten >= contentLength; boolean isBodyFullyWritten = this.contentLength > 0
&& this.contentWritten >= this.contentLength;
int bufferSize = getBufferSize(); int bufferSize = getBufferSize();
boolean requiresFlush = bufferSize > 0 && contentWritten >= bufferSize; boolean requiresFlush = bufferSize > 0 && this.contentWritten >= bufferSize;
if(isBodyFullyWritten || requiresFlush) { if (isBodyFullyWritten || requiresFlush) {
doOnResponseCommitted(); doOnResponseCommitted();
} }
} }
/** /**
* Calls <code>onResponseCommmitted()</code> with the current contents as long as * Calls <code>onResponseCommmitted()</code> with the current contents as long as
* {@link #disableOnResponseCommitted()()} was not invoked. * {@link #disableOnResponseCommitted()} was not invoked.
*/ */
private void doOnResponseCommitted() { private void doOnResponseCommitted() {
if(!disableOnCommitted) { if (!this.disableOnCommitted) {
onResponseCommitted(); onResponseCommitted();
disableOnResponseCommitted(); disableOnResponseCommitted();
} }
} }
/** /**
* Ensures {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before calling the prior to methods that commit the response. We delegate all methods * Ensures {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before
* to the original {@link java.io.PrintWriter} to ensure that the behavior is as close to the original {@link java.io.PrintWriter} * calling the prior to methods that commit the response. We delegate all methods to
* as possible. See SEC-2039 * the original {@link java.io.PrintWriter} to ensure that the behavior is as close to
* the original {@link java.io.PrintWriter} as possible. See SEC-2039
* @author Rob Winch * @author Rob Winch
*/ */
private class SaveContextPrintWriter extends PrintWriter { private class SaveContextPrintWriter extends PrintWriter {
@@ -227,197 +240,235 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
this.delegate = delegate; this.delegate = delegate;
} }
@Override
public void flush() { public void flush() {
doOnResponseCommitted(); doOnResponseCommitted();
delegate.flush(); this.delegate.flush();
} }
@Override
public void close() { public void close() {
doOnResponseCommitted(); doOnResponseCommitted();
delegate.close(); this.delegate.close();
} }
@Override
public int hashCode() { public int hashCode() {
return delegate.hashCode(); return this.delegate.hashCode();
} }
@Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
return delegate.equals(obj); return this.delegate.equals(obj);
} }
@Override
public String toString() { public String toString() {
return getClass().getName() + "[delegate=" + delegate.toString() + "]"; return getClass().getName() + "[delegate=" + this.delegate.toString() + "]";
} }
@Override
public boolean checkError() { public boolean checkError() {
return delegate.checkError(); return this.delegate.checkError();
} }
@Override
public void write(int c) { public void write(int c) {
trackContentLength(c); trackContentLength(c);
delegate.write(c); this.delegate.write(c);
} }
@Override
public void write(char[] buf, int off, int len) { public void write(char[] buf, int off, int len) {
checkContentLength(len); checkContentLength(len);
delegate.write(buf, off, len); this.delegate.write(buf, off, len);
} }
@Override
public void write(char[] buf) { public void write(char[] buf) {
trackContentLength(buf); trackContentLength(buf);
delegate.write(buf); this.delegate.write(buf);
} }
@Override
public void write(String s, int off, int len) { public void write(String s, int off, int len) {
checkContentLength(len); checkContentLength(len);
delegate.write(s, off, len); this.delegate.write(s, off, len);
} }
@Override
public void write(String s) { public void write(String s) {
trackContentLength(s); trackContentLength(s);
delegate.write(s); this.delegate.write(s);
} }
@Override
public void print(boolean b) { public void print(boolean b) {
trackContentLength(b); trackContentLength(b);
delegate.print(b); this.delegate.print(b);
} }
@Override
public void print(char c) { public void print(char c) {
trackContentLength(c); trackContentLength(c);
delegate.print(c); this.delegate.print(c);
} }
@Override
public void print(int i) { public void print(int i) {
trackContentLength(i); trackContentLength(i);
delegate.print(i); this.delegate.print(i);
} }
@Override
public void print(long l) { public void print(long l) {
trackContentLength(l); trackContentLength(l);
delegate.print(l); this.delegate.print(l);
} }
@Override
public void print(float f) { public void print(float f) {
trackContentLength(f); trackContentLength(f);
delegate.print(f); this.delegate.print(f);
} }
@Override
public void print(double d) { public void print(double d) {
trackContentLength(d); trackContentLength(d);
delegate.print(d); this.delegate.print(d);
} }
@Override
public void print(char[] s) { public void print(char[] s) {
trackContentLength(s); trackContentLength(s);
delegate.print(s); this.delegate.print(s);
} }
@Override
public void print(String s) { public void print(String s) {
trackContentLength(s); trackContentLength(s);
delegate.print(s); this.delegate.print(s);
} }
@Override
public void print(Object obj) { public void print(Object obj) {
trackContentLength(obj); trackContentLength(obj);
delegate.print(obj); this.delegate.print(obj);
} }
@Override
public void println() { public void println() {
trackContentLengthLn(); trackContentLengthLn();
delegate.println(); this.delegate.println();
} }
@Override
public void println(boolean x) { public void println(boolean x) {
trackContentLength(x); trackContentLength(x);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(x); this.delegate.println(x);
} }
@Override
public void println(char x) { public void println(char x) {
trackContentLength(x); trackContentLength(x);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(x); this.delegate.println(x);
} }
@Override
public void println(int x) { public void println(int x) {
trackContentLength(x); trackContentLength(x);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(x); this.delegate.println(x);
} }
@Override
public void println(long x) { public void println(long x) {
trackContentLength(x); trackContentLength(x);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(x); this.delegate.println(x);
} }
@Override
public void println(float x) { public void println(float x) {
trackContentLength(x); trackContentLength(x);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(x); this.delegate.println(x);
} }
@Override
public void println(double x) { public void println(double x) {
trackContentLength(x); trackContentLength(x);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(x); this.delegate.println(x);
} }
@Override
public void println(char[] x) { public void println(char[] x) {
trackContentLength(x); trackContentLength(x);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(x); this.delegate.println(x);
} }
@Override
public void println(String x) { public void println(String x) {
trackContentLength(x); trackContentLength(x);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(x); this.delegate.println(x);
} }
@Override
public void println(Object x) { public void println(Object x) {
trackContentLength(x); trackContentLength(x);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(x); this.delegate.println(x);
} }
@Override
public PrintWriter printf(String format, Object... args) { public PrintWriter printf(String format, Object... args) {
return delegate.printf(format, args); return this.delegate.printf(format, args);
} }
@Override
public PrintWriter printf(Locale l, String format, Object... args) { public PrintWriter printf(Locale l, String format, Object... args) {
return delegate.printf(l, format, args); return this.delegate.printf(l, format, args);
} }
@Override
public PrintWriter format(String format, Object... args) { public PrintWriter format(String format, Object... args) {
return delegate.format(format, args); return this.delegate.format(format, args);
} }
@Override
public PrintWriter format(Locale l, String format, Object... args) { public PrintWriter format(Locale l, String format, Object... args) {
return delegate.format(l, format, args); return this.delegate.format(l, format, args);
} }
@Override
public PrintWriter append(CharSequence csq) { public PrintWriter append(CharSequence csq) {
checkContentLength(csq.length()); checkContentLength(csq.length());
return delegate.append(csq); return this.delegate.append(csq);
} }
@Override
public PrintWriter append(CharSequence csq, int start, int end) { public PrintWriter append(CharSequence csq, int start, int end) {
checkContentLength(end - start); checkContentLength(end - start);
return delegate.append(csq, start, end); return this.delegate.append(csq, start, end);
} }
@Override
public PrintWriter append(char c) { public PrintWriter append(char c) {
trackContentLength(c); trackContentLength(c);
return delegate.append(c); return this.delegate.append(c);
} }
} }
/** /**
* Ensures{@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before calling methods that commit the response. We delegate all methods * Ensures{@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before
* to the original {@link javax.servlet.ServletOutputStream} to ensure that the behavior is as close to the original {@link javax.servlet.ServletOutputStream} * calling methods that commit the response. We delegate all methods to the original
* as possible. See SEC-2039 * {@link javax.servlet.ServletOutputStream} to ensure that the behavior is as close
* to the original {@link javax.servlet.ServletOutputStream} as possible. See SEC-2039
* *
* @author Rob Winch * @author Rob Winch
*/ */
@@ -428,123 +479,146 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
this.delegate = delegate; this.delegate = delegate;
} }
@Override
public void write(int b) throws IOException { public void write(int b) throws IOException {
trackContentLength(b); trackContentLength(b);
this.delegate.write(b); this.delegate.write(b);
} }
@Override
public void flush() throws IOException { public void flush() throws IOException {
doOnResponseCommitted(); doOnResponseCommitted();
delegate.flush(); this.delegate.flush();
} }
@Override
public void close() throws IOException { public void close() throws IOException {
doOnResponseCommitted(); doOnResponseCommitted();
delegate.close(); this.delegate.close();
} }
@Override
public int hashCode() { public int hashCode() {
return delegate.hashCode(); return this.delegate.hashCode();
} }
@Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
return delegate.equals(obj); return this.delegate.equals(obj);
} }
@Override
public void print(boolean b) throws IOException { public void print(boolean b) throws IOException {
trackContentLength(b); trackContentLength(b);
delegate.print(b); this.delegate.print(b);
} }
@Override
public void print(char c) throws IOException { public void print(char c) throws IOException {
trackContentLength(c); trackContentLength(c);
delegate.print(c); this.delegate.print(c);
} }
@Override
public void print(double d) throws IOException { public void print(double d) throws IOException {
trackContentLength(d); trackContentLength(d);
delegate.print(d); this.delegate.print(d);
} }
@Override
public void print(float f) throws IOException { public void print(float f) throws IOException {
trackContentLength(f); trackContentLength(f);
delegate.print(f); this.delegate.print(f);
} }
@Override
public void print(int i) throws IOException { public void print(int i) throws IOException {
trackContentLength(i); trackContentLength(i);
delegate.print(i); this.delegate.print(i);
} }
@Override
public void print(long l) throws IOException { public void print(long l) throws IOException {
trackContentLength(l); trackContentLength(l);
delegate.print(l); this.delegate.print(l);
} }
@Override
public void print(String s) throws IOException { public void print(String s) throws IOException {
trackContentLength(s); trackContentLength(s);
delegate.print(s); this.delegate.print(s);
} }
@Override
public void println() throws IOException { public void println() throws IOException {
trackContentLengthLn(); trackContentLengthLn();
delegate.println(); this.delegate.println();
} }
@Override
public void println(boolean b) throws IOException { public void println(boolean b) throws IOException {
trackContentLength(b); trackContentLength(b);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(b); this.delegate.println(b);
} }
@Override
public void println(char c) throws IOException { public void println(char c) throws IOException {
trackContentLength(c); trackContentLength(c);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(c); this.delegate.println(c);
} }
@Override
public void println(double d) throws IOException { public void println(double d) throws IOException {
trackContentLength(d); trackContentLength(d);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(d); this.delegate.println(d);
} }
@Override
public void println(float f) throws IOException { public void println(float f) throws IOException {
trackContentLength(f); trackContentLength(f);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(f); this.delegate.println(f);
} }
@Override
public void println(int i) throws IOException { public void println(int i) throws IOException {
trackContentLength(i); trackContentLength(i);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(i); this.delegate.println(i);
} }
@Override
public void println(long l) throws IOException { public void println(long l) throws IOException {
trackContentLength(l); trackContentLength(l);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(l); this.delegate.println(l);
} }
@Override
public void println(String s) throws IOException { public void println(String s) throws IOException {
trackContentLength(s); trackContentLength(s);
trackContentLengthLn(); trackContentLengthLn();
delegate.println(s); this.delegate.println(s);
} }
@Override
public void write(byte[] b) throws IOException { public void write(byte[] b) throws IOException {
trackContentLength(b); trackContentLength(b);
delegate.write(b); this.delegate.write(b);
} }
@Override
public void write(byte[] b, int off, int len) throws IOException { public void write(byte[] b, int off, int len) throws IOException {
checkContentLength(len); checkContentLength(len);
delegate.write(b, off, len); this.delegate.write(b, off, len);
} }
@Override
public String toString() { public String toString() {
return getClass().getName() + "[delegate=" + delegate.toString() + "]"; return getClass().getName() + "[delegate=" + this.delegate.toString() + "]";
} }
} }
} }

View File

@@ -15,21 +15,32 @@
*/ */
package org.springframework.security.web.header; package org.springframework.security.web.header;
import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException;
import static org.mockito.Mockito.verify;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.header.HeaderWriter;
import org.springframework.security.web.header.HeaderWriterFilter; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
/** /**
* Tests for the {@code HeadersFilter} * Tests for the {@code HeadersFilter}
@@ -60,8 +71,8 @@ public class HeaderWriterFilterTests {
@Test @Test
public void additionalHeadersShouldBeAddedToTheResponse() throws Exception { public void additionalHeadersShouldBeAddedToTheResponse() throws Exception {
List<HeaderWriter> headerWriters = new ArrayList<HeaderWriter>(); List<HeaderWriter> headerWriters = new ArrayList<HeaderWriter>();
headerWriters.add(writer1); headerWriters.add(this.writer1);
headerWriters.add(writer2); headerWriters.add(this.writer2);
HeaderWriterFilter filter = new HeaderWriterFilter(headerWriters); HeaderWriterFilter filter = new HeaderWriterFilter(headerWriters);
@@ -71,9 +82,34 @@ public class HeaderWriterFilterTests {
filter.doFilter(request, response, filterChain); filter.doFilter(request, response, filterChain);
verify(writer1).writeHeaders(request, response); verify(this.writer1).writeHeaders(request, response);
verify(writer2).writeHeaders(request, response); verify(this.writer2).writeHeaders(request, response);
assertThat(filterChain.getRequest()).isEqualTo(request); // verify the filterChain assertThat(filterChain.getRequest()).isEqualTo(request); // verify the filterChain
// continued // continued
} }
// gh-2953
@Test
public void headersDelayed() throws Exception {
HeaderWriterFilter filter = new HeaderWriterFilter(
Arrays.<HeaderWriter>asList(this.writer1));
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, new FilterChain() {
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
verifyZeroInteractions(HeaderWriterFilterTests.this.writer1);
response.flushBuffer();
verify(HeaderWriterFilterTests.this.writer1).writeHeaders(
any(HttpServletRequest.class), any(HttpServletResponse.class));
}
});
verifyNoMoreInteractions(this.writer1);
}
} }

View File

@@ -15,19 +15,32 @@
*/ */
package org.springframework.security.web.header.writers; package org.springframework.security.web.header.writers;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays; import java.util.Arrays;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.spy;
/** /**
* @author Rob Winch * @author Rob Winch
* *
*/ */
@RunWith(PowerMockRunner.class)
@PrepareOnlyThisForTest(ReflectionUtils.class)
public class CacheControlHeadersWriterTests { public class CacheControlHeadersWriterTests {
private MockHttpServletRequest request; private MockHttpServletRequest request;
@@ -38,20 +51,79 @@ public class CacheControlHeadersWriterTests {
@Before @Before
public void setup() { public void setup() {
request = new MockHttpServletRequest(); this.request = new MockHttpServletRequest();
response = new MockHttpServletResponse(); this.response = new MockHttpServletResponse();
writer = new CacheControlHeadersWriter(); this.writer = new CacheControlHeadersWriter();
} }
@Test @Test
public void writeHeaders() { public void writeHeaders() {
writer.writeHeaders(request, response); this.writer.writeHeaders(this.request, this.response);
assertThat(response.getHeaderNames().size()).isEqualTo(3); assertThat(this.response.getHeaderNames().size()).isEqualTo(3);
assertThat(response.getHeaderValues("Cache-Control")).isEqualTo( assertThat(this.response.getHeaderValues("Cache-Control")).isEqualTo(
Arrays.asList("no-cache, no-store, max-age=0, must-revalidate")); Arrays.asList("no-cache, no-store, max-age=0, must-revalidate"));
assertThat(response.getHeaderValues("Pragma")).isEqualTo( assertThat(this.response.getHeaderValues("Pragma"))
Arrays.asList("no-cache")); .isEqualTo(Arrays.asList("no-cache"));
assertThat(response.getHeaderValues("Expires")).isEqualTo(Arrays.asList("0")); assertThat(this.response.getHeaderValues("Expires"))
.isEqualTo(Arrays.asList("0"));
}
@Test
public void writeHeadersServlet25() {
spy(ReflectionUtils.class);
when(ReflectionUtils.findMethod(HttpServletResponse.class, "getHeader",
String.class)).thenReturn(null);
this.response = spy(this.response);
doThrow(NoSuchMethodError.class).when(this.response).getHeader(anyString());
this.writer = new CacheControlHeadersWriter();
this.writer.writeHeaders(this.request, this.response);
assertThat(this.response.getHeaderNames().size()).isEqualTo(3);
assertThat(this.response.getHeaderValues("Cache-Control")).isEqualTo(
Arrays.asList("no-cache, no-store, max-age=0, must-revalidate"));
assertThat(this.response.getHeaderValues("Pragma"))
.isEqualTo(Arrays.asList("no-cache"));
assertThat(this.response.getHeaderValues("Expires"))
.isEqualTo(Arrays.asList("0"));
}
// gh-2953
@Test
public void writeHeadersDisabledIfCacheControl() {
this.response.setHeader("Cache-Control", "max-age: 123");
this.writer.writeHeaders(this.request, this.response);
assertThat(this.response.getHeaderNames()).hasSize(1);
assertThat(this.response.getHeaderValues("Cache-Control"))
.containsOnly("max-age: 123");
assertThat(this.response.getHeaderValue("Pragma")).isNull();
assertThat(this.response.getHeaderValue("Expires")).isNull();
}
@Test
public void writeHeadersDisabledIfPragma() {
this.response.setHeader("Pragma", "mock");
this.writer.writeHeaders(this.request, this.response);
assertThat(this.response.getHeaderNames()).hasSize(1);
assertThat(this.response.getHeaderValues("Pragma")).containsOnly("mock");
assertThat(this.response.getHeaderValue("Expires")).isNull();
assertThat(this.response.getHeaderValue("Cache-Control")).isNull();
}
@Test
public void writeHeadersDisabledIfExpires() {
this.response.setHeader("Expires", "mock");
this.writer.writeHeaders(this.request, this.response);
assertThat(this.response.getHeaderNames()).hasSize(1);
assertThat(this.response.getHeaderValues("Expires")).containsOnly("mock");
assertThat(this.response.getHeaderValue("Cache-Control")).isNull();
assertThat(this.response.getHeaderValue("Pragma")).isNull();
} }
} }

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.security.web.context; package org.springframework.security.web.util;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
@@ -25,6 +25,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.security.web.util.OnCommittedResponseWrapper;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;