Servlet response wrapper has encoding problem
- by John O
A servlet response wrapper is being used in a Servlet Filter. The idea is that the response is manipulated, with a 'nonce' value being injected into forms, as part of defence against CSRF attacks.
The web app is using UTF-8 everywhere. When the Servlet Filter is absent, no problems. When the filter is added, encoding issues occur. (It seems as if the response is reverting to 8859-1.)
The guts of the code :
final class CsrfResponseWrapper extends AbstractResponseWrapper {
...
byte[] modifyResponse(byte[] aInputResponse){
...
String originalInput = new String(aInputResponse, encoding);
String modifiedResult = addHiddenParamToPostedForms(originalInput);
result = modifiedResult.getBytes(encoding);
...
}
...
}
As I understand it, the transition between byte-land and String-land should specify an encoding. That is done here, as you can see, in two places. The value of the 'encoding' variable is 'UTF-8'; the alteration of the String itself is standard string manipulation (with a regex), and never specifies an encoding (addHiddenParamToPostedForms).
Where am I in error about the encoding?
EDIT:
Here is the base class (sorry it's rather long):
package hirondelle.web4j.security;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
/**
Abstract Base Class for altering response content.
(May be useful in future contexts as well. For now, keep package-private.)
*/
abstract class AbstractResponseWrapper extends HttpServletResponseWrapper {
AbstractResponseWrapper(ServletResponse aServletResponse) throws IOException {
super((HttpServletResponse)aServletResponse);
fOutputStream = new ModifiedOutputStream(aServletResponse.getOutputStream());
fWriter = new PrintWriter(fOutputStream);
}
/** Return the modified response. */
abstract byte[] modifyResponse(byte[] aInputResponse);
/** Standard servlet method. */
public final ServletOutputStream getOutputStream() {
//fLogger.fine("Modified Response : Getting output stream.");
if ( fWriterReturned ) {
throw new IllegalStateException();
}
fOutputStreamReturned = true;
return fOutputStream;
}
/** Standard servlet method. */
public final PrintWriter getWriter() {
//fLogger.fine("Modified Response : Getting writer.");
if ( fOutputStreamReturned ) {
throw new IllegalStateException();
}
fWriterReturned = true;
return fWriter;
}
// PRIVATE
/*
Well-behaved servlets return either an OutputStream or a PrintWriter, but not both.
*/
private PrintWriter fWriter;
private ModifiedOutputStream fOutputStream;
/*
These items are used to implement conformance to the
javadoc for ServletResponse, regarding exceptions being thrown.
*/
private boolean fWriterReturned;
private boolean fOutputStreamReturned;
/** Modified low level output stream. */
private class ModifiedOutputStream extends ServletOutputStream {
public ModifiedOutputStream(ServletOutputStream aOutputStream) {
fServletOutputStream = aOutputStream;
fBuffer = new ByteArrayOutputStream();
}
/** Must be implemented to make this class concrete. */
public void write(int aByte) {
fBuffer.write(aByte);
}
public void close() throws IOException {
if ( !fIsClosed ){
processStream();
fServletOutputStream.close();
fIsClosed = true;
}
}
public void flush() throws IOException {
if ( fBuffer.size() != 0 ){
if ( !fIsClosed ) {
processStream();
fBuffer = new ByteArrayOutputStream();
}
}
}
/** Perform the core processing, by calling the abstract method. */
public void processStream() throws IOException {
fServletOutputStream.write(modifyResponse(fBuffer.toByteArray()));
fServletOutputStream.flush();
}
// PRIVATE //
private ServletOutputStream fServletOutputStream;
private ByteArrayOutputStream fBuffer;
/** Tracks if this stream has been closed. */
private boolean fIsClosed = false;
}
}