* Improve performance of parameter processing
      <add>
        Improve performance of parameter processing for GET and POST requests.
        Also add an option to limit the maximum number of parameters processed
        per request. This defaults to 10000. Excessive parameters are ignored.
        Note that <code>FailedRequestFilter</code> can be used to reject the
        request if some parameters were ignored. (markt/kkolinko)
      </add>
      <add>
        New filter <code>FailedRequestFilter</code> that will reject a request
        if there were errors during HTTP parameter parsing. (kkolinko)
      </add>
  Before the patch:
  Should be created by patch tool automatically, but just to be sure:
    mkdir container/catalina/src/share/org/apache/catalina/filters
    svn add container/catalina/src/share/org/apache/catalina/filters
  Apply patch:
    http://people.apache.org/~kkolinko/patches/2011-11-17_tc55_parameters-v5.patch
  After the patch:
    svn propset svn:eol-style native  connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties
    svn propset svn:eol-style native  container/catalina/src/share/org/apache/catalina/filters/FailedRequestFilter.java
    svn propset svn:eol-style native  container/webapps/docs/config/filter.xml
  +1: kkolinko, markt, funkman, jim
  -1:



git-svn-id: https://svn.apache.org/repos/asf/tomcat/tc5.5.x/trunk@1221282 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/STATUS.txt b/STATUS.txt
index 1f3fa39..ac1e274 100644
--- a/STATUS.txt
+++ b/STATUS.txt
@@ -24,31 +24,6 @@
 PATCHES ACCEPTED TO BACKPORT FROM TRUNK/OTHER:
   [ start all new proposals below, under PATCHES PROPOSED. ]
 
-* Improve performance of parameter processing
-      <add>
-        Improve performance of parameter processing for GET and POST requests.
-        Also add an option to limit the maximum number of parameters processed
-        per request. This defaults to 10000. Excessive parameters are ignored.
-        Note that <code>FailedRequestFilter</code> can be used to reject the
-        request if some parameters were ignored. (markt/kkolinko)
-      </add>
-      <add>
-        New filter <code>FailedRequestFilter</code> that will reject a request
-        if there were errors during HTTP parameter parsing. (kkolinko)
-      </add>
-  Before the patch:
-  Should be created by patch tool automatically, but just to be sure:
-    mkdir container/catalina/src/share/org/apache/catalina/filters
-    svn add container/catalina/src/share/org/apache/catalina/filters
-  Apply patch:
-    http://people.apache.org/~kkolinko/patches/2011-11-17_tc55_parameters-v5.patch
-  After the patch:
-    svn propset svn:eol-style native  connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties
-    svn propset svn:eol-style native  container/catalina/src/share/org/apache/catalina/filters/FailedRequestFilter.java
-    svn propset svn:eol-style native  container/webapps/docs/config/filter.xml
-  +1: kkolinko, markt, funkman, jim
-  -1:
-
 * Align %2f handling between implementations of UDecoder.convert()
   http://svn.apache.org/viewvc?rev=1203091&view=rev
   +1: kkolinko, markt, funkman, jim
diff --git a/connectors/coyote/src/java/org/apache/coyote/Request.java b/connectors/coyote/src/java/org/apache/coyote/Request.java
index 1c148af..c3d43ea 100644
--- a/connectors/coyote/src/java/org/apache/coyote/Request.java
+++ b/connectors/coyote/src/java/org/apache/coyote/Request.java
@@ -73,7 +73,6 @@
 
         parameters.setQuery(queryMB);
         parameters.setURLDecoder(urlDecoder);
-        parameters.setHeaders(headers);
 
     }
 
diff --git a/connectors/util/java/org/apache/tomcat/util/buf/B2CConverter.java b/connectors/util/java/org/apache/tomcat/util/buf/B2CConverter.java
index 770e800..b6ee59e 100644
--- a/connectors/util/java/org/apache/tomcat/util/buf/B2CConverter.java
+++ b/connectors/util/java/org/apache/tomcat/util/buf/B2CConverter.java
@@ -22,6 +22,11 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
 
 /** Efficient conversion of bytes  to character .
  *  
@@ -38,8 +43,40 @@
     
     
     private static org.apache.commons.logging.Log log=
-        org.apache.commons.logging.LogFactory.getLog( B2CConverter.class );
+            org.apache.commons.logging.LogFactory.getLog( B2CConverter.class );
+
+    private static final Map encodingToCharsetCache = new HashMap();
     
+    static {
+        Iterator charsets = Charset.availableCharsets().values().iterator();
+        while (charsets.hasNext()) {
+            Charset charset = (Charset) charsets.next();
+            encodingToCharsetCache.put(
+                    charset.name().toLowerCase(Locale.US), charset);
+            Iterator aliases = charset.aliases().iterator();
+            while (aliases.hasNext()) {
+                String alias = (String) aliases.next();
+                encodingToCharsetCache.put(
+                        alias.toLowerCase(Locale.US), charset);
+            }
+        }
+    }
+
+    public static Charset getCharset(String enc)
+            throws UnsupportedEncodingException {
+
+        // Encoding names should all be ASCII
+        String lowerCaseEnc = enc.toLowerCase(Locale.US);
+        
+        Charset charset = (Charset) encodingToCharsetCache.get(lowerCaseEnc);
+
+        if (charset == null) {
+            // Pre-population of the cache means this must be invalid
+            throw new UnsupportedEncodingException(enc);
+        }
+        return charset;
+    }
+
     private IntermediateInputStream iis;
     private ReadConvertor conv;
     private String encoding;
@@ -68,38 +105,42 @@
     char result[]=new char[BUFFER_SIZE];
 
     /** Convert a buffer of bytes into a chars
+     * @deprecated
      */
     public  void convert( ByteChunk bb, CharChunk cb )
         throws IOException
     {
+        // Set the ByteChunk as input to the Intermediate reader
         convert(bb, cb, cb.getBuffer().length - cb.getEnd());
     }
 
-    /** Convert a buffer of bytes into a chars
+    /**
+     * Convert a buffer of bytes into a chars.
+     *
+     * @param bb    Input byte buffer
+     * @param cb    Output char buffer
+     * @param limit Number of bytes to convert
+     * @throws IOException
      */
-    public  void convert( ByteChunk bb, CharChunk cb, int limit)
+    public void convert( ByteChunk bb, CharChunk cb, int limit) 
         throws IOException
     {
-        // Set the ByteChunk as input to the Intermediate reader
         iis.setByteChunk( bb );
         try {
             // read from the reader
             int bbLengthBeforeRead  = 0;
-            while( limit > 0 ) { 
-                int size = limit < BUFFER_SIZE ? limit : BUFFER_SIZE; 
+            while( limit > 0 ) {
+                int size = limit < BUFFER_SIZE ? limit : BUFFER_SIZE;
                 bbLengthBeforeRead = bb.getLength();
                 int cnt=conv.read( result, 0, size );
                 if( cnt <= 0 ) {
                     // End of stream ! - we may be in a bad state
                     if( debug>0)
                         log( "EOF" );
-                    //                    reset();
                     return;
                 }
                 if( debug > 1 )
                     log("Converted: " + new String( result, 0, cnt ));
-
-                // XXX go directly
                 cb.append( result, 0, cnt );
                 limit = limit - (bbLengthBeforeRead - bb.getLength());
             }
@@ -111,12 +152,13 @@
         }
     }
 
+
     public void reset()
         throws IOException
     {
         // destroy the reader/iis
         iis=new IntermediateInputStream();
-        conv=new ReadConvertor( iis, encoding );
+        conv=new ReadConvertor( iis, getCharset(encoding) );
     }
 
     private final int debug=0;
@@ -194,10 +236,9 @@
     
     /** Create a converter.
      */
-    public ReadConvertor( IntermediateInputStream in, String enc )
-        throws UnsupportedEncodingException
+    public ReadConvertor( IntermediateInputStream in, Charset charset )
     {
-        super( in, enc );
+        super( in, charset );
     }
     
     /** Overriden - will do nothing but reset internal state.
@@ -237,7 +278,6 @@
 */
 final class IntermediateInputStream extends InputStream {
     ByteChunk bc = null;
-    boolean initialized = false;
     
     public IntermediateInputStream() {
     }
@@ -248,27 +288,17 @@
     }
     
     public  final  int read(byte cbuf[], int off, int len) throws IOException {
-        if (!initialized) return -1;
-        int nread = bc.substract(cbuf, off, len);
-        return nread;
+        return bc.substract(cbuf, off, len);
     }
     
     public  final int read() throws IOException {
-        if (!initialized) return -1;
         return bc.substract();
     }
-    
-    public int available() throws IOException {
-        if (!initialized) return 0;
-        return bc.getLength();
-    }
-
 
     // -------------------- Internal methods --------------------
 
 
     void setByteChunk( ByteChunk mb ) {
-        initialized = (mb!=null);
         bc = mb;
     }
 
diff --git a/connectors/util/java/org/apache/tomcat/util/buf/ByteChunk.java b/connectors/util/java/org/apache/tomcat/util/buf/ByteChunk.java
index 403f4c4..4d1ac4f 100644
--- a/connectors/util/java/org/apache/tomcat/util/buf/ByteChunk.java
+++ b/connectors/util/java/org/apache/tomcat/util/buf/ByteChunk.java
@@ -19,6 +19,10 @@
 
 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
@@ -94,15 +98,26 @@
         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 final Charset DEFAULT_CHARSET;
+
+    static {
+        Charset c = null;
+        try {
+            c = B2CConverter.getCharset("ISO-8859-1");
+        } catch (UnsupportedEncodingException e) {
+            // Should never happen since all JVMs must support ISO-8859-1
+        }
+        DEFAULT_CHARSET = c;
+    }
+
+
     // byte[]
     private byte[] buff;
 
     private int start=0;
     private int end;
 
-    private String enc;
+    private Charset charset;
 
     private boolean isSet=false; // XXX
 
@@ -142,7 +157,7 @@
      */
     public void recycle() {
         //        buff = null;
-        enc=null;
+        charset=null;
         start=0;
         end=0;
         isSet=false;
@@ -182,13 +197,15 @@
         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;
-        return enc;
+
+    public Charset getCharset() {
+        if (charset == null) {
+            charset = DEFAULT_CHARSET;
+        }
+        return charset;
     }
 
     /**
@@ -491,28 +508,15 @@
     }
     
     public String toStringInternal() {
-        String strValue=null;
-        try {
-            if( enc==null ) enc=DEFAULT_CHARACTER_ENCODING;
-            strValue = new String( buff, start, end-start, enc );
-            /*
-             Does not improve the speed too much on most systems,
-             it's safer to use the "clasical" 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(), cb.arrayOffset(), cb.length());
     }
 
     public int getInt()
diff --git a/connectors/util/java/org/apache/tomcat/util/buf/MessageBytes.java b/connectors/util/java/org/apache/tomcat/util/buf/MessageBytes.java
index bae356e..7afe8ea 100644
--- a/connectors/util/java/org/apache/tomcat/util/buf/MessageBytes.java
+++ b/connectors/util/java/org/apache/tomcat/util/buf/MessageBytes.java
@@ -21,6 +21,7 @@
 import java.util.*;
 import java.io.Serializable;
 import java.io.IOException;
+import java.nio.charset.Charset;
 
 /**
  * This class is used to represent a subarray of bytes in an HTTP message.
@@ -140,13 +141,13 @@
      *  previous conversion is reset.
      *  If no encoding is set, we'll use 8859-1.
      */
-    public void setEncoding( String enc ) {
-	if( !byteC.isNull() ) {
-	    // if the encoding changes we need to reset the converion results
-	    charC.recycle();
-	    hasStrValue=false;
-	}
-	byteC.setEncoding(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.setCharset(charset);
     }
 
     /** 
diff --git a/connectors/util/java/org/apache/tomcat/util/buf/StringCache.java b/connectors/util/java/org/apache/tomcat/util/buf/StringCache.java
index 8cc662e..7aab90c 100644
--- a/connectors/util/java/org/apache/tomcat/util/buf/StringCache.java
+++ b/connectors/util/java/org/apache/tomcat/util/buf/StringCache.java
@@ -17,6 +17,7 @@
 
 package org.apache.tomcat.util.buf;
 
+import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -290,7 +291,7 @@
                             entry.name = new byte[bc.getLength()];
                             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;
@@ -470,7 +471,7 @@
     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;
@@ -622,7 +623,7 @@
     public static class ByteEntry {
 
         public byte[] name = null;
-        public String enc = null;
+        public Charset charset = null;
         public String value = null;
 
         public String toString() {
diff --git a/connectors/util/java/org/apache/tomcat/util/buf/UDecoder.java b/connectors/util/java/org/apache/tomcat/util/buf/UDecoder.java
index 162b36b..81e946d 100644
--- a/connectors/util/java/org/apache/tomcat/util/buf/UDecoder.java
+++ b/connectors/util/java/org/apache/tomcat/util/buf/UDecoder.java
@@ -34,7 +34,30 @@
             System.getProperty(
                     "org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH",
                     "false")).booleanValue();
-    
+
+    private static class DecodeException extends CharConversionException {
+        private static final long serialVersionUID = 1L;
+        public DecodeException(String s) {
+            super(s);
+        }
+
+        public synchronized Throwable fillInStackTrace() {
+            // This class does not provide a stack trace
+            return this;
+        }
+    }
+
+    /** Unexpected end of data. */
+    private static final IOException EXCEPTION_EOF = new DecodeException("EOF");
+
+    /** %xx with not-hex digit */
+    private static final IOException EXCEPTION_NOT_HEX_DIGIT = new DecodeException(
+            "isHexDigit");
+
+    /** %-encoded slash is forbidden in resource path */
+    private static final IOException EXCEPTION_SLASH = new DecodeException(
+            "noSlash");
+
     public UDecoder() 
     {
     }
@@ -59,16 +82,18 @@
 
         int idx= ByteChunk.indexOf( buff, start, end, '%' );
         int idx2=-1;
-        if( query )
-            idx2= ByteChunk.indexOf( buff, start, end, '+' );
+        if( query ) {
+            idx2= ByteChunk.indexOf( buff, start, (idx >= 0 ? idx : end), '+' );
+        }
         if( idx<0 && idx2<0 ) {
             return;
         }
 
-        // idx will be the smallest positive inxes ( first % or + )
-        if( idx2 >= 0 && idx2 < idx ) idx=idx2;
-        if( idx < 0 ) idx=idx2;
-    
+        // idx will be the smallest positive index ( first % or + )
+        if( (idx2 >= 0 && idx2 < idx) || idx < 0 ) {
+            idx=idx2;
+        }
+
         boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
 
         for( int j=idx; j<end; j++, idx++ ) {
@@ -79,17 +104,17 @@
             } else {
                 // read next 2 digits
                 if( j+2 >= end ) {
-                    throw new CharConversionException("EOF");
+                    throw EXCEPTION_EOF;
                 }
                 byte b1= buff[j+1];
                 byte b2=buff[j+2];
                 if( !isHexDigit( b1 ) || ! isHexDigit(b2 ))
-                    throw new CharConversionException( "isHexDigit");
+                    throw EXCEPTION_NOT_HEX_DIGIT;
                 
                 j+=2;
                 int res=x2c( b1, b2 );
                 if (noSlash && (res == '/')) {
-                    throw new CharConversionException( "noSlash");
+                    throw EXCEPTION_SLASH;
                 }
                 buff[idx]=(byte)res;
             }
@@ -124,14 +149,17 @@
 
         int idx= CharChunk.indexOf( buff, start, cend, '%' );
         int idx2=-1;
-        if( query )
-            idx2= CharChunk.indexOf( buff, start, cend, '+' );
+        if( query ) {
+            idx2= CharChunk.indexOf( buff, start, (idx >= 0 ? idx : cend), '+' );
+        }
         if( idx<0 && idx2<0 ) {
             return;
         }
-        
-        if( idx2 >= 0 && idx2 < idx ) idx=idx2; 
-        if( idx < 0 ) idx=idx2;
+
+        // idx will be the smallest positive index ( first % or + )
+        if( (idx2 >= 0 && idx2 < idx) || idx < 0 ) {
+            idx=idx2;
+        }
     
         boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
         for( int j=idx; j<cend; j++, idx++ ) {
@@ -143,17 +171,17 @@
                 // read next 2 digits
                 if( j+2 >= cend ) {
                     // invalid
-                    throw new CharConversionException("EOF");
+                    throw EXCEPTION_EOF;
                 }
                 char b1= buff[j+1];
                 char b2=buff[j+2];
                 if( !isHexDigit( b1 ) || ! isHexDigit(b2 ))
-                    throw new CharConversionException("isHexDigit");
+                    throw EXCEPTION_NOT_HEX_DIGIT;
                 
                 j+=2;
                 int res=x2c( b1, b2 );
                 if (noSlash && (res == '/')) {
-                    throw new CharConversionException( "noSlash");
+                    throw EXCEPTION_SLASH;
                     }
                 buff[idx]=(char)res;
             }
diff --git a/connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties b/connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties
new file mode 100644
index 0000000..476efb0
--- /dev/null
+++ b/connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties
@@ -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
diff --git a/connectors/util/java/org/apache/tomcat/util/http/Parameters.java b/connectors/util/java/org/apache/tomcat/util/http/Parameters.java
index 2ca5db0..d2986ef 100644
--- a/connectors/util/java/org/apache/tomcat/util/http/Parameters.java
+++ b/connectors/util/java/org/apache/tomcat/util/http/Parameters.java
@@ -18,227 +18,146 @@
 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.Collections;
 import java.util.Enumeration;
-import java.util.Hashtable;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
 
+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.collections.MultiMap;
+import org.apache.tomcat.util.res.StringManager;
 
 /**
  * 
  * @author Costin Manolache
  */
-public final class Parameters extends MultiMap {
+public final class Parameters {
 
-    
-    private static org.apache.commons.logging.Log log=
-        org.apache.commons.logging.LogFactory.getLog(Parameters.class );
-    
-    // Transition: we'll use the same Hashtable( String->String[] )
-    // for the beginning. When we are sure all accesses happen through
-    // this class - we can switch to MultiMap
-    private Hashtable paramHashStringArray=new Hashtable();
+    private static final org.apache.commons.logging.Log log =
+            org.apache.commons.logging.LogFactory.getLog(Parameters.class);
+
+    protected static final StringManager sm =
+        StringManager.getManager("org.apache.tomcat.util.http");
+
+    // HashMap<String,ArrayList<String>>
+    private final HashMap paramHashValues = new HashMap();
+
     private boolean didQueryParameters=false;
-    private boolean didMerge=false;
     
     MessageBytes queryMB;
-    MimeHeaders  headers;
 
     UDecoder urlDec;
     MessageBytes decodedQuery=MessageBytes.newInstance();
-    
-    public static final int INITIAL_SIZE=4;
-
-    // Garbage-less parameter merging.
-    // In a sub-request with parameters, the new parameters
-    // will be stored in child. When a getParameter happens,
-    // the 2 are merged togheter. The child will be altered
-    // to contain the merged values - the parent is allways the
-    // original request.
-    private Parameters child=null;
-    private Parameters parent=null;
-    private Parameters currentChild=null;
 
     String encoding=null;
     String queryStringEncoding=null;
-    
+
+    private int limit = -1;
+    private int parameterCount = 0;
+
     /**
-     * 
+     * Is set to <code>true</code> if there were failures during parameter
+     * parsing.
      */
+    private boolean parseFailed = false;
+
     public Parameters() {
-        super( INITIAL_SIZE );
+        // NO-OP
     }
 
     public void setQuery( MessageBytes queryMB ) {
         this.queryMB=queryMB;
     }
 
-    public void setHeaders( MimeHeaders headers ) {
-        this.headers=headers;
+    public void setLimit(int limit) {
+        this.limit = limit;
+    }
+
+    public String getEncoding() {
+        return encoding;
     }
 
     public void setEncoding( String s ) {
         encoding=s;
-        if(debug>0) log( "Set encoding to " + s );
+        if(log.isDebugEnabled()) {
+            log.debug( "Set encoding to " + s );
+        }
     }
 
     public void setQueryStringEncoding( String s ) {
         queryStringEncoding=s;
-        if(debug>0) log( "Set query string encoding to " + s );
+        if(log.isDebugEnabled()) {
+            log.debug( "Set query string encoding to " + s );
+        }
+    }
+
+    public boolean isParseFailed() {
+        return parseFailed;
+    }
+
+    public void setParseFailed(boolean parseFailed) {
+        this.parseFailed = parseFailed;
     }
 
     public void recycle() {
-        super.recycle();
-        paramHashStringArray.clear();
+        parameterCount = 0;
+        paramHashValues.clear();
         didQueryParameters=false;
-        currentChild=null;
-        didMerge=false;
         encoding=null;
         decodedQuery.recycle();
-    }
-    
-    // -------------------- Sub-request support --------------------
-
-    public Parameters getCurrentSet() {
-        if( currentChild==null )
-            return this;
-        return currentChild;
-    }
-    
-    /** Create ( or reuse ) a child that will be used during a sub-request.
-        All future changes ( setting query string, adding parameters )
-        will affect the child ( the parent request is never changed ).
-        Both setters and getters will return the data from the deepest
-        child, merged with data from parents.
-    */
-    public void push() {
-        // We maintain a linked list, that will grow to the size of the
-        // longest include chain.
-        // The list has 2 points of interest:
-        // - request.parameters() is the original request and head,
-        // - request.parameters().currentChild() is the current set.
-        // The ->child and parent<- links are preserved ( currentChild is not
-        // the last in the list )
-        
-        // create a new element in the linked list
-        // note that we reuse the child, if any - pop will not
-        // set child to null !
-        if( currentChild==null ) {
-            currentChild=new Parameters();
-            currentChild.setURLDecoder( urlDec );
-            currentChild.parent=this;
-            return;
-        }
-        if( currentChild.child==null ) {
-            currentChild.child=new Parameters();
-            currentChild.setURLDecoder( urlDec );
-            currentChild.child.parent=currentChild;
-        } // it is not null if this object already had a child
-        // i.e. a deeper include() ( we keep it )
-
-        // the head will be the new element.
-        currentChild=currentChild.child;
-        currentChild.setEncoding( encoding );
+        parseFailed = false;
     }
 
-    /** Discard the last child. This happens when we return from a
-        sub-request and the parameters are locally modified.
-     */
-    public void pop() {
-        if( currentChild==null ) {
-            throw new RuntimeException( "Attempt to pop without a push" );
-        }
-        currentChild.recycle();
-        currentChild=currentChild.parent;
-        // don't remove the top.
-    }
-    
     // -------------------- Data access --------------------
     // Access to the current name/values, no side effect ( processing ).
-    // You must explicitely call handleQueryParameters and the post methods.
+    // You must explicitly call handleQueryParameters and the post methods.
     
-    // This is the original data representation ( hash of String->String[])
-
-    public void addParameterValues( String key, String[] newValues) {
-        if ( key==null ) return;
-        String values[];
-        if (paramHashStringArray.containsKey(key)) {
-            String oldValues[] = (String[])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];
-            }
-        } else {
-            values = newValues;
+    public void addParameterValues(String key, String[] newValues) {
+        if (key == null) {
+            return;
         }
-
-        paramHashStringArray.put(key, values);
+        ArrayList values = (ArrayList) paramHashValues.get(key);
+        if (values == null) {
+            values = new ArrayList(newValues.length);
+            paramHashValues.put(key, values);
+        } else {
+            values.ensureCapacity(values.size() + newValues.length);
+        }
+        for (int i = 0; i < newValues.length; i++) {
+            values.add(newValues[i]);
+        }
     }
 
     public String[] getParameterValues(String name) {
         handleQueryParameters();
-        // sub-request
-        if( currentChild!=null ) {
-            currentChild.merge();
-            return (String[])currentChild.paramHashStringArray.get(name);
-        }
-
         // no "facade"
-        String values[]=(String[])paramHashStringArray.get(name);
-        return values;
+        ArrayList values = (ArrayList) paramHashValues.get(name);
+        if (values == null) {
+            return null;
+        }
+        return (String[]) values.toArray(new String[values.size()]);
     }
  
     public Enumeration getParameterNames() {
         handleQueryParameters();
-        // Slow - the original code
-        if( currentChild!=null ) {
-            currentChild.merge();
-            return currentChild.paramHashStringArray.keys();
-        }
-
-        // merge in child
-        return paramHashStringArray.keys();
+        return Collections.enumeration(paramHashValues.keySet());
     }
 
-    /** Combine the parameters from parent with our local ones
-     */
-    private void merge() {
-        // recursive
-        if( debug > 0 ) {
-            log("Before merging " + this + " " + parent + " " + didMerge );
-            log(  paramsAsString());
-        }
-        // Local parameters first - they take precedence as in spec.
-        handleQueryParameters();
-
-        // we already merged with the parent
-        if( didMerge ) return;
-
-        // we are the top level
-        if( parent==null ) return;
-
-        // Add the parent props to the child ( lower precedence )
-        parent.merge();
-        Hashtable parentProps=parent.paramHashStringArray;
-        merge2( paramHashStringArray , parentProps);
-        didMerge=true;
-        if(debug > 0 )
-            log("After " + paramsAsString());
-    }
-
-
     // Shortcut.
     public String getParameter(String name ) {
-        String[] values = getParameterValues(name);
+        handleQueryParameters();
+        ArrayList values = (ArrayList) paramHashValues.get(name);
         if (values != null) {
-            if( values.length==0 ) return "";
-            return values[0];
+            if(values.size() == 0) {
+                return "";
+            }
+            return (String) values.get(0);
         } else {
             return null;
         }
@@ -254,8 +173,10 @@
         if( queryMB==null || queryMB.isNull() )
             return;
         
-        if( debug > 0  )
-            log( "Decoding query " + decodedQuery + " " + queryStringEncoding);
+        if(log.isDebugEnabled()) {
+            log.debug("Decoding query " + decodedQuery + " " +
+                    queryStringEncoding);
+        }
 
         try {
             decodedQuery.duplicate( queryMB );
@@ -266,62 +187,15 @@
         processParameters( decodedQuery, queryStringEncoding );
     }
 
-    // --------------------
-    
-    /** Combine 2 hashtables into a new one.
-     *  ( two will be added to one ).
-     *  Used to combine child parameters ( RequestDispatcher's query )
-     *  with parent parameters ( original query or parent dispatcher )
-     */
-    private static void merge2(Hashtable one, Hashtable two ) {
-        Enumeration e = two.keys();
 
-        while (e.hasMoreElements()) {
-            String name = (String) e.nextElement();
-            String[] oneValue = (String[]) one.get(name);
-            String[] twoValue = (String[]) two.get(name);
-            String[] combinedValue;
-
-            if (twoValue == null) {
-                continue;
-            } else {
-                if( oneValue==null ) {
-                    combinedValue = new String[twoValue.length];
-                    System.arraycopy(twoValue, 0, combinedValue,
-                                     0, twoValue.length);
-                } else {
-                    combinedValue = new String[oneValue.length +
-                                               twoValue.length];
-                    System.arraycopy(oneValue, 0, combinedValue, 0,
-                                     oneValue.length);
-                    System.arraycopy(twoValue, 0, combinedValue,
-                                     oneValue.length, twoValue.length);
-                }
-                one.put(name, combinedValue);
-            }
-        }
-    }
-
-    // incredibly inefficient data representation for parameters,
-    // until we test the new one
     private void addParam( String key, String value ) {
         if( key==null ) return;
-        String values[];
-        if (paramHashStringArray.containsKey(key)) {
-            String oldValues[] = (String[])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;
-        } else {
-            values = new String[1];
-            values[0] = value;
+        ArrayList values = (ArrayList) paramHashValues.get(key);
+        if (values == null) {
+            values = new ArrayList(1);
+            paramHashValues.put(key, values);
         }
-        
-        
-        paramHashStringArray.put(key, values);
+        values.add(value);
     }
 
     public void setURLDecoder( UDecoder u ) {
@@ -329,289 +203,252 @@
     }
 
     // -------------------- Parameter parsing --------------------
-
-    // This code is not used right now - it's the optimized version
-    // of the above.
-
     // we are called from a single thread - we can do it the hard way
     // if needed
     ByteChunk tmpName=new ByteChunk();
     ByteChunk tmpValue=new ByteChunk();
-    CharChunk tmpNameC=new CharChunk(1024);
-    CharChunk tmpValueC=new CharChunk(1024);
+    private ByteChunk origName=new ByteChunk();
+    private ByteChunk origValue=new ByteChunk();
+    private static final String DEFAULT_ENCODING = "ISO-8859-1";
+    private static final Charset DEFAULT_CHARSET =
+        Charset.forName(DEFAULT_ENCODING);
+    
     
     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;
+    private void processParameters(byte bytes[], int start, int len,
+                                  Charset charset) {
         
-        if( debug>0 ) 
-            log( "Bytes: " + new String( bytes, start, len ));
-
-        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(debug>0) log("no equal " + nameStart + " " + nameEnd + " " +
-                        new String(bytes, nameStart, nameEnd-nameStart) );
+        if(log.isDebugEnabled()) {
+            try {
+                log.debug(sm.getString("parameters.bytes",
+                        new String(bytes, start, len, DEFAULT_CHARSET.name())));
+            } catch (UnsupportedEncodingException uee) {
+                // Not possible. All JVMs must support ISO-8859-1
             }
-            if( nameEnd== -1 ) 
-                nameEnd=end;
+        }
 
-            if( ! noEq ) {
-                valStart= (nameEnd < end) ? nameEnd+1 : end;
-                valEnd=ByteChunk.indexOf(bytes, valStart, end, '&');
-                if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
+        int decodeFailCount = 0;
+            
+        int pos = start;
+        int end = start + len;
+
+        while(pos < end) {
+            parameterCount ++;
+
+            if (limit > -1 && parameterCount >= limit) {
+                parseFailed = true;
+                log.warn(sm.getString("parameters.maxCountFail",
+                        Integer.toString(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 '%':
+                    case '+':
+                        // Decoding required
+                        if (parsingName) {
+                            decodeName = true;
+                        } else {
+                            decodeValue = true;
+                        }
+                        pos ++;
+                        break;
+                    default:
+                        pos ++;
+                        break;
+                }
+            } while (!parameterComplete && pos < end);
+
+            if (pos == end) {
+                if (nameEnd == -1) {
+                    nameEnd = pos;
+                } else if (valueStart > -1 && valueEnd == -1){
+                    valueEnd = pos;
+                }
             }
             
-            pos=valEnd+1;
+            if (log.isDebugEnabled() && valueStart == -1) {
+                try {
+                    log.debug(sm.getString("parameters.noequal",
+                            Integer.toString(nameStart),
+                            Integer.toString(nameEnd),
+                            new String(bytes, nameStart, nameEnd-nameStart,
+                                    DEFAULT_CHARSET.name())));
+                } catch (UnsupportedEncodingException uee) {
+                    // Not possible. All JVMs must support ISO-8859-1
+                }
+            }
             
-            if( nameEnd<=nameStart ) {
-                log.warn("Parameters: Invalid chunk ignored.");
+            if (nameEnd <= nameStart ) {
+                if (log.isInfoEnabled()) {
+                    if (valueEnd >= nameStart && log.isDebugEnabled()) {
+                        String extract = null;
+                        try {
+                            extract = new String(bytes, nameStart,
+                                    valueEnd - nameStart,
+                                    DEFAULT_CHARSET.name());
+                        } catch (UnsupportedEncodingException uee) {
+                            // Not possible. All JVMs must support ISO-8859-1
+                        }
+                        log.info(sm.getString("parameters.invalidChunk",
+                                Integer.toString(nameStart),
+                                Integer.toString(valueEnd),
+                                extract));
+                    } else {
+                        log.info(sm.getString("parameters.invalidChunk",
+                                Integer.toString(nameStart),
+                                Integer.toString(nameEnd),
+                                null));
+                    }
+                }
+                parseFailed = true;
                 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, valueStart, valueEnd - valueStart);
+                } catch (IOException ioe) {
+                    // Should never happen...
+                    log.error(sm.getString("paramerers.copyFail"), ioe);
+                }
+            }
+            
             try {
-                addParam( urlDecode(tmpName, enc), urlDecode(tmpValue, enc) );
+                String name;
+                String value;
+
+                if (decodeName) {
+                    urlDecode(tmpName);
+                }
+                tmpName.setCharset(charset);
+                name = tmpName.toString();
+
+                if (decodeValue) {
+                    urlDecode(tmpValue);
+                }
+                tmpValue.setCharset(charset);
+                value = tmpValue.toString();
+
+                addParam(name, value);
             } catch (IOException e) {
-                // Exception during character decoding: skip parameter
-                log.warn("Parameters: Character decoding failed. " + 
-                        "Parameter skipped.", e);
+                parseFailed = true;
+                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);
+                    }
+                }
             }
 
             tmpName.recycle();
             tmpValue.recycle();
+            // Only recycle copies if we used them
+            if (log.isDebugEnabled()) {
+                origName.recycle();
+                origValue.recycle();
+            }
+        }
 
-        } while( pos<end );
+        if (decodeFailCount > 1 && !log.isDebugEnabled()) {
+            log.info(sm.getString("parameters.multipleDecodingFail",
+                    Integer.toString(decodeFailCount)));
+        }
     }
 
-    private String urlDecode(ByteChunk bc, String enc)
+    private void urlDecode(ByteChunk bc)
         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;
-            cc.allocate(bc.getLength(), -1);
-            // Default encoding: fast conversion
-            byte[] bbuf = bc.getBuffer();
-            char[] cbuf = cc.getBuffer();
-            int start = bc.getStart();
-            for (int i = 0; i < bc.getLength(); i++) {
-                cbuf[i] = (char) (bbuf[i + start] & 0xff);
-            }
-            cc.setChars(cbuf, 0, bc.getLength());
-            result = cc.toString();
-            cc.recycle();
-        }
-        return result;
-    }
-
-    public void processParameters( char chars[], int start, int len ) {
-        int end=start+len;
-        int pos=start;
-        
-        if( debug>0 ) 
-            log( "Chars: " + new String( chars, start, len ));
-        do {
-            boolean noEq=false;
-            int nameStart=pos;
-            int valStart=-1;
-            int valEnd=-1;
-            
-            int nameEnd=CharChunk.indexOf(chars, nameStart, end, '=' );
-            int nameEnd2=CharChunk.indexOf(chars, nameStart, end, '&' );
-            if( (nameEnd2!=-1 ) &&
-                ( nameEnd==-1 || nameEnd > nameEnd2) ) {
-                nameEnd=nameEnd2;
-                noEq=true;
-                valStart=nameEnd;
-                valEnd=nameEnd;
-                if(debug>0) log("no equal " + nameStart + " " + nameEnd + " " +
-                        new String(chars, nameStart, nameEnd-nameStart) );
-            }
-            if( nameEnd== -1 ) nameEnd=end;
-            
-            if( ! noEq ) {
-                valStart= (nameEnd < end) ? nameEnd+1 : end;
-                valEnd=CharChunk.indexOf(chars, valStart, end, '&');
-                if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
-            }
-            
-            pos=valEnd+1;
-            
-            if( nameEnd<=nameStart ) {
-                continue;
-                // invalid chunk - no name, it's better to ignore
-                // XXX log it ?
-            }
-            
-            try {
-                tmpNameC.append( chars, nameStart, nameEnd-nameStart );
-                tmpValueC.append( chars, valStart, valEnd-valStart );
-
-                if( debug > 0 )
-                    log( tmpNameC + "= " + tmpValueC);
-
-                if( urlDec==null ) {
-                    urlDec=new UDecoder();   
-                }
-
-                urlDec.convert( tmpNameC );
-                urlDec.convert( tmpValueC );
-
-                if( debug > 0 )
-                    log( tmpNameC + "= " + tmpValueC);
-                
-                addParam( tmpNameC.toString(), tmpValueC.toString() );
-            } catch( IOException ex ) {
-                ex.printStackTrace();
-            }
-
-            tmpNameC.recycle();
-            tmpValueC.recycle();
-
-        } while( pos<end );
-    }
-    
-    public void processParameters( MessageBytes data ) {
-        processParameters(data, encoding);
     }
 
     public void processParameters( MessageBytes data, String encoding ) {
         if( data==null || data.isNull() || data.getLength() <= 0 ) return;
 
-        if( data.getType() == MessageBytes.T_BYTES ) {
-            ByteChunk bc=data.getByteChunk();
-            processParameters( bc.getBytes(), bc.getOffset(),
-                               bc.getLength(), encoding);
-        } else {
-            if (data.getType()!= MessageBytes.T_CHARS ) 
-                data.toChars();
-            CharChunk cc=data.getCharChunk();
-            processParameters( cc.getChars(), cc.getOffset(),
-                               cc.getLength());
+        if( data.getType() != MessageBytes.T_BYTES ) {
+            data.toBytes();
+        }
+        ByteChunk bc=data.getByteChunk();
+        processParameters( bc.getBytes(), bc.getOffset(),
+                           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
+    /**
+     * Debug purpose
      */
     public String paramsAsString() {
-        StringBuffer sb=new StringBuffer();
-        Enumeration en= paramHashStringArray.keys();
-        while( en.hasMoreElements() ) {
-            String k=(String)en.nextElement();
-            sb.append( k ).append("=");
-            String v[]=(String[])paramHashStringArray.get( k );
-            for( int i=0; i<v.length; i++ )
-                sb.append( v[i] ).append(",");
-            sb.append("\n");
+        StringBuffer sb = new StringBuffer();
+        Iterator it = paramHashValues.entrySet().iterator();
+        while (it.hasNext()) {
+            Map.Entry e = (Map.Entry) it.next();
+            sb.append(e.getKey()).append('=');
+            ArrayList values = (ArrayList) e.getValue();
+            for(int i = 0; i < values.size(); i++) {
+                sb.append(values.get(i)).append(',');
+            }
+            sb.append('\n');
         }
         return sb.toString();
     }
 
-    private static int debug=0;
-    private void log(String s ) {
-        if (log.isDebugEnabled())
-            log.debug("Parameters: " + s );
-    }
-   
-    // -------------------- Old code, needs rewrite --------------------
-    
-    /** Used by RequestDispatcher
-     */
-    public void processParameters( String str ) {
-        int end=str.length();
-        int pos=0;
-        if( debug > 0)
-            log("String: " + str );
-        
-        do {
-            boolean noEq=false;
-            int valStart=-1;
-            int valEnd=-1;
-            
-            int nameStart=pos;
-            int nameEnd=str.indexOf('=', nameStart );
-            int nameEnd2=str.indexOf('&', nameStart );
-            if( nameEnd2== -1 ) nameEnd2=end;
-            if( (nameEnd2!=-1 ) &&
-                ( nameEnd==-1 || nameEnd > nameEnd2) ) {
-                nameEnd=nameEnd2;
-                noEq=true;
-                valStart=nameEnd;
-                valEnd=nameEnd;
-                if(debug>0) log("no equal " + nameStart + " " + nameEnd + " " +
-                        str.substring(nameStart, nameEnd));
-            }
-
-            if( nameEnd== -1 ) nameEnd=end;
-
-            if( ! noEq ) {
-                valStart=nameEnd+1;
-                valEnd=str.indexOf('&', valStart);
-                if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
-            }
-            
-            pos=valEnd+1;
-            
-            if( nameEnd<=nameStart ) {
-                continue;
-            }
-            if( debug>0)
-                log( "XXX " + nameStart + " " + nameEnd + " "
-                     + valStart + " " + valEnd );
-            
-            try {
-                tmpNameC.append(str, nameStart, nameEnd-nameStart );
-                tmpValueC.append(str, valStart, valEnd-valStart );
-            
-                if( debug > 0 )
-                    log( tmpNameC + "= " + tmpValueC);
-
-                if( urlDec==null ) {
-                    urlDec=new UDecoder();   
-                }
-
-                urlDec.convert( tmpNameC );
-                urlDec.convert( tmpValueC );
-
-                if( debug > 0 )
-                    log( tmpNameC + "= " + tmpValueC);
-                
-                addParam( tmpNameC.toString(), tmpValueC.toString() );
-            } catch( IOException ex ) {
-                ex.printStackTrace();
-            }
-
-            tmpNameC.recycle();
-            tmpValueC.recycle();
-
-        } while( pos<end );
-    }
-
-
 }
diff --git a/container/catalina/src/conf/web.xml b/container/catalina/src/conf/web.xml
index b510f82..79805b6 100644
--- a/container/catalina/src/conf/web.xml
+++ b/container/catalina/src/conf/web.xml
@@ -404,6 +404,19 @@
 
   <!-- ================== Built In Filter Definitions ===================== -->
 
+  <!-- A filter that triggers request parameters parsing and rejects the    -->
+  <!-- request if some parameters were skipped because of parsing errors or -->
+  <!-- request size limitations.                                            -->
+<!--
+    <filter>
+        <filter-name>failedRequestFilter</filter-name>
+        <filter-class>
+          org.apache.catalina.filters.FailedRequestFilter
+        </filter-class>
+    </filter>
+-->
+
+
   <!-- NOTE: An SSI Servlet is also available as an alternative SSI         -->
   <!-- implementation. Use either the Servlet or the Filter but NOT both.   -->
   <!--                                                                      -->
@@ -467,6 +480,14 @@
 
   <!-- ==================== Built In Filter Mappings ====================== -->
 
+  <!-- The mapping for the Failed Request Filter -->
+<!--
+    <filter-mapping>
+        <filter-name>failedRequestFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+-->
+
   <!-- The mapping for the SSI Filter -->
 <!--
     <filter-mapping>
diff --git a/container/catalina/src/share/org/apache/catalina/Globals.java b/container/catalina/src/share/org/apache/catalina/Globals.java
index e1def33..7f5fb9e 100644
--- a/container/catalina/src/share/org/apache/catalina/Globals.java
+++ b/container/catalina/src/share/org/apache/catalina/Globals.java
@@ -327,6 +327,17 @@
 
 
     /**
+     * The request attribute that is set to <code>Boolean.TRUE</code> if some request
+     * parameters have been ignored during request parameters parsing. It can
+     * happen, for example, if there is a limit on the total count of parseable
+     * parameters, or if parameter cannot be decoded, or any other error
+     * happened during parameter parsing.
+     */
+    public static final String PARAMETER_PARSE_FAILED_ATTR =
+        "org.apache.catalina.parameter_parse_failed";
+
+
+    /**
      * The master flag which controls strict servlet specification 
      * compliance.
      */
diff --git a/container/catalina/src/share/org/apache/catalina/connector/Connector.java b/container/catalina/src/share/org/apache/catalina/connector/Connector.java
index 6bb1e4c..29be95b 100644
--- a/container/catalina/src/share/org/apache/catalina/connector/Connector.java
+++ b/container/catalina/src/share/org/apache/catalina/connector/Connector.java
@@ -18,7 +18,6 @@
 
 package org.apache.catalina.connector;
 
-import java.lang.reflect.Method;
 import java.net.URLEncoder;
 import java.util.HashMap;
 
@@ -187,6 +186,14 @@
 
 
     /**
+     * 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.
      */
@@ -498,14 +505,34 @@
     }
 
 
-     /**
-      * 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;
+    }
 
 
     /**
diff --git a/container/catalina/src/share/org/apache/catalina/connector/Request.java b/container/catalina/src/share/org/apache/catalina/connector/Request.java
index 4a89001..b33a678 100644
--- a/container/catalina/src/share/org/apache/catalina/connector/Request.java
+++ b/container/catalina/src/share/org/apache/catalina/connector/Request.java
@@ -864,6 +864,11 @@
             return (requestDispatcherPath == null) 
                 ? getRequestPathMB().toString()
                 : requestDispatcherPath.toString();
+        } else if (name.equals(Globals.PARAMETER_PARSE_FAILED_ATTR)) {
+            if (coyoteRequest.getParameters().isParseFailed()) {
+                return Boolean.TRUE;
+            }
+            return null;
         }
 
         Object attr=attributes.get(name);
@@ -912,6 +917,26 @@
     /**
      * Return the names of all request attributes for this Request, or an
      * empty <code>Enumeration</code> if there are none.
+     * Note that the attribute names return will only be those for the attributes set via
+     * {@link #setAttribute(String, Object)}. Tomcat internal attributes will
+     * not be included although they are accessible via
+     * {@link #getAttribute(String)}. The Tomcat internal attributes include:
+     * <ul>
+     * <li>{@link Globals.DISPATCHER_TYPE_ATTR}</li>
+     * <li>{@link Globals.DISPATCHER_REQUEST_PATH_ATTR}</li>
+     * <li>{@link Globals.CERTIFICATES_ATTR} (SSL connections only)</li>
+     * <li>{@link Globals.CIPHER_SUITE_ATTR} (SSL connections only)</li>
+     * <li>{@link Globals.KEY_SIZE_ATTR} (SSL connections only)</li>
+     * <li>{@link Globals.SSL_SESSION_ID_ATTR} (SSL connections only)</li>
+     * <li>{@link Globals#PARAMETER_PARSE_FAILED_ATTR}</li>
+     * </ul>
+     * The underlying connector may also expose request attributes. These all
+     * have names starting with "org.apache.tomcat" and include:
+     * <ul>
+     * <li>org.apache.tomcat.sendfile.support</li>
+     * </ul>
+     * Connector implementations may return some, all or none of these
+     * attributes and may also support additional attributes.
      */
     public Enumeration getAttributeNames() {
         if (isSecure()) {
@@ -2419,6 +2444,8 @@
         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
@@ -2459,47 +2486,56 @@
         if (!("application/x-www-form-urlencoded".equals(contentType)))
             return;
 
+        boolean success = false;
         int len = getContentLength();
 
-        if (len > 0) {
-            int maxPostSize = connector.getMaxPostSize();
-            if ((maxPostSize > 0) && (len > maxPostSize)) {
-                context.getLogger().info
-                    (sm.getString("coyoteRequest.postTooLarge"));
-                throw new IllegalStateException("Post too large");
-            }
-            try {
-                byte[] formData = null;
-                if (len < CACHED_POST_LEN) {
-                    if (postData == null)
-                        postData = new byte[CACHED_POST_LEN];
-                    formData = postData;
-                } else {
-                    formData = new byte[len];
+        try {
+            if (len > 0) {
+                int maxPostSize = connector.getMaxPostSize();
+                if ((maxPostSize > 0) && (len > maxPostSize)) {
+                    context.getLogger().info
+                        (sm.getString("coyoteRequest.postTooLarge"));
+                    throw new IllegalStateException("Post too large");
                 }
-                int actualLen = readPostBody(formData, len);
-                if (actualLen == len) {
+                try {
+                    byte[] formData = null;
+                    if (len < CACHED_POST_LEN) {
+                        if (postData == null)
+                            postData = new byte[CACHED_POST_LEN];
+                        formData = postData;
+                    } else {
+                        formData = new byte[len];
+                    }
+                    if (readPostBody(formData, len) != len) {
+                        return;
+                    }
                     parameters.processParameters(formData, 0, len);
+                } catch (Throwable t) {
+                    context.getLogger().warn
+                        (sm.getString("coyoteRequest.parseParameters"), t);
+                    return;
                 }
-            } catch (Throwable t) {
-                context.getLogger().warn
-                    (sm.getString("coyoteRequest.parseParameters"), t);
-            }
-        } else if ("chunked".equalsIgnoreCase(
-                coyoteRequest.getHeader("transfer-encoding"))) {
-            byte[] formData = null;
-            try {
-                formData = readChunkedPostBody();
-            } catch (IOException e) {
-                // Client disconnect
-                if (context.getLogger().isDebugEnabled()) {
-                    context.getLogger().debug(
-                            sm.getString("coyoteRequest.parseParameters"), e);
+            } else if ("chunked".equalsIgnoreCase(
+                    coyoteRequest.getHeader("transfer-encoding"))) {
+                byte[] formData = null;
+                try {
+                    formData = readChunkedPostBody();
+                } catch (IOException e) {
+                    // Client disconnect
+                    if (context.getLogger().isDebugEnabled()) {
+                        context.getLogger().debug(
+                                sm.getString("coyoteRequest.parseParameters"), e);
+                    }
+                    return;
                 }
-                return;
+                if (formData != null) {
+                    parameters.processParameters(formData, 0, formData.length);
+                }
             }
-            if (formData != null) {
-                parameters.processParameters(formData, 0, formData.length);
+            success = true;
+        } finally {
+            if (!success) {
+                parameters.setParseFailed(true);
             }
         }
 
diff --git a/container/catalina/src/share/org/apache/catalina/connector/mbeans-descriptors.xml b/container/catalina/src/share/org/apache/catalina/connector/mbeans-descriptors.xml
index fb87e82..ea35993 100644
--- a/container/catalina/src/share/org/apache/catalina/connector/mbeans-descriptors.xml
+++ b/container/catalina/src/share/org/apache/catalina/connector/mbeans-descriptors.xml
@@ -109,6 +109,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"/>
diff --git a/container/catalina/src/share/org/apache/catalina/filters/FailedRequestFilter.java b/container/catalina/src/share/org/apache/catalina/filters/FailedRequestFilter.java
new file mode 100644
index 0000000..7ba6cde
--- /dev/null
+++ b/container/catalina/src/share/org/apache/catalina/filters/FailedRequestFilter.java
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+package org.apache.catalina.filters;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Globals;
+
+/**
+ * Filter that will reject requests if there was a failure during parameter
+ * parsing. This filter can be used to ensure that none parameter values
+ * submitted by client are lost.
+ *
+ * <p>
+ * Note that it has side effect that it triggers parameter parsing and thus
+ * consumes the body for POST requests. Parameter parsing does check content
+ * type of the request, so there should not be problems with addresses that use
+ * <code>request.getInputStream()</code> and <code>request.getReader()</code>,
+ * if requests parsed by them do not use standard value for content mime-type.
+ */
+public class FailedRequestFilter implements Filter {
+
+    public void init(FilterConfig filterConfig) {
+        // NOOP
+    }
+
+    public void destroy() {
+        // NOOP
+    }
+
+    public void doFilter(ServletRequest request, ServletResponse response,
+            FilterChain chain) throws IOException, ServletException {
+        if (!isGoodRequest(request)) {
+            ((HttpServletResponse) response)
+                    .sendError(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        chain.doFilter(request, response);
+    }
+
+    private boolean isGoodRequest(ServletRequest request) {
+        // Trigger parsing of parameters
+        request.getParameter("none");
+        // Detect failure
+        if (request.getAttribute(Globals.PARAMETER_PARSE_FAILED_ATTR) != null) {
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/container/webapps/docs/config/ajp.xml b/container/webapps/docs/config/ajp.xml
index 70696e0..b07316d 100644
--- a/container/webapps/docs/config/ajp.xml
+++ b/container/webapps/docs/config/ajp.xml
@@ -95,6 +95,14 @@
       By default, DNS lookups are enabled.</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. Note that
+      <code>FailedRequestFilter</code> <a href="filter.html">filter</a> can be
+      used to reject requests that hit the limit.</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 feature can be disabled by
diff --git a/container/webapps/docs/config/filter.xml b/container/webapps/docs/config/filter.xml
new file mode 100644
index 0000000..4f00df7
--- /dev/null
+++ b/container/webapps/docs/config/filter.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0"?>
+<!--
+  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.
+-->
+<!DOCTYPE document [
+  <!ENTITY project SYSTEM "project.xml">
+]>
+<document url="filter.html">
+
+  &project;
+
+  <properties>
+    <title>Container Provided Filters</title>
+  </properties>
+
+<body>
+
+<section name="Table of Contents">
+<toc/>
+</section>
+
+<section name="Introduction">
+
+  <p>Tomcat provides a number of <strong>Filters</strong> which may be
+  configured for use with all web applications using
+  <code>$CATALINA_BASE/conf/web.xml</code> or may be configured for individual
+  web applications by configuring them in the application's
+  <code>WEB-INF/web.xml</code>. Each filter is described below.</p>
+
+    <blockquote><em>
+    <p>This description uses the variable name $CATALINA_BASE to refer the
+    base directory against which most relative paths are resolved. If you have
+    not configured Tomcat for multiple instances by setting a CATALINA_BASE
+    directory, then $CATALINA_BASE will be set to the value of $CATALINA_HOME,
+    the directory into which you have installed Tomcat.</p>
+    </em></blockquote>
+
+</section>
+
+
+<section name="Failed Request Filter">
+
+  <subsection name="Introduction">
+
+    <p>This filter triggers parameters parsing in a request and rejects the
+    request if some parameters were skipped during parameter parsing because
+    of parsing errors or request size limitations (such as
+    <code>maxParameterCount</code> attribute in a
+    <a href="http.html">Connector</a>).
+    This filter can be used to ensure that none parameter values submitted by
+    client are lost.</p>
+
+    <p>Note that parameter parsing may consume the body of an HTTP request, so
+    caution is needed if the servlet protected by this filter uses
+    <code>request.getInputStream()</code> or <code>request.getReader()</code>
+    calls. In general the risk of breaking a web application by adding this
+    filter is not so high, because parameter parsing does check content type
+    of the request before consuming the request body.</p>
+
+    <p>The request is rejected with HTTP status code 400 (Bad Request).</p>
+
+  </subsection>
+
+  <subsection name="Filter Class Name">
+
+    <p>The filter class name for the Failed Request Filter is
+    <strong><code>org.apache.catalina.filters.FailedRequestFilter</code>
+    </strong>.</p>
+
+  </subsection>
+
+  <subsection name="Initialisation parameters">
+
+    <p>The Failed Request Filter does not support any initialization parameters.</p>
+
+  </subsection>
+
+</section>
+
+
+</body>
+
+
+</document>
diff --git a/container/webapps/docs/config/http.xml b/container/webapps/docs/config/http.xml
index dda7518..9ccb022 100644
--- a/container/webapps/docs/config/http.xml
+++ b/container/webapps/docs/config/http.xml
@@ -98,6 +98,14 @@
       By default, DNS lookups are enabled.</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. Note that
+      <code>FailedRequestFilter</code> <a href="filter.html">filter</a> can be
+      used to reject requests that hit the limit.</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
diff --git a/container/webapps/docs/config/project.xml b/container/webapps/docs/config/project.xml
index c008830..d956909 100644
--- a/container/webapps/docs/config/project.xml
+++ b/container/webapps/docs/config/project.xml
@@ -49,6 +49,7 @@
     </menu>
 
     <menu name="Nested Components">
+        <item name="Filter"                href="filter.html"/>
         <item name="Global Resources"      href="globalresources.html"/>
         <item name="Loader"                href="loader.html"/>
         <item name="Manager"               href="manager.html"/>