KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tomcat > util > http > Cookies


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18 package org.apache.tomcat.util.http;
19
20 import java.io.PrintWriter JavaDoc;
21 import java.io.StringWriter JavaDoc;
22 import java.util.StringTokenizer JavaDoc;
23
24 import org.apache.tomcat.util.buf.ByteChunk;
25 import org.apache.tomcat.util.buf.MessageBytes;
26
27 /**
28  * A collection of cookies - reusable and tuned for server side performance.
29  * Based on RFC2965 ( and 2109 )
30  *
31  * This class is not synchronized.
32  *
33  * @author Costin Manolache
34  * @author kevin seguin
35  */

36 public final class Cookies { // extends MultiMap {
37

38     private static org.apache.commons.logging.Log log=
39         org.apache.commons.logging.LogFactory.getLog(Cookies.class );
40     
41     // expected average number of cookies per request
42
public static final int INITIAL_SIZE=4;
43     ServerCookie scookies[]=new ServerCookie[INITIAL_SIZE];
44     int cookieCount=0;
45     boolean unprocessed=true;
46
47     MimeHeaders headers;
48     
49     /**
50      * Construct a new cookie collection, that will extract
51      * the information from headers.
52      *
53      * @param headers Cookies are lazy-evaluated and will extract the
54      * information from the provided headers.
55      */

56     public Cookies(MimeHeaders headers) {
57         this.headers=headers;
58     }
59
60     /**
61      * Construct a new uninitialized cookie collection.
62      * Use {@link #setHeaders} to initialize.
63      */

64     // [seguin] added so that an empty Cookies object could be
65
// created, have headers set, then recycled.
66
public Cookies() {
67     }
68
69     /**
70      * Set the headers from which cookies will be pulled.
71      * This has the side effect of recycling the object.
72      *
73      * @param headers Cookies are lazy-evaluated and will extract the
74      * information from the provided headers.
75      */

76     // [seguin] added so that an empty Cookies object could be
77
// created, have headers set, then recycled.
78
public void setHeaders(MimeHeaders headers) {
79         recycle();
80         this.headers=headers;
81     }
82
83     /**
84      * Recycle.
85      */

86     public void recycle() {
87             for( int i=0; i< cookieCount; i++ ) {
88             if( scookies[i]!=null )
89                 scookies[i].recycle();
90         }
91         cookieCount=0;
92         unprocessed=true;
93     }
94
95     /**
96      * EXPENSIVE!!! only for debugging.
97      */

98     public String JavaDoc toString() {
99         StringWriter JavaDoc sw = new StringWriter JavaDoc();
100         PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
101         pw.println("=== Cookies ===");
102         int count = getCookieCount();
103         for (int i = 0; i < count; ++i) {
104             pw.println(getCookie(i).toString());
105         }
106         return sw.toString();
107     }
108
109     // -------------------- Indexed access --------------------
110

111     public ServerCookie getCookie( int idx ) {
112         if( unprocessed ) {
113             getCookieCount(); // will also update the cookies
114
}
115         return scookies[idx];
116     }
117
118     public int getCookieCount() {
119         if( unprocessed ) {
120             unprocessed=false;
121             processCookies(headers);
122         }
123         return cookieCount;
124     }
125
126     // -------------------- Adding cookies --------------------
127

128     /** Register a new, unitialized cookie. Cookies are recycled, and
129      * most of the time an existing ServerCookie object is returned.
130      * The caller can set the name/value and attributes for the cookie
131      */

132     public ServerCookie addCookie() {
133         if( cookieCount >= scookies.length ) {
134             ServerCookie scookiesTmp[]=new ServerCookie[2*cookieCount];
135             System.arraycopy( scookies, 0, scookiesTmp, 0, cookieCount);
136             scookies=scookiesTmp;
137         }
138         
139         ServerCookie c = scookies[cookieCount];
140         if( c==null ) {
141             c= new ServerCookie();
142             scookies[cookieCount]=c;
143         }
144         cookieCount++;
145         return c;
146     }
147
148
149     // code from CookieTools
150

151     /** Add all Cookie found in the headers of a request.
152      */

153     public void processCookies( MimeHeaders headers ) {
154         if( headers==null )
155             return;// nothing to process
156
// process each "cookie" header
157
int pos=0;
158         while( pos>=0 ) {
159             // Cookie2: version ? not needed
160
pos=headers.findHeader( "Cookie", pos );
161             // no more cookie headers headers
162
if( pos<0 ) break;
163
164             MessageBytes cookieValue=headers.getValue( pos );
165             if( cookieValue==null || cookieValue.isNull() ) {
166                 pos++;
167                 continue;
168             }
169
170             // Uncomment to test the new parsing code
171
if( cookieValue.getType() == MessageBytes.T_BYTES ) {
172                 if( dbg>0 ) log( "Parsing b[]: " + cookieValue.toString());
173                 ByteChunk bc=cookieValue.getByteChunk();
174                 processCookieHeader( bc.getBytes(),
175                                      bc.getOffset(),
176                                      bc.getLength());
177             } else {
178                 if( dbg>0 ) log( "Parsing S: " + cookieValue.toString());
179                 processCookieHeader( cookieValue.toString() );
180             }
181             pos++;// search from the next position
182
}
183     }
184
185     /** Process a byte[] header - allowing fast processing of the
186      * raw data
187      */

188     void processCookieHeader( byte bytes[], int off, int len )
189     {
190         if( len<=0 || bytes==null ) return;
191         int end=off+len;
192         int pos=off;
193         
194         int version=0; //sticky
195
ServerCookie sc=null;
196         
197
198         while( pos<end ) {
199             byte cc;
200             // [ skip_spaces name skip_spaces "=" skip_spaces value EXTRA ; ] *
201
if( dbg>0 ) log( "Start: " + pos + " " + end );
202             
203             pos=skipSpaces(bytes, pos, end);
204             if( pos>=end )
205                 return; // only spaces
206
int startName=pos;
207             if( dbg>0 ) log( "SN: " + pos );
208             
209             // Version should be the first token
210
boolean isSpecial=false;
211             if(bytes[pos]=='$') { pos++; isSpecial=true; }
212
213             pos= findDelim1( bytes, startName, end); // " =;,"
214
int endName=pos;
215             // current = "=" or " " or DELIM
216
pos= skipSpaces( bytes, endName, end );
217             if( dbg>0 ) log( "DELIM: " + endName + " " + (char)bytes[pos]);
218
219             if(pos >= end ) {
220                 // it's a name-only cookie ( valid in RFC2109 )
221
if( ! isSpecial ) {
222                     sc=addCookie();
223                     sc.getName().setBytes( bytes, startName,
224                                            endName-startName );
225                     sc.getValue().setString("");
226                     sc.setVersion( version );
227                     if( dbg>0 ) log( "Name only, end: " + startName + " " +
228                                      endName);
229                 }
230                 return;
231             }
232
233             cc=bytes[pos];
234             pos++;
235             if( cc==';' || cc==',' || pos>=end ) {
236                 if( ! isSpecial && startName!= endName ) {
237                     sc=addCookie();
238                     sc.getName().setBytes( bytes, startName,
239                                            endName-startName );
240                     sc.getValue().setString("");
241                     sc.setVersion( version );
242                     if( dbg>0 ) log( "Name only: " + startName + " " + endName);
243                 }
244                 continue;
245             }
246             
247             // we should have "=" ( tested all other alternatives )
248
int startValue=skipSpaces( bytes, pos, end);
249             int endValue=startValue;
250             
251             cc=bytes[pos];
252             if( cc== '\'' || cc=='"' ) {
253                 startValue++;
254                 endValue=indexOf( bytes, startValue, end, cc );
255                 pos=endValue+1; // to skip to next cookie
256
} else {
257                 endValue=findDelim2( bytes, startValue, end );
258                 pos=endValue+1;
259             }
260             
261             // if not $Version, etc
262
if( ! isSpecial ) {
263                 sc=addCookie();
264                 sc.getName().setBytes( bytes, startName, endName-startName );
265                 sc.getValue().setBytes( bytes, startValue, endValue-startValue);
266                 sc.setVersion( version );
267                 if( dbg>0 ) {
268                     log( "New: " + sc.getName() + "X=X" + sc.getValue());
269                 }
270                 continue;
271             }
272             
273             // special - Path, Version, Domain, Port
274
if( dbg>0 ) log( "Special: " + startName + " " + endName);
275             // XXX TODO
276
if( equals( "$Version", bytes, startName, endName ) ) {
277                 if(dbg>0 ) log( "Found version " );
278                 if( bytes[startValue]=='1' && endValue==startValue+1 ) {
279                     version=1;
280                     if(dbg>0 ) log( "Found version=1" );
281                 }
282                 continue;
283             }
284             if( sc==null ) {
285                 // Path, etc without a previous cookie
286
continue;
287             }
288             if( equals( "$Path", bytes, startName, endName ) ) {
289                 sc.getPath().setBytes( bytes,
290                                        startValue,
291                                        endValue-startValue );
292             }
293             if( equals( "$Domain", bytes, startName, endName ) ) {
294                 sc.getDomain().setBytes( bytes,
295                                          startValue,
296                                          endValue-startValue );
297             }
298             if( equals( "$Port", bytes, startName, endName ) ) {
299                 // sc.getPort().setBytes( bytes,
300
// startValue,
301
// endValue-startValue );
302
}
303         }
304     }
305
306     // -------------------- Utils --------------------
307
public static int skipSpaces( byte bytes[], int off, int end ) {
308         while( off < end ) {
309             byte b=bytes[off];
310             if( b!= ' ' ) return off;
311             off ++;
312         }
313         return off;
314     }
315
316     public static int findDelim1( byte bytes[], int off, int end )
317     {
318         while( off < end ) {
319             byte b=bytes[off];
320             if( b==' ' || b=='=' || b==';' || b==',' )
321                 return off;
322             off++;
323         }
324         return off;
325     }
326
327     public static int findDelim2( byte bytes[], int off, int end )
328     {
329         while( off < end ) {
330             byte b=bytes[off];
331             if( b==';' || b==',' )
332                 return off;
333             off++;
334         }
335         return off;
336     }
337
338     public static int indexOf( byte bytes[], int off, int end, byte qq )
339     {
340         while( off < end ) {
341             byte b=bytes[off];
342             if( b==qq )
343                 return off;
344             off++;
345         }
346         return off;
347     }
348
349     public static int indexOf( byte bytes[], int off, int end, char qq )
350     {
351         while( off < end ) {
352             byte b=bytes[off];
353             if( b==qq )
354                 return off;
355             off++;
356         }
357         return off;
358     }
359     
360     // XXX will be refactored soon!
361
public static boolean equals( String JavaDoc s, byte b[], int start, int end) {
362         int blen = end-start;
363         if (b == null || blen != s.length()) {
364             return false;
365         }
366         int boff = start;
367         for (int i = 0; i < blen; i++) {
368             if (b[boff++] != s.charAt(i)) {
369                 return false;
370             }
371         }
372         return true;
373     }
374     
375
376     // ---------------------------------------------------------
377
// -------------------- DEPRECATED, OLD --------------------
378

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

386         StringTokenizer JavaDoc tok = new StringTokenizer JavaDoc(cookieString,
387                                                   ";", false);
388         while (tok.hasMoreTokens()) {
389             String JavaDoc token = tok.nextToken();
390             int i = token.indexOf("=");
391             if (i > -1) {
392                 
393                 // XXX
394
// the trims here are a *hack* -- this should
395
// be more properly fixed to be spec compliant
396

397                 String JavaDoc name = token.substring(0, i).trim();
398                 String JavaDoc value = token.substring(i+1, token.length()).trim();
399                 // RFC 2109 and bug
400
value=stripQuote( value );
401                 ServerCookie cookie = addCookie();
402                 
403                 cookie.getName().setString(name);
404                 cookie.getValue().setString(value);
405                 if( dbg > 0 ) log( "Add cookie " + name + "=" + value);
406             } else {
407                 // we have a bad cookie.... just let it go
408
}
409         }
410     }
411
412     /**
413      *
414      * Strips quotes from the start and end of the cookie string
415      * This conforms to RFC 2109
416      *
417      * @param value a <code>String</code> specifying the cookie
418      * value (possibly quoted).
419      *
420      * @see #setValue
421      *
422      */

423     private static String JavaDoc stripQuote( String JavaDoc value )
424     {
425         // log("Strip quote from " + value );
426
if (((value.startsWith("\"")) && (value.endsWith("\""))) ||
427             ((value.startsWith("'") && (value.endsWith("'"))))) {
428             try {
429                 return value.substring(1,value.length()-1);
430             } catch (Exception JavaDoc ex) {
431             }
432         }
433         return value;
434     }
435
436
437     // log
438
static final int dbg=0;
439     public void log(String JavaDoc s ) {
440         if (log.isDebugEnabled())
441             log.debug("Cookies: " + s);
442     }
443
444     /*
445     public static void main( String args[] ) {
446         test("foo=bar; a=b");
447         test("foo=bar;a=b");
448         test("foo=bar;a=b;");
449         test("foo=bar;a=b; ");
450         test("foo=bar;a=b; ;");
451         test("foo=;a=b; ;");
452         test("foo;a=b; ;");
453         // v1
454         test("$Version=1; foo=bar;a=b");
455         test("$Version=\"1\"; foo='bar'; $Path=/path; $Domain=\"localhost\"");
456         test("$Version=1;foo=bar;a=b; ; ");
457         test("$Version=1;foo=;a=b; ; ");
458         test("$Version=1;foo= ;a=b; ; ");
459         test("$Version=1;foo;a=b; ; ");
460         test("$Version=1;foo=\"bar\";a=b; ; ");
461         test("$Version=1;foo=\"bar\";$Path=/examples;a=b; ; ");
462         test("$Version=1;foo=\"bar\";$Domain=apache.org;a=b");
463         test("$Version=1;foo=\"bar\";$Domain=apache.org;a=b;$Domain=yahoo.com");
464         // rfc2965
465         test("$Version=1;foo=\"bar\";$Domain=apache.org;$Port=8080;a=b");
466
467         // wrong
468         test("$Version=1;foo=\"bar\";$Domain=apache.org;$Port=8080;a=b");
469     }
470
471     public static void test( String s ) {
472         System.out.println("Processing " + s );
473         Cookies cs=new Cookies(null);
474         cs.processCookieHeader( s.getBytes(), 0, s.length());
475         for( int i=0; i< cs.getCookieCount() ; i++ ) {
476             System.out.println("Cookie: " + cs.getCookie( i ));
477         }
478             
479     }
480     */

481
482 }
483
Popular Tags