You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2011/10/27 19:34:59 UTC

svn commit: r1189882 - in /tomcat/trunk: java/org/apache/catalina/connector/ java/org/apache/tomcat/util/buf/ java/org/apache/tomcat/util/http/ webapps/docs/config/

Author: markt
Date: Thu Oct 27 17:34:58 2011
New Revision: 1189882

URL: http://svn.apache.org/viewvc?rev=1189882&view=rev
Log:
Refactor parameter parsing for performance.

Added:
    tomcat/trunk/java/org/apache/tomcat/util/http/LocalStrings.properties
Modified:
    tomcat/trunk/java/org/apache/catalina/connector/Connector.java
    tomcat/trunk/java/org/apache/catalina/connector/Request.java
    tomcat/trunk/java/org/apache/catalina/connector/mbeans-descriptors.xml
    tomcat/trunk/java/org/apache/tomcat/util/buf/ByteChunk.java
    tomcat/trunk/java/org/apache/tomcat/util/buf/MessageBytes.java
    tomcat/trunk/java/org/apache/tomcat/util/buf/StringCache.java
    tomcat/trunk/java/org/apache/tomcat/util/http/Parameters.java
    tomcat/trunk/webapps/docs/config/ajp.xml
    tomcat/trunk/webapps/docs/config/http.xml

Modified: tomcat/trunk/java/org/apache/catalina/connector/Connector.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/connector/Connector.java?rev=1189882&r1=1189881&r2=1189882&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/connector/Connector.java (original)
+++ tomcat/trunk/java/org/apache/catalina/connector/Connector.java Thu Oct 27 17:34:58 2011
@@ -163,6 +163,13 @@ public class Connector extends Lifecycle
 
 
     /**
+     * The maximum number of parameters (GET plus POST) which will be
+     * automatically parsed by the container. 10000 by default. A value of less
+     * than 0 means no limit.
+     */
+    protected int maxParameterCount = 10000;
+
+    /**
      * Maximum size of a POST which will be automatically parsed by the
      * container. 2MB by default.
      */
@@ -381,14 +388,34 @@ public class Connector extends Lifecycle
     }
 
 
-     /**
-      * Return the mapper.
-      */
-     public Mapper getMapper() {
+    /**
+     * Return the mapper.
+     */
+    public Mapper getMapper() {
+        return (mapper);
+    }
 
-         return (mapper);
 
-     }
+    /**
+     * Return the maximum number of parameters (GET plus POST) that will be
+     * automatically parsed by the container. A value of less than 0 means no
+     * limit.
+     */
+    public int getMaxParameterCount() {
+        return maxParameterCount;
+    }
+
+
+    /**
+     * Set the maximum number of parameters (GET plus POST) that will be
+     * automatically parsed by the container. A value of less than 0 means no
+     * limit.
+     *
+     * @param maxParameterCount The new setting
+     */
+    public void setMaxParameterCount(int maxParameterCount) {
+        this.maxParameterCount = maxParameterCount;
+    }
 
 
     /**

Modified: tomcat/trunk/java/org/apache/catalina/connector/Request.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/connector/Request.java?rev=1189882&r1=1189881&r2=1189882&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/connector/Request.java (original)
+++ tomcat/trunk/java/org/apache/catalina/connector/Request.java Thu Oct 27 17:34:58 2011
@@ -2732,6 +2732,8 @@ public class Request
         parametersParsed = true;
 
         Parameters parameters = coyoteRequest.getParameters();
+        // Set this every time in case limit has been changed via JMX
+        parameters.setLimit(getConnector().getMaxParameterCount());
 
         // getCharacterEncoding() may have been overridden to search for
         // hidden form field containing request encoding

Modified: tomcat/trunk/java/org/apache/catalina/connector/mbeans-descriptors.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/connector/mbeans-descriptors.xml?rev=1189882&r1=1189881&r2=1189882&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/connector/mbeans-descriptors.xml (original)
+++ tomcat/trunk/java/org/apache/catalina/connector/mbeans-descriptors.xml Thu Oct 27 17:34:58 2011
@@ -81,6 +81,10 @@
           description="Maximum number of Keep-Alive requests to honor per connection"
                  type="int"/>
 
+    <attribute   name="maxParameterCount"
+          description="The maximum number of parameters (GET plus POST) which will be automatically parsed by the container. 10000 by default. A value of less than 0 means no limit."
+                 type="int"/>
+
     <attribute   name="maxPostSize"
           description="Maximum size in bytes of a POST which will be handled by the servlet API provided features"
                  type="int"/>

Modified: tomcat/trunk/java/org/apache/tomcat/util/buf/ByteChunk.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/buf/ByteChunk.java?rev=1189882&r1=1189881&r2=1189882&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/buf/ByteChunk.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/util/buf/ByteChunk.java Thu Oct 27 17:34:58 2011
@@ -18,6 +18,10 @@ package org.apache.tomcat.util.buf;
 
 import java.io.IOException;
 import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
 
 /*
  * In a server it is very important to be able to operate on
@@ -96,7 +100,15 @@ public final class ByteChunk implements 
         as most standards seem to converge, but the servlet API requires
         8859_1, and this object is used mostly for servlets.
     */
-    public static final String DEFAULT_CHARACTER_ENCODING="ISO-8859-1";
+    public static Charset DEFAULT_CHARSET = null;
+
+    static {
+        try {
+            DEFAULT_CHARSET = B2CConverter.getCharset("ISO-8859-1");
+        } catch (UnsupportedEncodingException e) {
+            // Should never happen since all JVMs must support ISO-8859-1
+        }
+    }
 
     // byte[]
     private byte[] buff;
@@ -104,7 +116,7 @@ public final class ByteChunk implements 
     private int start=0;
     private int end;
 
-    private String enc;
+    private Charset charset;
 
     private boolean isSet=false; // XXX
 
@@ -145,7 +157,7 @@ public final class ByteChunk implements 
      */
     public void recycle() {
         //        buff = null;
-        enc=null;
+        charset=null;
         start=0;
         end=0;
         isSet=false;
@@ -185,14 +197,15 @@ public final class ByteChunk implements 
         this.optimizedWrite = optimizedWrite;
     }
 
-    public void setEncoding( String enc ) {
-        this.enc=enc;
+    public void setCharset(Charset charset) {
+        this.charset = charset;
     }
-    public String getEncoding() {
-        if (enc == null) {
-            enc=DEFAULT_CHARACTER_ENCODING;
+
+    public Charset getCharset() {
+        if (charset == null) {
+            charset = DEFAULT_CHARSET;
         }
-        return enc;
+        return charset;
     }
 
     /**
@@ -512,31 +525,15 @@ public final class ByteChunk implements 
     }
 
     public String toStringInternal() {
-        String strValue=null;
-        try {
-            if (enc == null) {
-                enc = DEFAULT_CHARACTER_ENCODING;
-            }
-            strValue = new String(buff, start, end-start,
-                    B2CConverter.getCharset(enc));
-            /*
-             Does not improve the speed too much on most systems,
-             it's safer to use the "classical" new String().
-
-             Most overhead is in creating char[] and copying,
-             the internal implementation of new String() is very close to
-             what we do. The decoder is nice for large buffers and if
-             we don't go to String ( so we can take advantage of reduced GC)
-
-             // Method is commented out, in:
-              return B2CConverter.decodeString( enc );
-              */
-        } catch (java.io.UnsupportedEncodingException e) {
-            // Use the platform encoding in that case; the usage of a bad
-            // encoding will have been logged elsewhere already
-            strValue = new String(buff, start, end-start);
+        if (charset == null) {
+            charset = DEFAULT_CHARSET;
         }
-        return strValue;
+        // new String(byte[], int, int, Charset) takes a defensive copy of the
+        // entire byte array. This is expensive if only a small subset of the
+        // bytes will be used. The code below is from Apache Harmony.
+        CharBuffer cb;
+        cb = charset.decode(ByteBuffer.wrap(buff, start, end-start));
+        return new String(cb.array());
     }
 
     public int getInt()

Modified: tomcat/trunk/java/org/apache/tomcat/util/buf/MessageBytes.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/buf/MessageBytes.java?rev=1189882&r1=1189881&r2=1189882&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/buf/MessageBytes.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/util/buf/MessageBytes.java Thu Oct 27 17:34:58 2011
@@ -129,13 +129,13 @@ public final class MessageBytes implemen
      *  previous conversion is reset.
      *  If no encoding is set, we'll use 8859-1.
      */
-    public void setEncoding( String enc ) {
+    public void setCharset(Charset charset) {
         if( !byteC.isNull() ) {
             // if the encoding changes we need to reset the conversion results
             charC.recycle();
             hasStrValue=false;
         }
-        byteC.setEncoding(enc);
+        byteC.setCharset(charset);
     }
 
     /**

Modified: tomcat/trunk/java/org/apache/tomcat/util/buf/StringCache.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/buf/StringCache.java?rev=1189882&r1=1189881&r2=1189882&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/buf/StringCache.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/util/buf/StringCache.java Thu Oct 27 17:34:58 2011
@@ -16,6 +16,7 @@
  */
 package org.apache.tomcat.util.buf;
 
+import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map.Entry;
@@ -298,7 +299,7 @@ public class StringCache {
                             System.arraycopy(bc.getBuffer(), start, entry.name,
                                     0, end - start);
                             // Set encoding
-                            entry.enc = bc.getEncoding();
+                            entry.charset = bc.getCharset();
                             // Initialize occurrence count to one
                             count = new int[1];
                             count[0] = 1;
@@ -484,7 +485,7 @@ public class StringCache {
     protected static final String find(ByteChunk name) {
         int pos = findClosest(name, bcCache, bcCache.length);
         if ((pos < 0) || (compare(name, bcCache[pos].name) != 0)
-                || !(name.getEncoding().equals(bcCache[pos].enc))) {
+                || !(name.getCharset().equals(bcCache[pos].charset))) {
             return null;
         } else {
             return bcCache[pos].value;
@@ -639,7 +640,7 @@ public class StringCache {
     public static class ByteEntry {
 
         public byte[] name = null;
-        public String enc = null;
+        public Charset charset = null;
         public String value = null;
 
         @Override

Added: tomcat/trunk/java/org/apache/tomcat/util/http/LocalStrings.properties
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/http/LocalStrings.properties?rev=1189882&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/http/LocalStrings.properties (added)
+++ tomcat/trunk/java/org/apache/tomcat/util/http/LocalStrings.properties Thu Oct 27 17:34:58 2011
@@ -0,0 +1,23 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+parameters.bytes=Start processing with input [{0}]
+paramerers.copyFail=Failed to create copy of original parameter values for debug logging purposes
+parameters.decodeFail.debug=Character decoding failed. Parameter [{0}] with value [{1}] has been ignored.
+parameters.decodeFail.info=Character decoding failed. Parameter [{0}] with value [{1}] has been ignored. Note that the name and value quoted here may be corrupted due to the failed decoding. Use debug level logging to see the original, non-corrupted values.
+parameters.invalidChunk=Invalid chunk starting at byte [{0}] and ending at byte [{1}] with a value of [{2}] ignored
+parameters.maxCountFail=More than the maximum number of request parameters (GET plus POST) for a single request ([{0}]) were detected. Any parameters beyond this limit have been ignored. To change this limit, set the maxParameterCount attribute on the Connector.
+parameters.multipleDecodingFail=Character decoding failed. A total of [{0}] failures were detected but only the first was logged. Enable debug level logging for this logger to log all failures.
+parameters.noequal=Parameter starting at position [{0}] and ending at position [{1}] with a value of [{0}] was not followed by an '=' character

Modified: tomcat/trunk/java/org/apache/tomcat/util/http/Parameters.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/http/Parameters.java?rev=1189882&r1=1189881&r2=1189882&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/http/Parameters.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/util/http/Parameters.java Thu Oct 27 17:34:58 2011
@@ -17,14 +17,18 @@
 package org.apache.tomcat.util.http;
 
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.nio.charset.Charset;
+import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.Hashtable;
 
+import org.apache.tomcat.util.buf.B2CConverter;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.CharChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
 import org.apache.tomcat.util.buf.UDecoder;
+import org.apache.tomcat.util.res.StringManager;
 
 /**
  *
@@ -32,11 +36,14 @@ import org.apache.tomcat.util.buf.UDecod
  */
 public final class Parameters {
 
-    private static final org.apache.juli.logging.Log log=
+    private static final org.apache.juli.logging.Log log =
         org.apache.juli.logging.LogFactory.getLog(Parameters.class );
 
-    private final Hashtable<String,String[]> paramHashStringArray =
-        new Hashtable<String,String[]>();
+    protected static final StringManager sm =
+        StringManager.getManager("org.apache.tomcat.util.http");
+
+    private final Hashtable<String,ArrayList<String>> paramHashValues =
+        new Hashtable<String,ArrayList<String>>();
     private boolean didQueryParameters=false;
 
     MessageBytes queryMB;
@@ -47,6 +54,9 @@ public final class Parameters {
     String encoding=null;
     String queryStringEncoding=null;
 
+    private int limit = -1;
+    private int parameterCount = 0;
+
     public Parameters() {
         // NO-OP
     }
@@ -55,6 +65,10 @@ public final class Parameters {
         this.queryMB=queryMB;
     }
 
+    public void setLimit(int limit) {
+        this.limit = limit;
+    }
+
     public String getEncoding() {
         return encoding;
     }
@@ -74,7 +88,8 @@ public final class Parameters {
     }
 
     public void recycle() {
-        paramHashStringArray.clear();
+        parameterCount = 0;
+        paramHashValues.clear();
         didQueryParameters=false;
         encoding=null;
         decodedQuery.recycle();
@@ -90,43 +105,39 @@ public final class Parameters {
         if ( key==null ) {
             return;
         }
-        String values[];
-        if (paramHashStringArray.containsKey(key)) {
-            String oldValues[] = paramHashStringArray.get(key);
-            values = new String[oldValues.length + newValues.length];
-            for (int i = 0; i < oldValues.length; i++) {
-                values[i] = oldValues[i];
-            }
-            for (int i = 0; i < newValues.length; i++) {
-                values[i+ oldValues.length] = newValues[i];
-            }
+        ArrayList<String> values;
+        if (paramHashValues.containsKey(key)) {
+             values = paramHashValues.get(key);
         } else {
-            values = newValues;
+            values = new ArrayList<String>(1);
+            paramHashValues.put(key, values);
+        }
+        values.ensureCapacity(values.size() + newValues.length);
+        for (String newValue : newValues) {
+            values.add(newValue);
         }
-
-        paramHashStringArray.put(key, values);
     }
 
     public String[] getParameterValues(String name) {
         handleQueryParameters();
         // no "facade"
-        String values[] = paramHashStringArray.get(name);
-        return values;
+        ArrayList<String> values = paramHashValues.get(name);
+        return values.toArray(new String[values.size()]);
     }
 
     public Enumeration<String> getParameterNames() {
         handleQueryParameters();
-        return paramHashStringArray.keys();
+        return paramHashValues.keys();
     }
 
     // Shortcut.
     public String getParameter(String name ) {
-        String[] values = getParameterValues(name);
+        ArrayList<String> values = paramHashValues.get(name);
         if (values != null) {
-            if( values.length==0 ) {
+            if(values.size() == 0) {
                 return "";
             }
-            return values[0];
+            return values.get(0);
         } else {
             return null;
         }
@@ -165,21 +176,14 @@ public final class Parameters {
         if( key==null ) {
             return;
         }
-        String values[];
-        if (paramHashStringArray.containsKey(key)) {
-            String oldValues[] = paramHashStringArray.get(key);
-            values = new String[oldValues.length + 1];
-            for (int i = 0; i < oldValues.length; i++) {
-                values[i] = oldValues[i];
-            }
-            values[oldValues.length] = value;
+        ArrayList<String> values;
+        if (paramHashValues.containsKey(key)) {
+            values = paramHashValues.get(key);
         } else {
-            values = new String[1];
-            values[0] = value;
+            values = new ArrayList<String>(1);
+            paramHashValues.put(key, values);
         }
-
-
-        paramHashStringArray.put(key, values);
+        values.add(value);
     }
 
     public void setURLDecoder( UDecoder u ) {
@@ -200,107 +204,158 @@ public final class Parameters {
 
 
     public void processParameters( byte bytes[], int start, int len ) {
-        processParameters(bytes, start, len, encoding);
+        processParameters(bytes, start, len, getCharset(encoding));
     }
 
-    public void processParameters( byte bytes[], int start, int len,
-                                   String enc ) {
-        int end=start+len;
-        int pos=start;
+    public void processParameters(byte bytes[], int start, int len,
+                                  Charset charset) {
 
         if(log.isDebugEnabled()) {
-            log.debug("Bytes: " +
-                    new String(bytes, start, len, DEFAULT_CHARSET));
+            log.debug(sm.getString("parameters.bytes",
+                    new String(bytes, start, len, DEFAULT_CHARSET)));
         }
 
-        do {
-            boolean noEq=false;
-            int valStart=-1;
-            int valEnd=-1;
-
-            int nameStart=pos;
-            int nameEnd=ByteChunk.indexOf(bytes, nameStart, end, '=' );
-            // Workaround for a&b&c encoding
-            int nameEnd2=ByteChunk.indexOf(bytes, nameStart, end, '&' );
-            if( (nameEnd2!=-1 ) &&
-                ( nameEnd==-1 || nameEnd > nameEnd2) ) {
-                nameEnd=nameEnd2;
-                noEq=true;
-                valStart=nameEnd;
-                valEnd=nameEnd;
-                if(log.isDebugEnabled()) {
-                    log.debug("no equal " + nameStart + " " + nameEnd + " " +
-                        new String(bytes, nameStart, nameEnd-nameStart,
-                                        DEFAULT_CHARSET));
+        int decodeFailCount = 0;
+
+        int pos = start;
+        int end = start + len;
+
+        while(pos < end) {
+            parameterCount ++;
+
+            if (limit > -1 && parameterCount >= limit) {
+                log.warn(sm.getString("parameters.maxCountFail",
+                        Integer.valueOf(limit)));
+                break;
+            }
+            int nameStart = pos;
+            int nameEnd = -1;
+            int valueStart = -1;
+            int valueEnd = -1;
+
+            boolean parsingName = true;
+            boolean decodeName = false;
+            boolean decodeValue = false;
+            boolean parameterComplete = false;
+
+            do {
+                switch(bytes[pos]) {
+                    case '=':
+                        if (parsingName) {
+                            // Name finished. Value starts from next character
+                            nameEnd = pos;
+                            parsingName = false;
+                            valueStart = ++pos;
+                        } else {
+                            // Equals character in value
+                            pos++;
+                        }
+                        break;
+                    case '&':
+                        if (parsingName) {
+                            // Name finished. No value.
+                            nameEnd = pos;
+                        } else {
+                            // Value finished
+                            valueEnd  = pos;
+                        }
+                        parameterComplete = true;
+                        pos++;
+                        break;
+                    case '%':
+                        // Decoding required
+                        if (parsingName) {
+                            decodeName = true;
+                        } else {
+                            decodeValue = true;
+                        }
+                        pos ++;
+                        break;
+                    default:
+                        pos ++;
+                        break;
                 }
-            }
-            if( nameEnd== -1 ) {
-                nameEnd=end;
-            }
+            } while (!parameterComplete && pos < end);
 
-            if( ! noEq ) {
-                valStart= (nameEnd < end) ? nameEnd+1 : end;
-                valEnd=ByteChunk.indexOf(bytes, valStart, end, '&');
-                if( valEnd== -1 ) {
-                    valEnd = (valStart < end) ? end : valStart;
+            if (pos == end) {
+                if (nameEnd == -1) {
+                    nameEnd = pos;
+                } else if (valueStart > -1 && valueEnd == -1){
+                    valueEnd = pos;
                 }
             }
 
-            pos=valEnd+1;
+            if (log.isDebugEnabled() && valueStart == -1) {
+                log.debug(sm.getString("parameters.noequal",
+                        Integer.valueOf(nameStart), Integer.valueOf(nameEnd),
+                        new String(bytes, nameStart, nameEnd-nameStart,
+                                DEFAULT_CHARSET)));
+            }
 
-            if( nameEnd<=nameStart ) {
+            if (nameEnd <= nameStart ) {
                 if (log.isInfoEnabled()) {
-                    StringBuilder msg = new StringBuilder("Parameters: Invalid chunk ");
-                    // No name eg ...&=xx&... will trigger this
-                    if (valEnd >= nameStart) {
-                        msg.append('\'');
-                        msg.append(new String(bytes, nameStart,
-                                valEnd - nameStart, DEFAULT_CHARSET));
-                        msg.append("' ");
+                    String extract;
+                    if (valueEnd >= nameStart) {
+                        extract = new String(bytes, nameStart,
+                                valueEnd - nameStart, DEFAULT_CHARSET);
+                        log.info(sm.getString("parameters.invalidChunk",
+                                Integer.valueOf(nameStart),
+                                Integer.valueOf(valueEnd),
+                                extract));
+                    } else {
+                        log.info(sm.getString("parameters.invalidChunk",
+                                Integer.valueOf(nameStart),
+                                Integer.valueOf(nameEnd),
+                                null));
                     }
-                    msg.append("ignored.");
-                    log.info(msg);
                 }
                 continue;
                 // invalid chunk - it's better to ignore
             }
-            tmpName.setBytes( bytes, nameStart, nameEnd-nameStart );
-            tmpValue.setBytes( bytes, valStart, valEnd-valStart );
+
+            tmpName.setBytes(bytes, nameStart, nameEnd - nameStart);
+            tmpValue.setBytes(bytes, valueStart, valueEnd - valueStart);
 
             // Take copies as if anything goes wrong originals will be
             // corrupted. This means original values can be logged.
             // For performance - only done for debug
             if (log.isDebugEnabled()) {
                 try {
-                    origName.append(bytes, nameStart, nameEnd-nameStart);
-                    origValue.append(bytes, valStart, valEnd-valStart);
+                    origName.append(bytes, nameStart, nameEnd - nameStart);
+                    origValue.append(bytes, valueStart, valueEnd - valueStart);
                 } catch (IOException ioe) {
                     // Should never happen...
-                    log.error("Error copying parameters", ioe);
+                    log.error(sm.getString("paramerers.copyFail"), ioe);
                 }
             }
 
             try {
-                addParam( urlDecode(tmpName, enc), urlDecode(tmpValue, enc) );
+                String name;
+                String value;
+
+                if (decodeName) {
+                    name = urlDecode(tmpName, charset);
+                } else {
+                    name = tmpName.toString();
+                }
+
+                if (decodeValue) {
+                    value = urlDecode(tmpValue, charset);
+                } else {
+                    value = tmpValue.toString();
+                }
+
+                addParam(name, value);
             } catch (IOException e) {
-                StringBuilder msg =
-                    new StringBuilder("Parameters: Character decoding failed.");
-                msg.append(" Parameter '");
-                if (log.isDebugEnabled()) {
-                    msg.append(origName.toString());
-                    msg.append("' with value '");
-                    msg.append(origValue.toString());
-                    msg.append("' has been ignored.");
-                    log.debug(msg, e);
-                } else if (log.isInfoEnabled()) {
-                    msg.append(tmpName.toString());
-                    msg.append("' with value '");
-                    msg.append(tmpValue.toString());
-                    msg.append("' has been ignored. Note that the name and ");
-                    msg.append("value quoted here may be corrupted due to ");
-                    msg.append("the failed decoding. Use debug level logging ");
-                    msg.append("to see the original, non-corrupted values.");
-                    log.info(msg);
+                decodeFailCount++;
+                if (decodeFailCount == 1 || log.isDebugEnabled()) {
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("parameters.decodeFail.debug",
+                                origName.toString(), origValue.toString()), e);
+                    } else if (log.isInfoEnabled()) {
+                        log.info(sm.getString("parameters.decodeFail.info",
+                                tmpName.toString(), tmpValue.toString()), e);
+                    }
                 }
             }
 
@@ -311,35 +366,22 @@ public final class Parameters {
                 origName.recycle();
                 origValue.recycle();
             }
-        } while( pos<end );
+        }
+
+        if (decodeFailCount > 1 && !log.isDebugEnabled()) {
+            log.info(sm.getString("parameters.multipleDecodingFail",
+                    Integer.valueOf(decodeFailCount)));
+        }
     }
 
-    private String urlDecode(ByteChunk bc, String enc)
+    private String urlDecode(ByteChunk bc, Charset charset)
         throws IOException {
         if( urlDec==null ) {
             urlDec=new UDecoder();
         }
         urlDec.convert(bc);
-        String result = null;
-        if (enc != null) {
-            bc.setEncoding(enc);
-            result = bc.toString();
-        } else {
-            CharChunk cc = tmpNameC;
-            int length = bc.getLength();
-            cc.allocate(length, -1);
-            // Default encoding: fast conversion
-            byte[] bbuf = bc.getBuffer();
-            char[] cbuf = cc.getBuffer();
-            int start = bc.getStart();
-            for (int i = 0; i < length; i++) {
-                cbuf[i] = (char) (bbuf[i + start] & 0xff);
-            }
-            cc.setChars(cbuf, 0, length);
-            result = cc.toString();
-            cc.recycle();
-        }
-        return result;
+        bc.setCharset(charset);
+        return bc.toString();
     }
 
     public void processParameters( MessageBytes data, String encoding ) {
@@ -352,20 +394,31 @@ public final class Parameters {
         }
         ByteChunk bc=data.getByteChunk();
         processParameters( bc.getBytes(), bc.getOffset(),
-                           bc.getLength(), encoding);
+                           bc.getLength(), getCharset(encoding));
+    }
+
+    private Charset getCharset(String encoding) {
+        if (encoding == null) {
+            return DEFAULT_CHARSET;
+        }
+        try {
+            return B2CConverter.getCharset(encoding);
+        } catch (UnsupportedEncodingException e) {
+            return DEFAULT_CHARSET;
+        }
     }
 
     /** Debug purpose
      */
     public String paramsAsString() {
         StringBuilder sb=new StringBuilder();
-        Enumeration<String> en= paramHashStringArray.keys();
+        Enumeration<String> en= paramHashValues.keys();
         while( en.hasMoreElements() ) {
             String k = en.nextElement();
             sb.append( k ).append("=");
-            String v[] = paramHashStringArray.get( k );
-            for( int i=0; i<v.length; i++ ) {
-                sb.append( v[i] ).append(",");
+            ArrayList<String> values = paramHashValues.get(k);
+            for(String value : values) {
+                sb.append(value).append(",");
             }
             sb.append("\n");
         }

Modified: tomcat/trunk/webapps/docs/config/ajp.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/ajp.xml?rev=1189882&r1=1189881&r2=1189882&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/config/ajp.xml (original)
+++ tomcat/trunk/webapps/docs/config/ajp.xml Thu Oct 27 17:34:58 2011
@@ -95,6 +95,12 @@
       By default, DNS lookups are disabled.</p>
     </attribute>
 
+    <attribute name="maxParameterCount" required="false">
+      <p>The maximum number of parameters (GET plus POST) which will be
+      automatically parsed by the container. A value of less than 0 means no
+      limit. If not specified, a default of 10000 is used.</p>
+    </attribute>
+
     <attribute name="maxPostSize" required="false">
       <p>The maximum size in bytes of the POST which will be handled by
       the container FORM URL parameter parsing. The limit can be disabled by

Modified: tomcat/trunk/webapps/docs/config/http.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/http.xml?rev=1189882&r1=1189881&r2=1189882&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/config/http.xml (original)
+++ tomcat/trunk/webapps/docs/config/http.xml Thu Oct 27 17:34:58 2011
@@ -93,6 +93,12 @@
       By default, DNS lookups are disabled.</p>
     </attribute>
 
+    <attribute name="maxParameterCount" required="false">
+      <p>The maximum number of parameters (GET plus POST) which will be
+      automatically parsed by the container. A value of less than 0 means no
+      limit. If not specified, a default of 10000 is used.</p>
+    </attribute>
+
     <attribute name="maxPostSize" required="false">
       <p>The maximum size in bytes of the POST which will be handled by
       the container FORM URL parameter parsing. The limit can be disabled by



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org