Skip to main content

Blarg #22: A Filter that auto-encodes session IDs on relative page links.

Posted by jfalkner on March 27, 2006 at 10:43 PM PST

This is an example Filter that auto-encodes all relative links on a website using the HttpServletResponse.encodeURL() method. It was originally encoded as an example during a Develop Mentor course I taught. It is a nice example of a servlet filter that buffers a response, locates links using a simple regular expression, and replaces links with encoded links.

The code. Remember each filter has three parts: the Filter, ServletResponseWrapper, and ServletOutputStream sub-class.

EncodeSessionInURLFilter.java

This filter does nothing more than wraps the HttpServletResponse in order to buffer text sent out to a client.

package example;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class EncodeSessionInURLFilter implements Filter {
    ServletContext sc = null;
   
    public void doFilter(ServletRequest req, ServletResponse res,
            FilterChain chain) throws IOException, ServletException {
        // check that it is a HTTP request
        if (req instanceof HttpServletRequest) {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
           
            // nonce encode the normal output
            EncodeSessionInURLResponseWrapper wrappedResponse = new EncodeSessionInURLResponseWrapper(
                    response, sc);
           
            // make sure a session exists
            HttpSession session = request.getSession(true);
           
            chain.doFilter(req, wrappedResponse);
            // finish the respone
            wrappedResponse.finishResponse();
        }
    }
   
    public void init(FilterConfig filterConfig) {
        // reference the context
        sc = filterConfig.getServletContext();
    }
   
    public void destroy() {
        // noop
    }
}

EncodeSessionInURLResponseWrapper.java

This wrapper sends back a custom ServletOutputStream object in order to buffer all text that is being sent to the client.

package example;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class EncodeSessionInURLResponseWrapper extends HttpServletResponseWrapper {
    protected HttpServletResponse origResponse = null;
    protected ServletOutputStream stream = null;
    protected PrintWriter writer = null;
    ServletContext sc;
   
    public EncodeSessionInURLResponseWrapper(HttpServletResponse response, ServletContext sc) {
        super(response);
        this.sc = sc;
        origResponse = response;
    }
   
    public ServletOutputStream createOutputStream() throws IOException {
        return (new EncodeSessionInURLResponseStream(origResponse, sc));
    }
   
    public void finishResponse() {
        try {
            if (writer != null) {
                writer.close();
            } else {
                if (stream != null) {
                    stream.close();
                }
            }
        } catch (IOException e) {}
    }
   
    public void flushBuffer() throws IOException {
        stream.flush();
    }
   
    public ServletOutputStream getOutputStream() throws IOException {
        if (writer != null) {
            throw new IllegalStateException("getWriter() has already been called!");
        }
       
        if (stream == null)
            stream = createOutputStream();
        return (stream);
    }
   
    public PrintWriter getWriter() throws IOException {
        if (writer != null) {
            return (writer);
        }
       
        if (stream != null) {
            throw new IllegalStateException("getOutputStream() has already been called!");
        }
       
        stream = createOutputStream();
        // BUG FIX 2003-12-01 Reuse content's encoding, don't assume UTF-8
        writer = new PrintWriter(new OutputStreamWriter(stream, origResponse.getCharacterEncoding()));
        return (writer);
    }
   
    public void setContentLength(int length) {}
}

EncodeSessionInURLResponseStream.java

This response stream buffers all text that is send to the client and uses a regular expression to locate and replace links with encoded links.

package example;

import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.*;
import javax.servlet.http.*;

/**
*
* @author Jayson Falkner - jayson@jspinsider.com
*/
public class EncodeSessionInURLResponseStream extends ServletOutputStream {
    // abstraction of the output stream used for compression
    protected OutputStream bufferedOutput = null;
   
    // state keeping variable for if close() has been called
    protected boolean closed = false;
   
    // reference to original response
    protected HttpServletResponse response = null;
   
    // reference to the output stream to the client's browser
    protected ServletOutputStream output = null;
   
    // default size of the in-memory buffer
    private int bufferSize = 50000;
   
    ServletContext sc;
   
    public EncodeSessionInURLResponseStream(HttpServletResponse response, ServletContext sc) throws IOException {
        super();
        closed = false;
        this.sc = sc;
        this.response = response;
        this.output = response.getOutputStream();
        bufferedOutput = new ByteArrayOutputStream();
    }
   
    public void close() throws IOException {
        // make up a nonce
        String nonce = Integer.toString((int)(Math.random()*Integer.MAX_VALUE));
        // set the nonce in app scope
        sc.setAttribute("nonce", nonce);
       
        // get the content
        ByteArrayOutputStream baos = (ByteArrayOutputStream) bufferedOutput;
       
        // make a string out of the response
        String pageText = new String(baos.toByteArray());
       
        // use regex to find the links
        Pattern p = Pattern.compile(" href="[^"]*|action="[^"]*");
        Matcher m = p.matcher(pageText);
       
        String newText = "";
        int offset = 0;
        while (m.find(offset)) {
            // update the text
            newText += pageText.substring(offset, m.start());
            // update the offset
            offset = m.end();
            // get the matching string
            String match = pageText.substring(m.start(), m.end());
            // get the URL
            String[] split = match.split(""");
            String url = split[1];
            // encode the match
            String encoded = response.encodeURL(url);
           
            // add the match to the new text
            newText += split[0]+"""+encoded;
        }
        // add the final text
        newText += pageText.substring(offset, pageText.length());
       
       
       
        // set appropriate HTTP headers
//        response.setContentLength(compressedBytes.length);
        output.write(newText.getBytes());
        output.flush();
        output.close();
        closed = true;
       
    }
   
    public void flush() throws IOException {
        if (closed) {
            throw new IOException("Cannot flush a closed output stream");
        }
        bufferedOutput.flush();
    }
   
    public void write(int b) throws IOException {
        if (closed) {
            throw new IOException("Cannot write to a closed output stream");
        }
        // write the byte to the temporary output
        bufferedOutput.write((byte) b);
    }
   
    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }
   
    public void write(byte b[], int off, int len) throws IOException {
        System.out.println("writing...");
        if (closed) {
            throw new IOException("Cannot write to a closed output stream");
        }
        // write the content to the buffer
        bufferedOutput.write(b, off, len);
    }
   
    public boolean closed() {
        return (this.closed);
    }
   
    public void reset() {
        //noop
    }
}