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

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