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.IOException;
   21   import java.io.UnsupportedEncodingException;
   22   import java.util.Enumeration;
   23   import java.util.Hashtable;
   24   
   25   import org.apache.tomcat.util.buf.ByteChunk;
   26   import org.apache.tomcat.util.buf.CharChunk;
   27   import org.apache.tomcat.util.buf.MessageBytes;
   28   import org.apache.tomcat.util.buf.UDecoder;
   29   import org.apache.tomcat.util.collections.MultiMap;
   30   
   31   /**
   32    * 
   33    * @author Costin Manolache
   34    */
   35   public final class Parameters extends MultiMap {
   36   
   37       
   38       private static org.apache.juli.logging.Log log=
   39           org.apache.juli.logging.LogFactory.getLog(Parameters.class );
   40       
   41       // Transition: we'll use the same Hashtable( String->String[] )
   42       // for the beginning. When we are sure all accesses happen through
   43       // this class - we can switch to MultiMap
   44       private Hashtable<String,String[]> paramHashStringArray =
   45           new Hashtable<String,String[]>();
   46       private boolean didQueryParameters=false;
   47       private boolean didMerge=false;
   48       
   49       MessageBytes queryMB;
   50       MimeHeaders  headers;
   51   
   52       UDecoder urlDec;
   53       MessageBytes decodedQuery=MessageBytes.newInstance();
   54       
   55       public static final int INITIAL_SIZE=4;
   56   
   57       // Garbage-less parameter merging.
   58       // In a sub-request with parameters, the new parameters
   59       // will be stored in child. When a getParameter happens,
   60       // the 2 are merged togheter. The child will be altered
   61       // to contain the merged values - the parent is allways the
   62       // original request.
   63       private Parameters child=null;
   64       private Parameters parent=null;
   65       private Parameters currentChild=null;
   66   
   67       String encoding=null;
   68       String queryStringEncoding=null;
   69       
   70       /**
   71        * 
   72        */
   73       public Parameters() {
   74           super( INITIAL_SIZE );
   75       }
   76   
   77       public void setQuery( MessageBytes queryMB ) {
   78           this.queryMB=queryMB;
   79       }
   80   
   81       public void setHeaders( MimeHeaders headers ) {
   82           this.headers=headers;
   83       }
   84   
   85       public void setEncoding( String s ) {
   86           encoding=s;
   87           if(debug>0) log( "Set encoding to " + s );
   88       }
   89   
   90       public void setQueryStringEncoding( String s ) {
   91           queryStringEncoding=s;
   92           if(debug>0) log( "Set query string encoding to " + s );
   93       }
   94   
   95       public void recycle() {
   96           super.recycle();
   97           paramHashStringArray.clear();
   98           didQueryParameters=false;
   99           currentChild=null;
  100           didMerge=false;
  101           encoding=null;
  102           decodedQuery.recycle();
  103       }
  104       
  105       // -------------------- Sub-request support --------------------
  106   
  107       public Parameters getCurrentSet() {
  108           if( currentChild==null )
  109               return this;
  110           return currentChild;
  111       }
  112       
  113       /** Create ( or reuse ) a child that will be used during a sub-request.
  114           All future changes ( setting query string, adding parameters )
  115           will affect the child ( the parent request is never changed ).
  116           Both setters and getters will return the data from the deepest
  117           child, merged with data from parents.
  118       */
  119       public void push() {
  120           // We maintain a linked list, that will grow to the size of the
  121           // longest include chain.
  122           // The list has 2 points of interest:
  123           // - request.parameters() is the original request and head,
  124           // - request.parameters().currentChild() is the current set.
  125           // The ->child and parent<- links are preserved ( currentChild is not
  126           // the last in the list )
  127           
  128           // create a new element in the linked list
  129           // note that we reuse the child, if any - pop will not
  130           // set child to null !
  131           if( currentChild==null ) {
  132               currentChild=new Parameters();
  133               currentChild.setURLDecoder( urlDec );
  134               currentChild.parent=this;
  135               return;
  136           }
  137           if( currentChild.child==null ) {
  138               currentChild.child=new Parameters();
  139               currentChild.setURLDecoder( urlDec );
  140               currentChild.child.parent=currentChild;
  141           } // it is not null if this object already had a child
  142           // i.e. a deeper include() ( we keep it )
  143   
  144           // the head will be the new element.
  145           currentChild=currentChild.child;
  146           currentChild.setEncoding( encoding );
  147       }
  148   
  149       /** Discard the last child. This happens when we return from a
  150           sub-request and the parameters are locally modified.
  151        */
  152       public void pop() {
  153           if( currentChild==null ) {
  154               throw new RuntimeException( "Attempt to pop without a push" );
  155           }
  156           currentChild.recycle();
  157           currentChild=currentChild.parent;
  158           // don't remove the top.
  159       }
  160       
  161       // -------------------- Data access --------------------
  162       // Access to the current name/values, no side effect ( processing ).
  163       // You must explicitely call handleQueryParameters and the post methods.
  164       
  165       // This is the original data representation ( hash of String->String[])
  166   
  167       public void addParameterValues( String key, String[] newValues) {
  168           if ( key==null ) return;
  169           String values[];
  170           if (paramHashStringArray.containsKey(key)) {
  171               String oldValues[] = (String[])paramHashStringArray.get(key);
  172               values = new String[oldValues.length + newValues.length];
  173               for (int i = 0; i < oldValues.length; i++) {
  174                   values[i] = oldValues[i];
  175               }
  176               for (int i = 0; i < newValues.length; i++) {
  177                   values[i+ oldValues.length] = newValues[i];
  178               }
  179           } else {
  180               values = newValues;
  181           }
  182   
  183           paramHashStringArray.put(key, values);
  184       }
  185   
  186       public String[] getParameterValues(String name) {
  187           handleQueryParameters();
  188           // sub-request
  189           if( currentChild!=null ) {
  190               currentChild.merge();
  191               return (String[])currentChild.paramHashStringArray.get(name);
  192           }
  193   
  194           // no "facade"
  195           String values[]=(String[])paramHashStringArray.get(name);
  196           return values;
  197       }
  198    
  199       public Enumeration getParameterNames() {
  200           handleQueryParameters();
  201           // Slow - the original code
  202           if( currentChild!=null ) {
  203               currentChild.merge();
  204               return currentChild.paramHashStringArray.keys();
  205           }
  206   
  207           // merge in child
  208           return paramHashStringArray.keys();
  209       }
  210   
  211       /** Combine the parameters from parent with our local ones
  212        */
  213       private void merge() {
  214           // recursive
  215           if( debug > 0 ) {
  216               log("Before merging " + this + " " + parent + " " + didMerge );
  217               log(  paramsAsString());
  218           }
  219           // Local parameters first - they take precedence as in spec.
  220           handleQueryParameters();
  221   
  222           // we already merged with the parent
  223           if( didMerge ) return;
  224   
  225           // we are the top level
  226           if( parent==null ) return;
  227   
  228           // Add the parent props to the child ( lower precedence )
  229           parent.merge();
  230           Hashtable<String,String[]> parentProps=parent.paramHashStringArray;
  231           merge2( paramHashStringArray , parentProps);
  232           didMerge=true;
  233           if(debug > 0 )
  234               log("After " + paramsAsString());
  235       }
  236   
  237   
  238       // Shortcut.
  239       public String getParameter(String name ) {
  240           String[] values = getParameterValues(name);
  241           if (values != null) {
  242               if( values.length==0 ) return "";
  243               return values[0];
  244           } else {
  245               return null;
  246           }
  247       }
  248       // -------------------- Processing --------------------
  249       /** Process the query string into parameters
  250        */
  251       public void handleQueryParameters() {
  252           if( didQueryParameters ) return;
  253   
  254           didQueryParameters=true;
  255   
  256           if( queryMB==null || queryMB.isNull() )
  257               return;
  258           
  259           if( debug > 0  )
  260               log( "Decoding query " + decodedQuery + " " + queryStringEncoding);
  261   
  262           try {
  263               decodedQuery.duplicate( queryMB );
  264           } catch (IOException e) {
  265               // Can't happen, as decodedQuery can't overflow
  266               e.printStackTrace();
  267           }
  268           processParameters( decodedQuery, queryStringEncoding );
  269       }
  270   
  271       // --------------------
  272       
  273       /** Combine 2 hashtables into a new one.
  274        *  ( two will be added to one ).
  275        *  Used to combine child parameters ( RequestDispatcher's query )
  276        *  with parent parameters ( original query or parent dispatcher )
  277        */
  278       private static void merge2(Hashtable<String,String[]> one,
  279               Hashtable<String,String[]> two ) {
  280           Enumeration e = two.keys();
  281   
  282           while (e.hasMoreElements()) {
  283               String name = (String) e.nextElement();
  284               String[] oneValue = one.get(name);
  285               String[] twoValue = two.get(name);
  286               String[] combinedValue;
  287   
  288               if (twoValue == null) {
  289                   continue;
  290               } else {
  291                   if( oneValue==null ) {
  292                       combinedValue = new String[twoValue.length];
  293                       System.arraycopy(twoValue, 0, combinedValue,
  294                                        0, twoValue.length);
  295                   } else {
  296                       combinedValue = new String[oneValue.length +
  297                                                  twoValue.length];
  298                       System.arraycopy(oneValue, 0, combinedValue, 0,
  299                                        oneValue.length);
  300                       System.arraycopy(twoValue, 0, combinedValue,
  301                                        oneValue.length, twoValue.length);
  302                   }
  303                   one.put(name, combinedValue);
  304               }
  305           }
  306       }
  307   
  308       // incredibly inefficient data representation for parameters,
  309       // until we test the new one
  310       private void addParam( String key, String value ) {
  311           if( key==null ) return;
  312           String values[];
  313           if (paramHashStringArray.containsKey(key)) {
  314               String oldValues[] = (String[])paramHashStringArray.
  315                   get(key);
  316               values = new String[oldValues.length + 1];
  317               for (int i = 0; i < oldValues.length; i++) {
  318                   values[i] = oldValues[i];
  319               }
  320               values[oldValues.length] = value;
  321           } else {
  322               values = new String[1];
  323               values[0] = value;
  324           }
  325           
  326           
  327           paramHashStringArray.put(key, values);
  328       }
  329   
  330       public void setURLDecoder( UDecoder u ) {
  331           urlDec=u;
  332       }
  333   
  334       // -------------------- Parameter parsing --------------------
  335   
  336       // This code is not used right now - it's the optimized version
  337       // of the above.
  338   
  339       // we are called from a single thread - we can do it the hard way
  340       // if needed
  341       ByteChunk tmpName=new ByteChunk();
  342       ByteChunk tmpValue=new ByteChunk();
  343       private ByteChunk origName=new ByteChunk();
  344       private ByteChunk origValue=new ByteChunk();
  345       CharChunk tmpNameC=new CharChunk(1024);
  346       CharChunk tmpValueC=new CharChunk(1024);
  347       private static final String DEFAULT_ENCODING = "ISO-8859-1";
  348       
  349       public void processParameters( byte bytes[], int start, int len ) {
  350           processParameters(bytes, start, len, encoding);
  351       }
  352   
  353       public void processParameters( byte bytes[], int start, int len, 
  354                                      String enc ) {
  355           int end=start+len;
  356           int pos=start;
  357           
  358           if(log.isDebugEnabled()) {
  359               try {
  360                   log.debug("Bytes: " +
  361                           new String(bytes, start, len, DEFAULT_ENCODING));
  362               } catch (UnsupportedEncodingException e) {
  363                   // Should never happen...
  364                   log.error("Unable to convert bytes", e);
  365               }
  366           }
  367   
  368           do {
  369               boolean noEq=false;
  370               int valStart=-1;
  371               int valEnd=-1;
  372               
  373               int nameStart=pos;
  374               int nameEnd=ByteChunk.indexOf(bytes, nameStart, end, '=' );
  375               // Workaround for a&b&c encoding
  376               int nameEnd2=ByteChunk.indexOf(bytes, nameStart, end, '&' );
  377               if( (nameEnd2!=-1 ) &&
  378                   ( nameEnd==-1 || nameEnd > nameEnd2) ) {
  379                   nameEnd=nameEnd2;
  380                   noEq=true;
  381                   valStart=nameEnd;
  382                   valEnd=nameEnd;
  383                   if(log.isDebugEnabled()) {
  384                       try {
  385                           log.debug("no equal " + nameStart + " " + nameEnd + " " +
  386                                   new String(bytes, nameStart, nameEnd-nameStart,
  387                                           DEFAULT_ENCODING) );
  388                       } catch (UnsupportedEncodingException e) {
  389                           // Should never happen...
  390                           log.error("Unable to convert bytes", e);
  391                       }
  392                   }
  393               }
  394               if( nameEnd== -1 ) 
  395                   nameEnd=end;
  396   
  397               if( ! noEq ) {
  398                   valStart= (nameEnd < end) ? nameEnd+1 : end;
  399                   valEnd=ByteChunk.indexOf(bytes, valStart, end, '&');
  400                   if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
  401               }
  402               
  403               pos=valEnd+1;
  404               
  405               if( nameEnd<=nameStart ) {
  406                   StringBuilder msg = new StringBuilder("Parameters: Invalid chunk ");
  407                   // No name eg ...&=xx&... will trigger this
  408                   if (valEnd >= nameStart) {
  409                       msg.append('\'');
  410                       try {
  411                           msg.append(new String(bytes, nameStart,
  412                                   valEnd - nameStart, DEFAULT_ENCODING));
  413                       } catch (UnsupportedEncodingException e) {
  414                           // Should never happen...
  415                           log.error("Unable to convert bytes", e);
  416                       }
  417                       msg.append("' ");
  418                   }
  419                   msg.append("ignored.");
  420                   log.warn(msg);
  421                   continue;
  422                   // invalid chunk - it's better to ignore
  423               }
  424               tmpName.setBytes( bytes, nameStart, nameEnd-nameStart );
  425               tmpValue.setBytes( bytes, valStart, valEnd-valStart );
  426               
  427               // Take copies as if anything goes wrong originals will be
  428               // corrupted. This means original values can be logged.
  429               // For performance - only done for debug
  430               if (log.isDebugEnabled()) {
  431                   try {
  432                       origName.append(bytes, nameStart, nameEnd-nameStart);
  433                       origValue.append(bytes, valStart, valEnd-valStart);
  434                   } catch (IOException ioe) {
  435                       // Should never happen...
  436                       log.error("Error copying parameters", ioe);
  437                   }
  438               }
  439               
  440               try {
  441                   addParam( urlDecode(tmpName, enc), urlDecode(tmpValue, enc) );
  442               } catch (IOException e) {
  443                   StringBuilder msg =
  444                       new StringBuilder("Parameters: Character decoding failed.");
  445                   msg.append(" Parameter '");
  446                   if (log.isDebugEnabled()) {
  447                       msg.append(origName.toString());
  448                       msg.append("' with value '");
  449                       msg.append(origValue.toString());
  450                       msg.append("' has been ignored.");
  451                       log.debug(msg, e);
  452                   } else {
  453                       msg.append(tmpName.toString());
  454                       msg.append("' with value '");
  455                       msg.append(tmpValue.toString());
  456                       msg.append("' has been ignored. Note that the name and ");
  457                       msg.append("value quoted here may corrupted due to the ");
  458                       msg.append("failed decoding. Use debug level logging to ");
  459                       msg.append("see the original, non-corrupted values.");
  460                       log.warn(msg);
  461                   }
  462               }
  463   
  464               tmpName.recycle();
  465               tmpValue.recycle();
  466               // Only recycle copies if we used them
  467               if (log.isDebugEnabled()) {
  468                   origName.recycle();
  469                   origValue.recycle();
  470               }
  471           } while( pos<end );
  472       }
  473   
  474       private String urlDecode(ByteChunk bc, String enc)
  475           throws IOException {
  476           if( urlDec==null ) {
  477               urlDec=new UDecoder();   
  478           }
  479           urlDec.convert(bc);
  480           String result = null;
  481           if (enc != null) {
  482               bc.setEncoding(enc);
  483               result = bc.toString();
  484           } else {
  485               CharChunk cc = tmpNameC;
  486               int length = bc.getLength();
  487               cc.allocate(length, -1);
  488               // Default encoding: fast conversion
  489               byte[] bbuf = bc.getBuffer();
  490               char[] cbuf = cc.getBuffer();
  491               int start = bc.getStart();
  492               for (int i = 0; i < length; i++) {
  493                   cbuf[i] = (char) (bbuf[i + start] & 0xff);
  494               }
  495               cc.setChars(cbuf, 0, length);
  496               result = cc.toString();
  497               cc.recycle();
  498           }
  499           return result;
  500       }
  501   
  502       public void processParameters( char chars[], int start, int len ) {
  503           int end=start+len;
  504           int pos=start;
  505           
  506           if( debug>0 ) 
  507               log( "Chars: " + new String( chars, start, len ));
  508           do {
  509               boolean noEq=false;
  510               int nameStart=pos;
  511               int valStart=-1;
  512               int valEnd=-1;
  513               
  514               int nameEnd=CharChunk.indexOf(chars, nameStart, end, '=' );
  515               int nameEnd2=CharChunk.indexOf(chars, nameStart, end, '&' );
  516               if( (nameEnd2!=-1 ) &&
  517                   ( nameEnd==-1 || nameEnd > nameEnd2) ) {
  518                   nameEnd=nameEnd2;
  519                   noEq=true;
  520                   valStart=nameEnd;
  521                   valEnd=nameEnd;
  522                   if( debug>0) log("no equal " + nameStart + " " + nameEnd + " " + new String(chars, nameStart, nameEnd-nameStart) );
  523               }
  524               if( nameEnd== -1 ) nameEnd=end;
  525               
  526               if( ! noEq ) {
  527                   valStart= (nameEnd < end) ? nameEnd+1 : end;
  528                   valEnd=CharChunk.indexOf(chars, valStart, end, '&');
  529                   if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
  530               }
  531               
  532               pos=valEnd+1;
  533               
  534               if( nameEnd<=nameStart ) {
  535                   continue;
  536                   // invalid chunk - no name, it's better to ignore
  537                   // XXX log it ?
  538               }
  539               
  540               try {
  541                   tmpNameC.append( chars, nameStart, nameEnd-nameStart );
  542                   tmpValueC.append( chars, valStart, valEnd-valStart );
  543   
  544                   if( debug > 0 )
  545                       log( tmpNameC + "= " + tmpValueC);
  546   
  547                   if( urlDec==null ) {
  548                       urlDec=new UDecoder();   
  549                   }
  550   
  551                   urlDec.convert( tmpNameC );
  552                   urlDec.convert( tmpValueC );
  553   
  554                   if( debug > 0 )
  555                       log( tmpNameC + "= " + tmpValueC);
  556                   
  557                   addParam( tmpNameC.toString(), tmpValueC.toString() );
  558               } catch( IOException ex ) {
  559                   ex.printStackTrace();
  560               }
  561   
  562               tmpNameC.recycle();
  563               tmpValueC.recycle();
  564   
  565           } while( pos<end );
  566       }
  567       
  568       public void processParameters( MessageBytes data ) {
  569           processParameters(data, encoding);
  570       }
  571   
  572       public void processParameters( MessageBytes data, String encoding ) {
  573           if( data==null || data.isNull() || data.getLength() <= 0 ) return;
  574   
  575           if( data.getType() == MessageBytes.T_BYTES ) {
  576               ByteChunk bc=data.getByteChunk();
  577               processParameters( bc.getBytes(), bc.getOffset(),
  578                                  bc.getLength(), encoding);
  579           } else {
  580               if (data.getType()!= MessageBytes.T_CHARS ) 
  581                   data.toChars();
  582               CharChunk cc=data.getCharChunk();
  583               processParameters( cc.getChars(), cc.getOffset(),
  584                                  cc.getLength());
  585           }
  586       }
  587   
  588       /** Debug purpose
  589        */
  590       public String paramsAsString() {
  591           StringBuffer sb=new StringBuffer();
  592           Enumeration en= paramHashStringArray.keys();
  593           while( en.hasMoreElements() ) {
  594               String k=(String)en.nextElement();
  595               sb.append( k ).append("=");
  596               String v[]=(String[])paramHashStringArray.get( k );
  597               for( int i=0; i<v.length; i++ )
  598                   sb.append( v[i] ).append(",");
  599               sb.append("\n");
  600           }
  601           return sb.toString();
  602       }
  603   
  604       private static int debug=0;
  605       private void log(String s ) {
  606           if (log.isDebugEnabled())
  607               log.debug("Parameters: " + s );
  608       }
  609      
  610       // -------------------- Old code, needs rewrite --------------------
  611       
  612       /** Used by RequestDispatcher
  613        */
  614       public void processParameters( String str ) {
  615           int end=str.length();
  616           int pos=0;
  617           if( debug > 0)
  618               log("String: " + str );
  619           
  620           do {
  621               boolean noEq=false;
  622               int valStart=-1;
  623               int valEnd=-1;
  624               
  625               int nameStart=pos;
  626               int nameEnd=str.indexOf('=', nameStart );
  627               int nameEnd2=str.indexOf('&', nameStart );
  628               if( nameEnd2== -1 ) nameEnd2=end;
  629               if( (nameEnd2!=-1 ) &&
  630                   ( nameEnd==-1 || nameEnd > nameEnd2) ) {
  631                   nameEnd=nameEnd2;
  632                   noEq=true;
  633                   valStart=nameEnd;
  634                   valEnd=nameEnd;
  635                   if( debug>0) log("no equal " + nameStart + " " + nameEnd + " " + str.substring(nameStart, nameEnd) );
  636               }
  637   
  638               if( nameEnd== -1 ) nameEnd=end;
  639   
  640               if( ! noEq ) {
  641                   valStart=nameEnd+1;
  642                   valEnd=str.indexOf('&', valStart);
  643                   if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
  644               }
  645               
  646               pos=valEnd+1;
  647               
  648               if( nameEnd<=nameStart ) {
  649                   continue;
  650               }
  651               if( debug>0)
  652                   log( "XXX " + nameStart + " " + nameEnd + " "
  653                        + valStart + " " + valEnd );
  654               
  655               try {
  656                   tmpNameC.append(str, nameStart, nameEnd-nameStart );
  657                   tmpValueC.append(str, valStart, valEnd-valStart );
  658               
  659                   if( debug > 0 )
  660                       log( tmpNameC + "= " + tmpValueC);
  661   
  662                   if( urlDec==null ) {
  663                       urlDec=new UDecoder();   
  664                   }
  665   
  666                   urlDec.convert( tmpNameC );
  667                   urlDec.convert( tmpValueC );
  668   
  669                   if( debug > 0 )
  670                       log( tmpNameC + "= " + tmpValueC);
  671                   
  672                   addParam( tmpNameC.toString(), tmpValueC.toString() );
  673               } catch( IOException ex ) {
  674                   ex.printStackTrace();
  675               }
  676   
  677               tmpNameC.recycle();
  678               tmpValueC.recycle();
  679   
  680           } while( pos<end );
  681       }
  682   
  683   
  684   }

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