You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by Annabel Liu <an...@escalate.com> on 2000/03/21 02:16:22 UTC

[PATCH] automatic url rewriting for cookie disabled browser

Hi,

We at Escalate Inc have written the automatic url rewriting for tomcat 3.0.
It is equivalent
to the automatic url rewriting feature in NAS and has been tested in solaris
and NT. 
We want to contribute the source code  back to the apache community since it
might 
be useful to others. 

Basically, mostly of work is in the URLRewrittenOutputStream.  This file
detects 
URLs as bytes are written and calls HttpServletResponseFacade.encodeURL to
encode the url. 
The following patch shows the change needed to the current 3.1 code. 

Please let me know what you think.

Thanks.

-Annabel

--- UrlRewrittenOutputStream.java.orig	Mon Mar 20 16:51:26 2000
+++ UrlRewrittenOutputStream.java	Mon Mar 20 14:52:43 2000
@@ -0,0 +1,330 @@
+package org.apache.tomcat.core;
+
+import java.io.*;
+import java.util.Hashtable;
+import javax.servlet.ServletOutputStream;
+
+public class URLRewrittenOutputStream extends BufferedServletOutputStream {
+    
+    // states
+    public final static int INITIAL_STATE = 1;
+    public final static int TAG_FOUND = 2;
+    public final static int TAGNAME_STARTED = 3;
+    public final static int TAG_NOT_NEEDED = 4;
+    public final static int TAG_NEEDED = 5;
+    public final static int ATTRIBUTE_STARTED = 6;
+    public final static int ATTRIBUTE_COMPLETED = 7;
+    public final static int VALUE_NEEDED = 8;
+    public final static int VALUE_NOT_NEEDED = 9;
+    public final static int VALUE_NEEDED_IN_QUOTE = 10;
+    public final static int VALUE_NEEDED_NOT_IN_QUOTE = 11;
+    public final static int VALUE_NOT_NEEDED_IN_QUOTE = 12;
+    public final static int VALUE_NOT_NEEDED_NOT_IN_QUOTE = 13;
+    
+    int currentState = INITIAL_STATE;
+    StringBuffer tag = new StringBuffer();  // for tag name
+    StringBuffer attribute = new StringBuffer(); // for attribute name
+    StringBuffer value = new StringBuffer(); // for url
+    String urlAttribute = null;  // the attribute that represents a
url,href 
+    byte currentQuote = (byte)-1;
+    byte[] onebyte = new byte[1];
+    HttpServletResponseFacade responseFacade;
+    
+    static final Hashtable rewritableUrls = new Hashtable();
+    static {
+        rewritableUrls.put("script", "src");
+        rewritableUrls.put("embed", "src");
+        rewritableUrls.put("input", "src");
+        rewritableUrls.put("img", "src");
+        rewritableUrls.put("body", "background");
+        rewritableUrls.put("a", "href");
+        rewritableUrls.put("form", "action");
+    }
+    
+    // need to url rewrite
+    public URLRewrittenOutputStream() {
+        
+    }
+    
+    public URLRewrittenOutputStream(ResponseImpl resA) {
+        super(resA);
+    }
+    
+    public void setResponseFacade(HttpServletResponseFacade
responseFacade){
+        this.responseFacade = responseFacade;
+    }
+    private boolean isWhiteSpace(byte c)
+    {
+        return (( ((char)c) == ' ') ||
+                ( ((char)c) == '\n'));
+    }
+
+    private boolean isQuote(byte c){
+        return (( ((char)c) == '\'') || ( ((char)c) == '"'));
+    }
+    private boolean isTagBegin(byte c){
+        return (((char)c) == '<');
+    }
+
+    private boolean isTagEnd(byte c){
+        return ( ((char)c) == '>');
+    }
+    
+    private void backToInitial()
+    {
+        currentState = INITIAL_STATE;
+        tag.setLength(0);
+        attribute.setLength(0);
+        value.setLength(0);
+    }
+    
+    private boolean sameAttributes(StringBuffer attribute1,
+                                   String attribute2){
+        String attr1 = attribute1.toString().trim();
+        return (attr1.equalsIgnoreCase(attribute2));
+    }
+
+    protected void checkForUrlRewrite(byte[] data, int off, int len)
+    throws IOException
+    {
+        int lastWrite = 0;
+        byte[] buffer = new byte[data.length];
+        int total = off + len;
+        
+        for (int i = off; i < total; i++){
+            switch (currentState){
+            case INITIAL_STATE:
+                buffer[lastWrite++] = data[i];
+                if (isTagBegin(data[i])){
+                    currentState = TAG_FOUND;
+                }
+            break;
+            case TAG_FOUND:
+                buffer[lastWrite ++] = data[i];
+                if (isTagEnd(data[i])){
+                    backToInitial();
+                }
+                else if (!isWhiteSpace(data[i])){
+                    tag.append((char)data[i]);
+                    currentState = TAGNAME_STARTED;
+                }
+                break;
+            case TAGNAME_STARTED:
+                buffer[lastWrite ++] = data[i];
+                if (isTagEnd(data[i])){
+                    String tagName = tag.toString().trim().toUpperCase();
+                    backToInitial();
+                }
+                else if (isWhiteSpace(data[i])){
+                    String tagName = tag.toString().trim().toUpperCase();
+                    urlAttribute = checkUrlAttribute(tag.toString());
+                    if (urlAttribute == null){
+                        currentState = TAG_NOT_NEEDED;
+                    }
+                    else {
+                        currentState = TAG_NEEDED;                  
+                    }
+                }
+                else {
+                    tag.append((char)data[i]);
+                }
+                break;
+            case TAG_NOT_NEEDED:
+                buffer[lastWrite ++] = data[i];
+                if (isTagEnd(data[i])){
+                    backToInitial();
+                }
+                break;
+            case TAG_NEEDED:
+                buffer[lastWrite ++] = data[i];
+                if (isTagEnd(data[i])){
+                    backToInitial();
+                }
+                else if (!isWhiteSpace(data[i])){
+                    attribute.append((char)data[i]);
+                    currentState = ATTRIBUTE_STARTED;
+                }
+                break;
+            case ATTRIBUTE_STARTED:
+                buffer[lastWrite ++] = data[i];
+                if (isWhiteSpace(data[i])){
+                    currentState = ATTRIBUTE_COMPLETED;
+                }
+                else if (isTagEnd(data[i])){
+                    backToInitial();
+                }
+                else if (data[i] == '='){
+                    if (sameAttributes(attribute, urlAttribute)){
+                        currentState = VALUE_NEEDED; 
+                    }
+                    else {
+                        currentState = VALUE_NOT_NEEDED;
+                    }
+                }
+                else {
+                    attribute.append((char)data[i]);
+                }
+                break;
+            case ATTRIBUTE_COMPLETED:
+                buffer[lastWrite ++] = data[i];
+                if (isTagEnd(data[i])){
+                    backToInitial();
+                }
+                else if (data[i] == '='){
+                    if (sameAttributes(attribute, urlAttribute)){
+                        currentState = VALUE_NEEDED; 
+                    }
+                    else {
+                        currentState = VALUE_NOT_NEEDED;
+                    }
+                }
+                else if (!isWhiteSpace(data[i])){
+                    currentState = ATTRIBUTE_STARTED;
+                }
+                break;
+            case VALUE_NEEDED:
+                if (isQuote(data[i])){
+                    currentQuote = data[i];
+                    currentState = VALUE_NEEDED_IN_QUOTE;
+                }
+                else if (!isWhiteSpace(data[i])){
+                    value.append((char)data[i]);
+                    currentState = VALUE_NEEDED_NOT_IN_QUOTE;
+                }
+                else if (isTagEnd(data[i])){
+                    backToInitial();
+                }
+                break;
+            case VALUE_NEEDED_IN_QUOTE:
+                if (data[i] == currentQuote){
+                    if (lastWrite > 0){
+                        reallyWrite(buffer, 0, lastWrite);
+                    }
+                    onebyte[0] = currentQuote;
+                    reallyWrite(onebyte, 0, 1);
+                    String
urlString=responseFacade.encodeURL(value.toString());
+                    byte[] encodedUrl = urlString.getBytes(); 
+                    
+                    reallyWrite(encodedUrl, 0, encodedUrl.length);
+                    reallyWrite(onebyte, 0, 1);
+                    lastWrite = 0;
+                    backToInitial();
+                    currentState = TAG_NEEDED;
+                }
+                else {
+                    value.append((char)data[i]);
+                }
+                break;
+            case VALUE_NEEDED_NOT_IN_QUOTE:
+                if (isTagEnd(data[i])){
+                    reallyWrite(buffer, 0, lastWrite);
+                    String
urlString=responseFacade.encodeURL(value.toString());
+                    byte[] encodedUrl = urlString.getBytes(); 
+                    reallyWrite(encodedUrl, 0, encodedUrl.length);
+                    onebyte[0] = (byte)'>';
+                    reallyWrite(onebyte, 0, 1);
+                    lastWrite = 0;
+                    backToInitial();
+                }
+                else if (isWhiteSpace(data[i])){
+                    reallyWrite(buffer, 0, lastWrite);
+                    String
urlString=responseFacade.encodeURL(value.toString());
+                    byte[] encodedUrl = urlString.getBytes(); 
+                    reallyWrite(encodedUrl, 0, encodedUrl.length);
+                    lastWrite = 0;
+                    backToInitial();
+                    currentState = TAG_NEEDED;
+                }
+                else {
+                    value.append((char)data[i]);
+                }
+                break;
+            case VALUE_NOT_NEEDED:
+                buffer[lastWrite++] = data[i];
+                if (isQuote(data[i])){
+                    currentQuote = data[i];
+                    currentState = VALUE_NOT_NEEDED_IN_QUOTE;
+                }
+                else if (isTagEnd(data[i])){
+                    backToInitial();
+                }
+                else if (!isWhiteSpace(data[i])){
+                    currentState = VALUE_NOT_NEEDED_NOT_IN_QUOTE;
+                }
+                break;
+            case VALUE_NOT_NEEDED_IN_QUOTE:
+                buffer[lastWrite++] = data[i];
+                if (data[i] == currentQuote){
+                    currentState = TAG_NEEDED;
+                }
+                break;
+            case VALUE_NOT_NEEDED_NOT_IN_QUOTE:
+                buffer[lastWrite ++] = data[i];
+                if (isTagEnd(data[i])){
+                    backToInitial();
+                }
+                else if (isWhiteSpace(data[i])){
+                    currentState = TAG_NEEDED;
+                }
+                break;
+            }
+        }
+        if (lastWrite > 0){
+            reallyWrite(buffer, 0 , lastWrite);
+        }
+    }
+    
+    public void write(int i) throws IOException {
+        onebyte[0] = (byte)i;
+        write(onebyte, 0, 1);
+    }
+    
+    public void write(byte[] b, int off,int len)
+    throws IOException
+    {
+        if (closed) {
+            return;
+        }
+        
+        if (len < 0) {
+            String msg = sm.getString("servletOutputStreamImpl.write.iae");
+            throw new IllegalArgumentException(msg);
+        }
+        checkForUrlRewrite(b, off, len);
+        
+    } 
+    
+    public void close()
+        throws IOException
+    {
+        if (value.length() >0){
+            byte[] lastbyte = value.toString().getBytes();
+            super.write(lastbyte, 0, lastbyte.length);
+        }
+        super.close();
+    }
+    
+    private void reallyWrite(byte[] b, int off, int len) 
+        throws IOException 
+    {
+        super.write(b, off, len);
+    }
+
+    private String checkUrlAttribute(String tag)
+    {
+        return (String)(rewritableUrls.get(tag.toLowerCase()));
+    }
+}

--- ResponseImpl.java.orig	Wed Feb 16 09:13:23 2000
+++ ResponseImpl.java	Mon Mar 20 16:50:57 2000
@@ -108,8 +108,8 @@
 
     public ResponseImpl() {
         responseFacade = new HttpServletResponseFacade(this);
-	out=new BufferedServletOutputStream();
-	out.setResponse(this);
+        //out=new BufferedServletOutputStream();
+        //out.setResponse(this);
     }
 
     public HttpServletResponseFacade getFacade() {
@@ -118,6 +118,9 @@
 
     public void setRequest(Request request) {
 	this.request = request;
+        out = new URLRewrittenOutputStream();
+        out.setResponse(this);
+        ((URLRewrittenOutputStream)out).setResponseFacade(responseFacade);
     }
 
     public Request getRequest() {
@@ -449,3 +452,4 @@
     }
     
 }
+

Re: [PATCH] automatic url rewriting for cookie disabled browser

Posted by Costin Manolache <Co...@eng.sun.com>.
Hi Annabel,

Thanks for the patch, it is an interesting aproach.

There are few problems:

1. Performance - it will hit the performance by parsing all output,
and that's very bad because we'll pay the price for all requests, including
requests that don't have anything to do with sessions.

2. The API is based on explicit rewriting, i.e. the servlet controls
when a rewrite is needed.

3. It will work only for plain HTML - what about URLs constructed
in JavaScript, or XML ?

Probably we can solve 1 by checking if rewrite is needed, but 3 is probably
imposible to fix. IMHO we shouldn't include the patch in 3.1 ( it's too
dangerous), but we can discuss it for next tomcat.

Costin



Annabel Liu wrote:

> Hi,
>
> We at Escalate Inc have written the automatic url rewriting for tomcat 3.0.
> It is equivalent
> to the automatic url rewriting feature in NAS and has been tested in solaris
> and NT.
> We want to contribute the source code  back to the apache community since it
> might
> be useful to others.
>
> Basically, mostly of work is in the URLRewrittenOutputStream.  This file
> detects
> URLs as bytes are written and calls HttpServletResponseFacade.encodeURL to
> encode the url.
> The following patch shows the change needed to the current 3.1 code.
>
> Please let me know what you think.
>
> Thanks.
>
> -Annabel