Save This Page
Home » apache-tomcat-6.0.16-src » org.apache » tomcat » util » http » [javadoc | source]
    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;
   21   import java.io.StringWriter;
   22   import java.util.StringTokenizer;
   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.juli.logging.Log log=
   39           org.apache.juli.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       List of Separator Characters (see isSeparator())
   51       Excluding the '/' char violates the RFC, but 
   52       it looks like a lot of people put '/'
   53       in unquoted values: '/': ; //47 
   54       '\t':9 ' ':32 '\"':34 '\'':39 '(':40 ')':41 ',':44 ':':58 ';':59 '<':60 
   55       '=':61 '>':62 '?':63 '@':64 '[':91 '\\':92 ']':93 '{':123 '}':125
   56       */
   57       public static final char SEPARATORS[] = { '\t', ' ', '\"', '\'', '(', ')', ',', 
   58           ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '{', '}' };
   59   
   60       protected static final boolean separators[] = new boolean[128];
   61       static {
   62           for (int i = 0; i < 128; i++) {
   63               separators[i] = false;
   64           }
   65           for (int i = 0; i < SEPARATORS.length; i++) {
   66               separators[SEPARATORS[i]] = true;
   67           }
   68       }
   69   
   70       /**
   71        *  Construct a new cookie collection, that will extract
   72        *  the information from headers.
   73        *
   74        * @param headers Cookies are lazy-evaluated and will extract the
   75        *     information from the provided headers.
   76        */
   77       public Cookies(MimeHeaders headers) {
   78           this.headers=headers;
   79       }
   80   
   81       /**
   82        * Construct a new uninitialized cookie collection.
   83        * Use {@link #setHeaders} to initialize.
   84        */
   85       // [seguin] added so that an empty Cookies object could be
   86       // created, have headers set, then recycled.
   87       public Cookies() {
   88       }
   89   
   90       /**
   91        * Set the headers from which cookies will be pulled.
   92        * This has the side effect of recycling the object.
   93        *
   94        * @param headers Cookies are lazy-evaluated and will extract the
   95        *     information from the provided headers.
   96        */
   97       // [seguin] added so that an empty Cookies object could be
   98       // created, have headers set, then recycled.
   99       public void setHeaders(MimeHeaders headers) {
  100           recycle();
  101           this.headers=headers;
  102       }
  103   
  104       /**
  105        * Recycle.
  106        */
  107       public void recycle() {
  108               for( int i=0; i< cookieCount; i++ ) {
  109               if( scookies[i]!=null )
  110                   scookies[i].recycle();
  111           }
  112           cookieCount=0;
  113           unprocessed=true;
  114       }
  115   
  116       /**
  117        * EXPENSIVE!!!  only for debugging.
  118        */
  119       public String toString() {
  120           StringWriter sw = new StringWriter();
  121           PrintWriter pw = new PrintWriter(sw);
  122           pw.println("=== Cookies ===");
  123           int count = getCookieCount();
  124           for (int i = 0; i < count; ++i) {
  125               pw.println(getCookie(i).toString());
  126           }
  127           return sw.toString();
  128       }
  129   
  130       // -------------------- Indexed access --------------------
  131       
  132       public ServerCookie getCookie( int idx ) {
  133           if( unprocessed ) {
  134               getCookieCount(); // will also update the cookies
  135           }
  136           return scookies[idx];
  137       }
  138   
  139       public int getCookieCount() {
  140           if( unprocessed ) {
  141               unprocessed=false;
  142               processCookies(headers);
  143           }
  144           return cookieCount;
  145       }
  146   
  147       // -------------------- Adding cookies --------------------
  148   
  149       /** Register a new, unitialized cookie. Cookies are recycled, and
  150        *  most of the time an existing ServerCookie object is returned.
  151        *  The caller can set the name/value and attributes for the cookie
  152        */
  153       public ServerCookie addCookie() {
  154           if( cookieCount >= scookies.length  ) {
  155               ServerCookie scookiesTmp[]=new ServerCookie[2*cookieCount];
  156               System.arraycopy( scookies, 0, scookiesTmp, 0, cookieCount);
  157               scookies=scookiesTmp;
  158           }
  159           
  160           ServerCookie c = scookies[cookieCount];
  161           if( c==null ) {
  162               c= new ServerCookie();
  163               scookies[cookieCount]=c;
  164           }
  165           cookieCount++;
  166           return c;
  167       }
  168   
  169   
  170       // code from CookieTools 
  171   
  172       /** Add all Cookie found in the headers of a request.
  173        */
  174       public  void processCookies( MimeHeaders headers ) {
  175           if( headers==null )
  176               return;// nothing to process
  177           // process each "cookie" header
  178           int pos=0;
  179           while( pos>=0 ) {
  180               // Cookie2: version ? not needed
  181               pos=headers.findHeader( "Cookie", pos );
  182               // no more cookie headers headers
  183               if( pos<0 ) break;
  184   
  185               MessageBytes cookieValue=headers.getValue( pos );
  186               if( cookieValue==null || cookieValue.isNull() ) {
  187                   pos++;
  188                   continue;
  189               }
  190   
  191               // Uncomment to test the new parsing code
  192               if( cookieValue.getType() == MessageBytes.T_BYTES ) {
  193                   if( dbg>0 ) log( "Parsing b[]: " + cookieValue.toString());
  194                   ByteChunk bc=cookieValue.getByteChunk();
  195                   processCookieHeader( bc.getBytes(),
  196                                        bc.getOffset(),
  197                                        bc.getLength());
  198               } else {
  199                   if( dbg>0 ) log( "Parsing S: " + cookieValue.toString());
  200                   processCookieHeader( cookieValue.toString() );
  201               }
  202               pos++;// search from the next position
  203           }
  204       }
  205   
  206       // XXX will be refactored soon!
  207       public static boolean equals( String s, byte b[], int start, int end) {
  208           int blen = end-start;
  209           if (b == null || blen != s.length()) {
  210               return false;
  211           }
  212           int boff = start;
  213           for (int i = 0; i < blen; i++) {
  214               if (b[boff++] != s.charAt(i)) {
  215                   return false;
  216               }
  217           }
  218           return true;
  219       }
  220       
  221   
  222       // ---------------------------------------------------------
  223       // -------------------- DEPRECATED, OLD --------------------
  224       
  225       private void processCookieHeader(  String cookieString )
  226       {
  227           if( dbg>0 ) log( "Parsing cookie header " + cookieString );
  228           // normal cookie, with a string value.
  229           // This is the original code, un-optimized - it shouldn't
  230           // happen in normal case
  231   
  232           StringTokenizer tok = new StringTokenizer(cookieString,
  233                                                     ";", false);
  234           while (tok.hasMoreTokens()) {
  235               String token = tok.nextToken();
  236               int i = token.indexOf("=");
  237               if (i > -1) {
  238                   
  239                   // XXX
  240                   // the trims here are a *hack* -- this should
  241                   // be more properly fixed to be spec compliant
  242                   
  243                   String name = token.substring(0, i).trim();
  244                   String value = token.substring(i+1, token.length()).trim();
  245                   // RFC 2109 and bug 
  246                   value=stripQuote( value );
  247                   ServerCookie cookie = addCookie();
  248                   
  249                   cookie.getName().setString(name);
  250                   cookie.getValue().setString(value);
  251                   if( dbg > 0 ) log( "Add cookie " + name + "=" + value);
  252               } else {
  253                   // we have a bad cookie.... just let it go
  254               }
  255           }
  256       }
  257   
  258       /**
  259        *
  260        * Strips quotes from the start and end of the cookie string
  261        * This conforms to RFC 2965
  262        * 
  263        * @param value            a <code>String</code> specifying the cookie 
  264        *                         value (possibly quoted).
  265        *
  266        * @see #setValue
  267        *
  268        */
  269       private static String stripQuote( String value )
  270       {
  271           //        log("Strip quote from " + value );
  272           if (value.startsWith("\"") && value.endsWith("\"")) {
  273               try {
  274                   return value.substring(1,value.length()-1);
  275               } catch (Exception ex) { 
  276               }
  277           }
  278           return value;
  279       }  
  280   
  281   
  282       // log
  283       static final int dbg=0;
  284       public void log(String s ) {
  285           if (log.isDebugEnabled())
  286               log.debug("Cookies: " + s);
  287       }
  288   
  289   
  290      /**
  291        * Returns true if the byte is a separator character as
  292        * defined in RFC2619. Since this is called often, this
  293        * function should be organized with the most probable
  294        * outcomes first.
  295        * JVK
  296        */
  297       public static final boolean isSeparator(final byte c) {
  298            if (c > 0 && c < 126)
  299                return separators[c];
  300            else
  301                return false;
  302       }
  303       
  304       /**
  305        * Returns true if the byte is a whitespace character as
  306        * defined in RFC2619
  307        * JVK
  308        */
  309       public static final boolean isWhiteSpace(final byte c) {
  310           // This switch statement is slightly slower
  311           // for my vm than the if statement.
  312           // Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_07-164)
  313           /* 
  314           switch (c) {
  315           case ' ':;
  316           case '\t':;
  317           case '\n':;
  318           case '\r':;
  319           case '\f':;
  320               return true;
  321           default:;
  322               return false;
  323           }
  324           */
  325          if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f')
  326              return true;
  327          else
  328              return false;
  329       }
  330   
  331       /**
  332        * Parses a cookie header after the initial "Cookie:"
  333        * [WS][$]token[WS]=[WS](token|QV)[;|,]
  334        * RFC 2965
  335        * JVK
  336        */
  337       public final void processCookieHeader(byte bytes[], int off, int len){
  338           if( len<=0 || bytes==null ) return;
  339           int end=off+len;
  340           int pos=off;
  341           int nameStart=0;
  342           int nameEnd=0;
  343           int valueStart=0;
  344           int valueEnd=0;
  345           int version = 0;
  346           ServerCookie sc=null;
  347           boolean isSpecial;
  348           boolean isQuoted;
  349   
  350           while (pos < end) {
  351               isSpecial = false;
  352               isQuoted = false;
  353   
  354               // Skip whitespace and non-token characters (separators)
  355               while (pos < end && 
  356                      (isSeparator(bytes[pos]) || isWhiteSpace(bytes[pos]))) 
  357                   {pos++; } 
  358   
  359               if (pos >= end)
  360                   return;
  361   
  362               // Detect Special cookies
  363               if (bytes[pos] == '$') {
  364                   isSpecial = true;
  365                   pos++;
  366               }
  367   
  368               // Get the cookie name. This must be a token            
  369               valueEnd = valueStart = nameStart = pos; 
  370               pos = nameEnd = getTokenEndPosition(bytes,pos,end);
  371   
  372               // Skip whitespace
  373               while (pos < end && isWhiteSpace(bytes[pos])) {pos++; }; 
  374            
  375   
  376               // Check for an '=' -- This could also be a name-only
  377               // cookie at the end of the cookie header, so if we
  378               // are past the end of the header, but we have a name
  379               // skip to the name-only part.
  380               if (pos < end && bytes[pos] == '=') {                
  381   
  382                   // Skip whitespace
  383                   do {
  384                       pos++;
  385                   } while (pos < end && isWhiteSpace(bytes[pos])); 
  386   
  387                   if (pos >= end)
  388                       return;
  389   
  390                   // Determine what type of value this is, quoted value,
  391                   // token, name-only with an '=', or other (bad)
  392                   switch (bytes[pos]) {
  393                   case '"':; // Quoted Value
  394                       isQuoted = true;
  395                       valueStart=pos + 1; // strip "
  396                       // getQuotedValue returns the position before 
  397                       // at the last qoute. This must be dealt with
  398                       // when the bytes are copied into the cookie
  399                       valueEnd=getQuotedValueEndPosition(bytes, 
  400                                                          valueStart, end);
  401                       // We need pos to advance
  402                       pos = valueEnd; 
  403                       // Handles cases where the quoted value is 
  404                       // unterminated and at the end of the header, 
  405                       // e.g. [myname="value]
  406                       if (pos >= end)
  407                           return;
  408                       break;
  409                   case ';':
  410                   case ',':
  411                       // Name-only cookie with an '=' after the name token
  412                       // This may not be RFC compliant
  413                       valueStart = valueEnd = -1;
  414                       // The position is OK (On a delimiter)
  415                       break;
  416                   default:;
  417                       if (!isSeparator(bytes[pos])) {
  418                           // Token
  419                           valueStart=pos;
  420                           // getToken returns the position at the delimeter
  421                           // or other non-token character
  422                           valueEnd=getTokenEndPosition(bytes, valueStart, end);
  423                           // We need pos to advance
  424                           pos = valueEnd;
  425                       } else  {
  426                           // INVALID COOKIE, advance to next delimiter
  427                           // The starting character of the cookie value was
  428                           // not valid.
  429                           log("Invalid cookie. Value not a token or quoted value");
  430                           while (pos < end && bytes[pos] != ';' && 
  431                                  bytes[pos] != ',') 
  432                               {pos++; };
  433                           pos++;
  434                           // Make sure no special avpairs can be attributed to 
  435                           // the previous cookie by setting the current cookie
  436                           // to null
  437                           sc = null;
  438                           continue;                        
  439                       }
  440                   }
  441               } else {
  442                   // Name only cookie
  443                   valueStart = valueEnd = -1;
  444                   pos = nameEnd;
  445   
  446               }
  447             
  448               // We should have an avpair or name-only cookie at this
  449               // point. Perform some basic checks to make sure we are
  450               // in a good state.
  451     
  452               // Skip whitespace
  453               while (pos < end && isWhiteSpace(bytes[pos])) {pos++; }; 
  454   
  455   
  456               // Make sure that after the cookie we have a separator. This
  457               // is only important if this is not the last cookie pair
  458               while (pos < end && bytes[pos] != ';' && bytes[pos] != ',') { 
  459                   pos++;
  460               }
  461               
  462               pos++;
  463   
  464               /*
  465               if (nameEnd <= nameStart || valueEnd < valueStart ) {
  466                   // Something is wrong, but this may be a case
  467                   // of having two ';' characters in a row.
  468                   // log("Cookie name/value does not conform to RFC 2965");
  469                   // Advance to next delimiter (ignoring everything else)
  470                   while (pos < end && bytes[pos] != ';' && bytes[pos] != ',') 
  471                       { pos++; };
  472                   pos++;
  473                   // Make sure no special cookies can be attributed to 
  474                   // the previous cookie by setting the current cookie
  475                   // to null
  476                   sc = null;
  477                   continue;
  478               }
  479               */
  480   
  481               // All checks passed. Add the cookie, start with the 
  482               // special avpairs first
  483               if (isSpecial) {
  484                   isSpecial = false;
  485                   // $Version must be the first avpair in the cookie header
  486                   // (sc must be null)
  487                   if (equals( "Version", bytes, nameStart, nameEnd) && 
  488                       sc == null) {
  489                       // Set version
  490                       if( bytes[valueStart] =='1' && valueEnd == (valueStart+1)) {
  491                           version=1;
  492                       } else {
  493                           // unknown version (Versioning is not very strict)
  494                       }
  495                       continue;
  496                   } 
  497                   
  498                   // We need an active cookie for Path/Port/etc.
  499                   if (sc == null) {
  500                       continue;
  501                   }
  502   
  503                   // Domain is more common, so it goes first
  504                   if (equals( "Domain", bytes, nameStart, nameEnd)) {
  505                       sc.getDomain().setBytes( bytes,
  506                                              valueStart,
  507                                              valueEnd-valueStart);
  508                       continue;
  509                   } 
  510   
  511                   if (equals( "Path", bytes, nameStart, nameEnd)) {
  512                       sc.getPath().setBytes( bytes,
  513                                              valueStart,
  514                                              valueEnd-valueStart);
  515                       continue;
  516                   } 
  517   
  518   
  519                   if (equals( "Port", bytes, nameStart, nameEnd)) {
  520                       // sc.getPort is not currently implemented.
  521                       // sc.getPort().setBytes( bytes,
  522                       //                        valueStart,
  523                       //                        valueEnd-valueStart );
  524                       continue;
  525                   } 
  526   
  527                   // Unknown cookie, complain
  528                   log("Unknown Special Cookie");
  529   
  530               } else { // Normal Cookie
  531                   sc = addCookie();
  532                   sc.setVersion( version );
  533                   sc.getName().setBytes( bytes, nameStart,
  534                                          nameEnd-nameStart);
  535                   
  536                   if (valueStart != -1) { // Normal AVPair
  537                       sc.getValue().setBytes( bytes, valueStart,
  538                               valueEnd-valueStart);
  539                       if (isQuoted) {
  540                           // We know this is a byte value so this is safe
  541                           ServerCookie.unescapeDoubleQuotes(
  542                                   sc.getValue().getByteChunk());
  543                       }
  544                   } else {
  545                       // Name Only
  546                       sc.getValue().setString(""); 
  547                   }
  548                   continue;
  549               }
  550           }
  551       }
  552   
  553       /**
  554        * Given the starting position of a token, this gets the end of the
  555        * token, with no separator characters in between.
  556        * JVK
  557        */
  558       public static final int getTokenEndPosition(byte bytes[], int off, int end){
  559           int pos = off;
  560           while (pos < end && !isSeparator(bytes[pos])) {pos++; };
  561           
  562           if (pos > end)
  563               return end;
  564           return pos;
  565       }
  566   
  567       /** 
  568        * Given a starting position after an initial quote chracter, this gets
  569        * the position of the end quote. This escapes anything after a '\' char
  570        * JVK RFC 2616
  571        */
  572       public static final int getQuotedValueEndPosition(byte bytes[], int off, int end){
  573           int pos = off;
  574           while (pos < end) {
  575               if (bytes[pos] == '"') {
  576                   return pos;                
  577               } else if (bytes[pos] == '\\' && pos < (end - 1)) {
  578                   pos+=2;
  579               } else {
  580                   pos++;
  581               }
  582           }
  583           // Error, we have reached the end of the header w/o a end quote
  584           return end;
  585       }
  586   
  587   }

Save This Page
Home » apache-tomcat-6.0.16-src » org.apache » tomcat » util » http » [javadoc | source]