|
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( pos0 ) log( "Start: " + pos + " " + end );
pos=skipSpaces(bytes, pos, end);
if( 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 ));
}
}
*/
}
|