Implement maxHeaderCount attribute on HTTP Connectors. It is equivalent of LimitRequestFields directive of Apache HTTPD

git-svn-id: https://svn.apache.org/repos/asf/tomcat/tc5.5.x/trunk@1392247 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/STATUS.txt b/STATUS.txt
index 43fbd91..e69b89b 100644
--- a/STATUS.txt
+++ b/STATUS.txt
@@ -28,36 +28,6 @@
 PATCHES PROPOSED TO BACKPORT:
   [ New proposals should be added at the end of the list ]
 
-* Implement maxHeaderCount attribute on HTTP Connectors.
-  It is equivalent of LimitRequestFields directive of Apache HTTPD
-  See r1356239 in Tomcat 6.
-
-  Notes:
-  1. Implemented for HTTP protocol only. (MimeHeaders.setLimit() is called
-  by HTTP protocol processors only).
-
-  I suppose that users of AJP can leverage the LimitRequestFields directive
-  in Apache HTTPD server.
-
-  2. The feature is manageable through JMX on the ProtocolHandler MBean.
-
-  Unlike later Tomcat versions, I did not add setter/getter methods to
-  Connector class and did not expose the property on Connector MBean.
-
-  Note that Catalina MBeans are not visible in Tomcat 5.5 by default.
-  See r1356696 for instructions.
-
-  3. To test the feature one can use
-    http://localhost:8080/servlets-examples/servlet/RequestHeaderExample
-
-  Refreshing the page in Firefox changes the number of headers in incoming request
-  (adds 'cache-control' for "F5" refresh, adds 'pragma=no-cache' for "Ctrl+F5" refresh).
-
-  Patch:
-  http://people.apache.org/~kkolinko/patches/2012-07-03_tc55_maxHeaderCount_v1.patch
-  +1: kkolinko, markt, kfujino
-  -1:
-
 * Various DIGEST improvements ported from Tomcat 7
   http://people.apache.org/~markt/patches/2012-08-28-digest-tc5.patch
   +1: markt, kkolinko, kfujino
diff --git a/connectors/http11/src/java/org/apache/coyote/http11/Http11AprProcessor.java b/connectors/http11/src/java/org/apache/coyote/http11/Http11AprProcessor.java
index dd9ca0e..ec43d48 100644
--- a/connectors/http11/src/java/org/apache/coyote/http11/Http11AprProcessor.java
+++ b/connectors/http11/src/java/org/apache/coyote/http11/Http11AprProcessor.java
@@ -803,6 +803,8 @@
                 if (!disableUploadTimeout) {
                     Socket.timeoutSet(socket, timeout * 1000);
                 }
+                // Set this every time in case limit has been changed via JMX
+                request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());
                 inputBuffer.parseHeaders();
             } catch (IOException e) {
                 error = true;
diff --git a/connectors/http11/src/java/org/apache/coyote/http11/Http11AprProtocol.java b/connectors/http11/src/java/org/apache/coyote/http11/Http11AprProtocol.java
index cb1e3bb..96270bc 100644
--- a/connectors/http11/src/java/org/apache/coyote/http11/Http11AprProtocol.java
+++ b/connectors/http11/src/java/org/apache/coyote/http11/Http11AprProtocol.java
@@ -367,6 +367,15 @@
         setAttribute("maxHttpHeaderSize", "" + valueI);
     }
 
+    public int getMaxHeaderCount() {
+        return ep.getMaxHeaderCount();
+    }
+
+    public void setMaxHeaderCount(int maxHeaderCount) {
+        ep.setMaxHeaderCount(maxHeaderCount);
+        setAttribute("maxHeaderCount", "" + maxHeaderCount);
+    }
+
     public String getRestrictedUserAgents() {
         return restrictedUserAgents;
     }
diff --git a/connectors/http11/src/java/org/apache/coyote/http11/Http11BaseProtocol.java b/connectors/http11/src/java/org/apache/coyote/http11/Http11BaseProtocol.java
index 9fb5ea8..0ef5902 100644
--- a/connectors/http11/src/java/org/apache/coyote/http11/Http11BaseProtocol.java
+++ b/connectors/http11/src/java/org/apache/coyote/http11/Http11BaseProtocol.java
@@ -377,6 +377,15 @@
         setAttribute("maxHttpHeaderSize", "" + valueI);
     }
 
+    public int getMaxHeaderCount() {
+        return ep.getMaxHeaderCount();
+    }
+
+    public void setMaxHeaderCount(int maxHeaderCount) {
+        ep.setMaxHeaderCount(maxHeaderCount);
+        setAttribute("maxHeaderCount", "" + maxHeaderCount);
+    }
+
     public String getRestrictedUserAgents() {
         return restrictedUserAgents;
     }
diff --git a/connectors/http11/src/java/org/apache/coyote/http11/Http11Processor.java b/connectors/http11/src/java/org/apache/coyote/http11/Http11Processor.java
index 473a12a..17999cd 100644
--- a/connectors/http11/src/java/org/apache/coyote/http11/Http11Processor.java
+++ b/connectors/http11/src/java/org/apache/coyote/http11/Http11Processor.java
@@ -839,6 +839,8 @@
                 if (!disableUploadTimeout) {
                     socket.setSoTimeout(timeout);
                 }
+                // Set this every time in case limit has been changed via JMX
+                request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());
                 inputBuffer.parseHeaders();
             } catch (IOException e) {
                 error = true;
diff --git a/connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties b/connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties
index 815751d..badd276 100644
--- a/connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties
+++ b/connectors/util/java/org/apache/tomcat/util/http/LocalStrings.properties
@@ -22,3 +22,5 @@
 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
+
+headers.maxCountFail=More than the maximum allowed number of headers ([{0}]) were detected.
diff --git a/connectors/util/java/org/apache/tomcat/util/http/MimeHeaders.java b/connectors/util/java/org/apache/tomcat/util/http/MimeHeaders.java
index 0640cd5..7d9c217 100644
--- a/connectors/util/java/org/apache/tomcat/util/http/MimeHeaders.java
+++ b/connectors/util/java/org/apache/tomcat/util/http/MimeHeaders.java
@@ -22,6 +22,7 @@
 import java.util.Enumeration;
 
 import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.res.StringManager;
 
 /* XXX XXX XXX Need a major rewrite  !!!!
  */
@@ -96,7 +97,10 @@
      *  XXX  make it configurable ( fine-tuning of web-apps )
      */
     public static final int DEFAULT_HEADER_SIZE=8;
-    
+
+    private static final StringManager sm =
+            StringManager.getManager("org.apache.tomcat.util.http");
+
     /**
      * The header fields.
      */
@@ -109,12 +113,30 @@
     private int count;
 
     /**
+     * The limit on the number of header fields.
+     */
+    private int limit = -1;
+
+    /**
      * Creates a new MimeHeaders object using a default buffer size.
      */
     public MimeHeaders() {
     }
 
     /**
+     * Set limit on the number of header fields.
+     */
+    public void setLimit(int limit) {
+        this.limit = limit;
+        if (limit > 0 && headers.length > limit && count < limit) {
+            // shrink header list array
+            MimeHeaderField tmp[] = new MimeHeaderField[limit];
+            System.arraycopy(headers, 0, tmp, 0, count);
+            headers = tmp;
+        }
+    }
+
+    /**
      * Clears all header fields.
      */
     // [seguin] added for consistency -- most other objects have recycle().
@@ -213,11 +235,19 @@
      * field has not had its name or value initialized.
      */
     private MimeHeaderField createHeader() {
+        if (limit > -1 && count >= limit) {
+            throw new IllegalStateException(sm.getString(
+                    "headers.maxCountFail", new Integer(limit)));
+        }
         MimeHeaderField mh;
         int len = headers.length;
         if (count >= len) {
             // expand header list array
-            MimeHeaderField tmp[] = new MimeHeaderField[count * 2];
+            int newLength = count * 2;
+            if (limit > 0 && newLength > limit) {
+                newLength = limit;
+            }
+            MimeHeaderField tmp[] = new MimeHeaderField[newLength];
             System.arraycopy(headers, 0, tmp, 0, len);
             headers = tmp;
         }
diff --git a/connectors/util/java/org/apache/tomcat/util/net/AprEndpoint.java b/connectors/util/java/org/apache/tomcat/util/net/AprEndpoint.java
index 01646f6..01b6081 100644
--- a/connectors/util/java/org/apache/tomcat/util/net/AprEndpoint.java
+++ b/connectors/util/java/org/apache/tomcat/util/net/AprEndpoint.java
@@ -455,6 +455,18 @@
     public void setSSLVerifyDepth(int SSLVerifyDepth) { this.SSLVerifyDepth = SSLVerifyDepth; }
 
 
+    /**
+     * The maximum number of headers in a request that are allowed.
+     * 100 by default. A value of less than 0 means no limit.
+     */
+    private int maxHeaderCount = 100; // as in Apache HTTPD server
+    public int getMaxHeaderCount() {
+        return maxHeaderCount;
+    }
+    public void setMaxHeaderCount(int maxHeaderCount) {
+        this.maxHeaderCount = maxHeaderCount;
+    }
+
     // --------------------------------------------------------- Public Methods
 
 
diff --git a/connectors/util/java/org/apache/tomcat/util/net/PoolTcpEndpoint.java b/connectors/util/java/org/apache/tomcat/util/net/PoolTcpEndpoint.java
index ee62a28..a8be3d4 100644
--- a/connectors/util/java/org/apache/tomcat/util/net/PoolTcpEndpoint.java
+++ b/connectors/util/java/org/apache/tomcat/util/net/PoolTcpEndpoint.java
@@ -89,7 +89,12 @@
     protected int socketTimeout=-1;
     private boolean lf = true;
 
-    
+    /**
+     * The maximum number of headers in a request that are allowed.
+     * 100 by default. A value of less than 0 means no limit.
+     */
+    private int maxHeaderCount = 100; // as in Apache HTTPD server
+
     // ------ Leader follower fields
 
     
@@ -273,6 +278,14 @@
         }
     }
 
+    public int getMaxHeaderCount() {
+        return maxHeaderCount;
+    }
+
+    public void setMaxHeaderCount(int maxHeaderCount) {
+        this.maxHeaderCount = maxHeaderCount;
+    }
+
     public int getCurrentThreadCount() {
         return curThreads;
     }
diff --git a/container/webapps/docs/changelog.xml b/container/webapps/docs/changelog.xml
index 580f832..979722e 100644
--- a/container/webapps/docs/changelog.xml
+++ b/container/webapps/docs/changelog.xml
@@ -76,6 +76,9 @@
         Ensure that the chunked input filter is correctly recycled between
         requests. (kkolinko/jim)
       </fix>
+      <add>
+        Implement the maxHeaderCount for the HTTP connectors. (kkolinko)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Webapps">
diff --git a/container/webapps/docs/config/http.xml b/container/webapps/docs/config/http.xml
index f842712..66ff8db 100644
--- a/container/webapps/docs/config/http.xml
+++ b/container/webapps/docs/config/http.xml
@@ -277,6 +277,13 @@
       this attribute is set to "true".</p>
     </attribute>
 
+    <attribute name="maxHeaderCount" required="false">
+      <p>The maximum number of headers in a request that are allowed by the
+      container. A request that contains more headers than the specified limit
+      will be rejected. A value of less than 0 means no limit.
+      If not specified, a default of 100 is used.</p>
+    </attribute>
+
     <attribute name="maxHttpHeaderSize" required="false">
       <p>The maximum size of the request and response HTTP header, specified
       in bytes.