alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *  Copyright 1999-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.tomcat.util.http;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.StringTokenizer;

import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.MessageBytes;

/**
 * A collection of cookies - reusable and tuned for server side performance.
 * Based on RFC2965 ( and 2109 )
 *
 * This class is not synchronized.
 *
 * @author Costin Manolache
 * @author kevin seguin
 */
public final class Cookies { // extends MultiMap {

    // expected average number of cookies per request
    public static final int INITIAL_SIZE=4; 
    ServerCookie scookies[]=new ServerCookie[INITIAL_SIZE];
    int cookieCount=0;
    boolean unprocessed=true;

    MimeHeaders headers;
    
    /**
     *  Construct a new cookie collection, that will extract
     *  the information from headers.
     *
     * @param headers Cookies are lazy-evaluated and will extract the
     *     information from the provided headers.
     */
    public Cookies(MimeHeaders headers) {
	this.headers=headers;
    }

    /**
     * Construct a new uninitialized cookie collection.
     * Use {@link #setHeaders} to initialize.
     */
    // [seguin] added so that an empty Cookies object could be
    // created, have headers set, then recycled.
    public Cookies() {
    }

    /**
     * Set the headers from which cookies will be pulled.
     * This has the side effect of recycling the object.
     *
     * @param headers Cookies are lazy-evaluated and will extract the
     *     information from the provided headers.
     */
    // [seguin] added so that an empty Cookies object could be
    // created, have headers set, then recycled.
    public void setHeaders(MimeHeaders headers) {
        recycle();
        this.headers=headers;
    }

    /**
     * Recycle.
     */
    public void recycle() {
    	for( int i=0; i< cookieCount; i++ ) {
	    if( scookies[i]!=null )
		scookies[i].recycle();
	}
	cookieCount=0;
	unprocessed=true;
    }

    /**
     * EXPENSIVE!!!  only for debugging.
     */
    public String toString() {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        pw.println("=== Cookies ===");
        int count = getCookieCount();
        for (int i = 0; i < count; ++i) {
            pw.println(getCookie(i).toString());
        }
        return sw.toString();
    }

    // -------------------- Indexed access --------------------
    
    public ServerCookie getCookie( int idx ) {
	if( unprocessed ) {
	    getCookieCount(); // will also update the cookies
	}
	return scookies[idx];
    }

    public int getCookieCount() {
	if( unprocessed ) {
	    unprocessed=false;
	    processCookies(headers);
	}
	return cookieCount;
    }

    // -------------------- Adding cookies --------------------

    /** Register a new, unitialized cookie. Cookies are recycled, and
     *  most of the time an existing ServerCookie object is returned.
     *  The caller can set the name/value and attributes for the cookie
     */
    public ServerCookie addCookie() {
	if( cookieCount >= scookies.length  ) {
	    ServerCookie scookiesTmp[]=new ServerCookie[2*cookieCount];
	    System.arraycopy( scookies, 0, scookiesTmp, 0, cookieCount);
	    scookies=scookiesTmp;
	}
	
	ServerCookie c = scookies[cookieCount];
	if( c==null ) {
	    c= new ServerCookie();
	    scookies[cookieCount]=c;
	}
	cookieCount++;
	return c;
    }


    // code from CookieTools 

    /** Add all Cookie found in the headers of a request.
     */
    public  void processCookies( MimeHeaders headers ) {
	if( headers==null )
	    return;// nothing to process
	// process each "cookie" header
	int pos=0;
	while( pos>=0 ) {
	    // Cookie2: version ? not needed
	    pos=headers.findHeader( "Cookie", pos );
	    // no more cookie headers headers
	    if( pos<0 ) break;

	    MessageBytes cookieValue=headers.getValue( pos );
	    if( cookieValue==null || cookieValue.isNull() ) continue;

	    // Uncomment to test the new parsing code
	    if( cookieValue.getType() == MessageBytes.T_BYTES ) {
		if( dbg>0 ) log( "Parsing b[]: " + cookieValue.toString());
		ByteChunk bc=cookieValue.getByteChunk();
		processCookieHeader( bc.getBytes(),
				     bc.getOffset(),
				     bc.getLength());
	    } else {
		if( dbg>0 ) log( "Parsing S: " + cookieValue.toString());
		processCookieHeader( cookieValue.toString() );
	    }
	    pos++;// search from the next position
	}
    }

    /** Process a byte[] header - allowing fast processing of the
     *  raw data
     */
    void processCookieHeader(  byte bytes[], int off, int len )
    {
	if( len<=0 || bytes==null ) return;
	int end=off+len;
	int pos=off;
	
	int version=0; //sticky
	ServerCookie sc=null;
	

	while( pos=end )
		return; // only spaces
	    int startName=pos;
	    if( dbg>0 ) log( "SN: " + pos );
	    
	    // Version should be the first token
	    boolean isSpecial=false;
	    if(bytes[pos]=='$') { pos++; isSpecial=true; }

	    pos= findDelim1( bytes, startName, end); // " =;,"
	    int endName=pos;
	    // current = "=" or " " or DELIM
	    pos= skipSpaces( bytes, endName, end ); 
	    if( dbg>0 ) log( "DELIM: " + endName + " " + (char)bytes[pos]);

	    if(pos >= end ) {
		// it's a name-only cookie ( valid in RFC2109 )
		if( ! isSpecial ) {
		    sc=addCookie();
		    sc.getName().setBytes( bytes, startName,
					   endName-startName );
		    sc.getValue().setString("");
		    sc.setVersion( version );
		    if( dbg>0 ) log( "Name only, end: " + startName + " " +
				     endName);
		}
		return;
	    }

	    cc=bytes[pos];
	    pos++;
	    if( cc==';' || cc==',' ) {
		if( ! isSpecial && startName!= endName ) {
		    sc=addCookie();
		    sc.getName().setBytes( bytes, startName,
					   endName-startName );
		    sc.getValue().setString("");
		    sc.setVersion( version );
		    if( dbg>0 ) log( "Name only: " + startName + " " + endName);
		}
		continue;
	    }
	    
	    // we should have "=" ( tested all other alternatives )
	    int startValue=skipSpaces( bytes, pos, end);
	    int endValue=startValue;
	    
	    // quote is valid only in version=1 cookies
	    cc=bytes[pos];
	    if( ( version == 1 || isSpecial ) && ( cc== '\'' || cc=='"' ) ) {
		startValue++;
		endValue=indexOf( bytes, startValue, end, cc );
		pos=endValue+1; // to skip to next cookie
 	    } else {
		endValue=findDelim2( bytes, startValue, end );
		pos=endValue+1;
	    }
	    
	    // if not $Version, etc
	    if( ! isSpecial ) {
		sc=addCookie();
		sc.getName().setBytes( bytes, startName, endName-startName );
		sc.getValue().setBytes( bytes, startValue, endValue-startValue);
		sc.setVersion( version );
		if( dbg>0 ) log( "New: " + sc.getName() + "X=X" + sc.getValue());
		continue;
	    }
	    
	    // special - Path, Version, Domain, Port
	    if( dbg>0 ) log( "Special: " + startName + " " + endName);
	    // XXX TODO
	    if( equals( "$Version", bytes, startName, endName ) ) {
		if(dbg>0 ) log( "Found version " );
		if( bytes[startValue]=='1' && endValue==startValue+1 ) {
		    version=1;
		    if(dbg>0 ) log( "Found version=1" );
		}
		continue;
	    }
	    if( sc==null ) {
		// Path, etc without a previous cookie
		continue;
	    }
	    if( equals( "$Path", bytes, startName, endName ) ) {
		sc.getPath().setBytes( bytes, startValue, endValue-startValue );
	    }
	    if( equals( "$Domain", bytes, startName, endName ) ) {
		sc.getDomain().setBytes( bytes, startValue, endValue-startValue );
	    }
	    if( equals( "$Port", bytes, startName, endName ) ) {
		// sc.getPort().setBytes( bytes, startValue, endValue-startValue );
	    }
	}
    }

    // -------------------- Utils --------------------
    public static int skipSpaces(  byte bytes[], int off, int end ) {
	while( off < end ) {
	    byte b=bytes[off];
	    if( b!= ' ' ) return off;
	    off ++;
	}
	return off;
    }

    public static int findDelim1( byte bytes[], int off, int end )
    {
	while( off < end ) {
	    byte b=bytes[off];
	    if( b==' ' || b=='=' || b==';' || b==',' )
		return off;
	    off++;
	}
	return off;
    }

    public static int findDelim2( byte bytes[], int off, int end )
    {
	while( off < end ) {
	    byte b=bytes[off];
	    if( b==';' || b==',' )
		return off;
	    off++;
	}
	return off;
    }

    public static int indexOf( byte bytes[], int off, int end, byte qq )
    {
	while( off < end ) {
	    byte b=bytes[off];
	    if( b==qq )
		return off;
	    off++;
	}
	return off;
    }

    public static int indexOf( byte bytes[], int off, int end, char qq )
    {
	while( off < end ) {
	    byte b=bytes[off];
	    if( b==qq )
		return off;
	    off++;
	}
	return off;
    }
    
    // XXX will be refactored soon!
    public static boolean equals( String s, byte b[], int start, int end) {
	int blen = end-start;
	if (b == null || blen != s.length()) {
	    return false;
	}
	int boff = start;
	for (int i = 0; i < blen; i++) {
	    if (b[boff++] != s.charAt(i)) {
		return false;
	    }
	}
	return true;
    }
    

    // ---------------------------------------------------------
    // -------------------- DEPRECATED, OLD --------------------
    
    private void processCookieHeader(  String cookieString )
    {
	if( dbg>0 ) log( "Parsing cookie header " + cookieString );
	// normal cookie, with a string value.
	// This is the original code, un-optimized - it shouldn't
	// happen in normal case

	StringTokenizer tok = new StringTokenizer(cookieString,
						  ";", false);
	while (tok.hasMoreTokens()) {
	    String token = tok.nextToken();
	    int i = token.indexOf("=");
	    if (i > -1) {
		
		// XXX
		// the trims here are a *hack* -- this should
		// be more properly fixed to be spec compliant
		
		String name = token.substring(0, i).trim();
		String value = token.substring(i+1, token.length()).trim();
		// RFC 2109 and bug 
		value=stripQuote( value );
		ServerCookie cookie = addCookie();
		
		cookie.getName().setString(name);
		cookie.getValue().setString(value);
		if( dbg > 0 ) log( "Add cookie " + name + "=" + value);
	    } else {
		// we have a bad cookie.... just let it go
	    }
	}
    }

    /**
     *
     * Strips quotes from the start and end of the cookie string
     * This conforms to RFC 2109
     * 
     * @param value            a String specifying the cookie 
     *                         value (possibly quoted).
     *
     * @see #setValue
     *
     */
    private static String stripQuote( String value )
    {
	//	log("Strip quote from " + value );
	if (((value.startsWith("\"")) && (value.endsWith("\""))) ||
	    ((value.startsWith("'") && (value.endsWith("'"))))) {
	    try {
		return value.substring(1,value.length()-1);
	    } catch (Exception ex) { 
	    }
	}
	return value;
    }  


    // log
    static final int dbg=0;
    public void log(String s ) {
	System.out.println("Cookies: " + s);
    }

    /*
    public static void main( String args[] ) {
	test("foo=bar; a=b");
	test("foo=bar;a=b");
	test("foo=bar;a=b;");
	test("foo=bar;a=b; ");
	test("foo=bar;a=b; ;");
	test("foo=;a=b; ;");
	test("foo;a=b; ;");
	// v1 
	test("$Version=1; foo=bar;a=b"); 
        test("$Version=\"1\"; foo='bar'; $Path=/path; $Domain=\"localhost\"");
	test("$Version=1;foo=bar;a=b; ; ");
	test("$Version=1;foo=;a=b; ; ");
	test("$Version=1;foo= ;a=b; ; ");
	test("$Version=1;foo;a=b; ; ");
	test("$Version=1;foo=\"bar\";a=b; ; ");
	test("$Version=1;foo=\"bar\";$Path=/examples;a=b; ; ");
	test("$Version=1;foo=\"bar\";$Domain=apache.org;a=b");
	test("$Version=1;foo=\"bar\";$Domain=apache.org;a=b;$Domain=yahoo.com");
	// rfc2965
	test("$Version=1;foo=\"bar\";$Domain=apache.org;$Port=8080;a=b");

	// wrong
	test("$Version=1;foo=\"bar\";$Domain=apache.org;$Port=8080;a=b");
    }

    public static void test( String s ) {
	System.out.println("Processing " + s );
	Cookies cs=new Cookies(null);
	cs.processCookieHeader( s.getBytes(), 0, s.length());
	for( int i=0; i< cs.getCookieCount() ; i++ ) {
	    System.out.println("Cookie: " + cs.getCookie( i ));
	}
	    
    }
    */

}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.