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

Home » apache-tomcat-6.0.26-src » org.apache » tomcat » util » http » [javadoc | source]