John
John

Reputation: 1653

Servlet response wrapper has encoding problem

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;
  }
}

Upvotes: 2

Views: 4470

Answers (2)

BalusC
BalusC

Reputation: 1108852

From the new PrintWriter(OutputStream) javadoc:

Creates a new PrintWriter, without automatic line flushing, from an existing OutputStream. This convenience constructor creates the necessary intermediate OutputStreamWriter, which will convert characters into bytes using the default character encoding.

There's your culprit. Have a look at OutputStreamWriter where you can specify the encoding.

Upvotes: 5

mtraut
mtraut

Reputation: 4740

The code you present does not not seem to be in error.

What are you doing elsewhere?

  • are there other filters in the chain that may manipulate the encoding/byte stream, maybe you have a filter position problem
  • is "encoding" fix or dynamically read from the request?

Upvotes: 0

Related Questions