Apr 25, 2007

Auto-encodes session IDs in URL

Blarg #22: A Filter that auto-encodes session IDs on relative page links.
Posted by jfalkner on March 27, 2006 at 10:43 PM | Comments (3)

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
* EncodeSessionInURLResponseWrapper.java
* EncodeSessionInURLResponseStream.java

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
}
}
in web.xml
<filter>
<filter-name>CookieSessionFilter</filter-name>
<filter-class> net.kokolink.zimbra.controller.EncodeSessionInURLFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CookieSessionFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>

No comments: