Sophie

Sophie

distrib > Mageia > 3 > i586 > media > core-release-src > by-pkgid > 13381d75d67ffe2e5359fb12f7794ba5 > files > 36

tomcat5-5.5.31-14.mga3.src.rpm

Index: container/webapps/docs/config/filter.xml
===================================================================
--- container/webapps/docs/config/filter.xml	(révision 0)
+++ container/webapps/docs/config/filter.xml	(révision 1221282)
@@ -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>

Modification de propriétés sur container/webapps/docs/config/filter.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: container/webapps/docs/config/project.xml
===================================================================
--- container/webapps/docs/config/project.xml	(révision 1221281)
+++ container/webapps/docs/config/project.xml	(révision 1221282)
@@ -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"/> 
Index: container/webapps/docs/config/ajp.xml
===================================================================
--- container/webapps/docs/config/ajp.xml	(révision 1221281)
+++ container/webapps/docs/config/ajp.xml	(révision 1221282)
@@ -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
Index: container/webapps/docs/config/http.xml
===================================================================
--- container/webapps/docs/config/http.xml	(révision 1221281)
+++ container/webapps/docs/config/http.xml	(révision 1221282)
@@ -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
Index: container/catalina/src/share/org/apache/catalina/Globals.java
===================================================================
--- container/catalina/src/share/org/apache/catalina/Globals.java	(révision 1221281)
+++ container/catalina/src/share/org/apache/catalina/Globals.java	(révision 1221282)
@@ -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.
      */
Index: container/catalina/src/share/org/apache/catalina/filters/FailedRequestFilter.java
===================================================================
--- container/catalina/src/share/org/apache/catalina/filters/FailedRequestFilter.java	(révision 0)
+++ container/catalina/src/share/org/apache/catalina/filters/FailedRequestFilter.java	(révision 1221282)
@@ -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;
+    }
+
+}

Modification de propriétés sur container/catalina/src/share/org/apache/catalina/filters/FailedRequestFilter.java
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: container/catalina/src/share/org/apache/catalina/connector/mbeans-descriptors.xml
===================================================================
--- container/catalina/src/share/org/apache/catalina/connector/mbeans-descriptors.xml	(révision 1221281)
+++ container/catalina/src/share/org/apache/catalina/connector/mbeans-descriptors.xml	(révision 1221282)
@@ -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"/>
Index: container/catalina/src/share/org/apache/catalina/connector/Request.java
===================================================================
--- container/catalina/src/share/org/apache/catalina/connector/Request.java	(révision 1221281)
+++ container/catalina/src/share/org/apache/catalina/connector/Request.java	(révision 1221282)
@@ -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);
             }
         }
 
Index: container/catalina/src/share/org/apache/catalina/connector/Connector.java
===================================================================
--- container/catalina/src/share/org/apache/catalina/connector/Connector.java	(révision 1221281)
+++ container/catalina/src/share/org/apache/catalina/connector/Connector.java	(révision 1221282)
@@ -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,17 +505,37 @@
     }
 
 
-     /**
-      * 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;
+    }
+
+
+    /**
      * Return the maximum size of a POST which will be automatically
      * parsed by the container.
      */
Index: container/catalina/src/conf/web.xml
===================================================================
--- container/catalina/src/conf/web.xml	(révision 1221281)
+++ container/catalina/src/conf/web.xml	(révision 1221282)
@@ -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>
Index: connectors/coyote/src/java/org/apache/coyote/Request.java
===================================================================
--- connectors/coyote/src/java/org/apache/coyote/Request.java	(révision 1221281)
+++ connectors/coyote/src/java/org/apache/coyote/Request.java	(révision 1221282)
@@ -73,7 +73,6 @@
 
         parameters.setQuery(queryMB);
         parameters.setURLDecoder(urlDecoder);
-        parameters.setHeaders(headers);
 
     }
 
Index: connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties
===================================================================
--- connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties	(révision 0)
+++ connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties	(révision 1221282)
@@ -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

Modification de propriétés sur connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: connectors/util/java/org/apache/tomcat/util/http/Parameters.java
===================================================================
--- connectors/util/java/org/apache/tomcat/util/http/Parameters.java	(révision 1221281)
+++ connectors/util/java/org/apache/tomcat/util/http/Parameters.java	(révision 1221282)
@@ -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();
+        parseFailed = false;
     }
-    
-    // -------------------- Sub-request support --------------------
 
-    public Parameters getCurrentSet() {
-        if( currentChild==null )
-            return this;
-        return currentChild;
-    }
+    // -------------------- Data access --------------------
+    // Access to the current name/values, no side effect ( processing ).
+    // You must explicitly call handleQueryParameters and the post methods.
     
-    /** 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;
+    public void addParameterValues(String key, String[] newValues) {
+        if (key == null) {
             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 );
-    }
-
-    /** 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.
-    
-    // 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];
-            }
+        ArrayList values = (ArrayList) paramHashValues.get(key);
+        if (values == null) {
+            values = new ArrayList(newValues.length);
+            paramHashValues.put(key, values);
         } else {
-            values = newValues;
+            values.ensureCapacity(values.size() + newValues.length);
         }
-
-        paramHashStringArray.put(key, values);
+        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"
+        ArrayList values = (ArrayList) paramHashValues.get(name);
+        if (values == null) {
+            return null;
         }
-
-        // no "facade"
-        String values[]=(String[])paramHashStringArray.get(name);
-        return values;
+        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 ));
+        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
+            }
+        }
 
-        do {
-            boolean noEq=false;
-            int valStart=-1;
-            int valEnd=-1;
+        int decodeFailCount = 0;
             
-            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) );
+        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;
             }
-            if( nameEnd== -1 ) 
-                nameEnd=end;
+            int nameStart = pos;
+            int nameEnd = -1;
+            int valueStart = -1;
+            int valueEnd = -1;
 
-            if( ! noEq ) {
-                valStart= (nameEnd < end) ? nameEnd+1 : end;
-                valEnd=ByteChunk.indexOf(bytes, valStart, end, '&');
-                if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
+            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));
     }
 
-    /** 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");
+    private Charset getCharset(String encoding) {
+        if (encoding == null) {
+            return DEFAULT_CHARSET;
         }
-        return sb.toString();
+        try {
+            return B2CConverter.getCharset(encoding);
+        } catch (UnsupportedEncodingException e) {
+            return DEFAULT_CHARSET;
+        }
     }
 
-    private static int debug=0;
-    private void log(String s ) {
-        if (log.isDebugEnabled())
-            log.debug("Parameters: " + s );
-    }
-   
-    // -------------------- Old code, needs rewrite --------------------
-    
-    /** Used by RequestDispatcher
+    /**
+     * Debug purpose
      */
-    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));
+    public String paramsAsString() {
+        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(',');
             }
-
-            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 );
+            sb.append('\n');
+        }
+        return sb.toString();
     }
 
-
 }
Index: connectors/util/java/org/apache/tomcat/util/buf/StringCache.java
===================================================================
--- connectors/util/java/org/apache/tomcat/util/buf/StringCache.java	(révision 1221281)
+++ connectors/util/java/org/apache/tomcat/util/buf/StringCache.java	(révision 1221282)
@@ -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() {
Index: connectors/util/java/org/apache/tomcat/util/buf/UDecoder.java
===================================================================
--- connectors/util/java/org/apache/tomcat/util/buf/UDecoder.java	(révision 1221281)
+++ connectors/util/java/org/apache/tomcat/util/buf/UDecoder.java	(révision 1221282)
@@ -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;
             }
Index: connectors/util/java/org/apache/tomcat/util/buf/MessageBytes.java
===================================================================
--- connectors/util/java/org/apache/tomcat/util/buf/MessageBytes.java	(révision 1221281)
+++ connectors/util/java/org/apache/tomcat/util/buf/MessageBytes.java	(révision 1221282)
@@ -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);
     }
 
     /** 
Index: connectors/util/java/org/apache/tomcat/util/buf/ByteChunk.java
===================================================================
--- connectors/util/java/org/apache/tomcat/util/buf/ByteChunk.java	(révision 1221281)
+++ connectors/util/java/org/apache/tomcat/util/buf/ByteChunk.java	(révision 1221282)
@@ -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()
Index: connectors/util/java/org/apache/tomcat/util/buf/B2CConverter.java
===================================================================
--- connectors/util/java/org/apache/tomcat/util/buf/B2CConverter.java	(révision 1221281)
+++ connectors/util/java/org/apache/tomcat/util/buf/B2CConverter.java	(révision 1221282)
@@ -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;
     }
 
Index: connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties
===================================================================
--- connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties	(révision 1224639)
+++ connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties	(révision 1224640)
@@ -14,7 +14,7 @@
 # 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.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
Index: connectors/util/java/org/apache/tomcat/util/http/Parameters.java
===================================================================
--- connectors/util/java/org/apache/tomcat/util/http/Parameters.java	(révision 1224639)
+++ connectors/util/java/org/apache/tomcat/util/http/Parameters.java	(révision 1224640)
@@ -353,7 +353,7 @@
                     origValue.append(bytes, valueStart, valueEnd - valueStart);
                 } catch (IOException ioe) {
                     // Should never happen...
-                    log.error(sm.getString("paramerers.copyFail"), ioe);
+                    log.error(sm.getString("parameters.copyFail"), ioe);
                 }
             }
             
Index: connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties
===================================================================
--- connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties	(révision 1228190)
+++ connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties	(révision 1228191)
@@ -17,6 +17,7 @@
 parameters.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.emptyChunk=Empty parameter chunk ignored
 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.
Index: connectors/util/java/org/apache/tomcat/util/http/Parameters.java
===================================================================
--- connectors/util/java/org/apache/tomcat/util/http/Parameters.java	(révision 1228190)
+++ connectors/util/java/org/apache/tomcat/util/http/Parameters.java	(révision 1228191)
@@ -315,7 +315,17 @@
             }
             
             if (nameEnd <= nameStart ) {
+                if (valueStart == -1) {
+                    // &&
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("parameters.emptyChunk"));
+                    }
+                    // Do not flag as error
+                    continue;
+                }
+                // &=foo&
                 if (log.isInfoEnabled()) {
+
                     if (valueEnd >= nameStart && log.isDebugEnabled()) {
                         String extract = null;
                         try {
@@ -342,7 +352,11 @@
             }
             
             tmpName.setBytes(bytes, nameStart, nameEnd - nameStart);
-            tmpValue.setBytes(bytes, valueStart, valueEnd - valueStart);
+            if (valueStart >= 0) {
+                tmpValue.setBytes(bytes, valueStart, valueEnd - valueStart);
+            } else {
+                tmpValue.setBytes(bytes, 0, 0);
+            }
 
             // Take copies as if anything goes wrong originals will be
             // corrupted. This means original values can be logged.
@@ -350,7 +364,11 @@
             if (log.isDebugEnabled()) {
                 try {
                     origName.append(bytes, nameStart, nameEnd - nameStart);
-                    origValue.append(bytes, valueStart, valueEnd - valueStart);
+                    if (valueStart >= 0) {
+                        origValue.append(bytes, valueStart, valueEnd - valueStart);
+                    } else {
+                        origValue.append(bytes, 0, 0);
+                    }
                 } catch (IOException ioe) {
                     // Should never happen...
                     log.error(sm.getString("parameters.copyFail"), ioe);
@@ -367,11 +385,15 @@
                 tmpName.setCharset(charset);
                 name = tmpName.toString();
 
-                if (decodeValue) {
-                    urlDecode(tmpValue);
+                if (valueStart >= 0) {
+                    if (decodeValue) {
+                        urlDecode(tmpValue);
+                    }
+                    tmpValue.setCharset(charset);
+                    value = tmpValue.toString();
+                } else {
+                    value = "";
                 }
-                tmpValue.setCharset(charset);
-                value = tmpValue.toString();
 
                 addParam(name, value);
             } catch (IOException e) {