--- java/org/apache/coyote/ajp/AbstractAjpProcessor.java.orig 2014-03-14 17:13:46.228345000 -0400 +++ java/org/apache/coyote/ajp/AbstractAjpProcessor.java 2014-03-18 13:54:13.570758000 -0400 @@ -25,6 +25,8 @@ import java.security.cert.X509Certificate; import java.util.concurrent.atomic.AtomicBoolean; +import javax.servlet.http.HttpServletResponse; + import org.apache.coyote.AbstractProcessor; import org.apache.coyote.ActionCode; import org.apache.coyote.AsyncContextCallback; @@ -651,7 +653,7 @@ // Set this every time in case limit has been changed via JMX headers.setLimit(endpoint.getMaxHeaderCount()); - + boolean contentLengthSet = false; int hCount = requestHeaderMessage.getInt(); for(int i = 0 ; i < hCount ; i++) { String hName = null; @@ -686,10 +688,15 @@ if (hId == Constants.SC_REQ_CONTENT_LENGTH || (hId == -1 && tmpMB.equalsIgnoreCase("Content-Length"))) { - // just read the content-length header, so set it long cl = vMB.getLong(); - if(cl < Integer.MAX_VALUE) - request.setContentLength( (int)cl ); + if (contentLengthSet) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + error = true; + } else { + contentLengthSet = true; + // Set the content-length header for the request + request.setContentLength((int)cl); + } } else if (hId == Constants.SC_REQ_CONTENT_TYPE || (hId == -1 && tmpMB.equalsIgnoreCase("Content-Type"))) { // just read the content-type header, so set it --- java/org/apache/coyote/http11/AbstractHttp11Processor.java.orig 2014-03-14 17:13:46.514347000 -0400 +++ java/org/apache/coyote/http11/AbstractHttp11Processor.java 2014-03-14 17:13:46.353345000 -0400 @@ -1277,10 +1277,20 @@ // Parse content-length header long contentLength = request.getContentLengthLong(); - if (contentLength >= 0 && !contentDelimitation) { - getInputBuffer().addActiveFilter - (inputFilters[Constants.IDENTITY_FILTER]); - contentDelimitation = true; + if (contentLength >= 0) { + if (contentDelimitation) { + // contentDelimitation being true at this point indicates that + // chunked encoding is being used but chunked encoding should + // not be used with a content length. RFC 2616, section 4.4, + // bullet 3 states Content-Length must be ignored in this case - + // so remove it. + headers.removeHeader("content-length"); + request.setContentLength(-1); + } else { + getInputBuffer().addActiveFilter + (inputFilters[Constants.IDENTITY_FILTER]); + contentDelimitation = true; + } } MessageBytes valueMB = headers.getValue("host"); --- test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java.orig 2014-03-14 17:13:52.878367000 -0400 +++ test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java 2014-03-14 17:21:43.278956000 -0400 @@ -90,6 +90,61 @@ ajpClient.disconnect(); } + @Test + public void testPost() throws Exception { + doTestPost(false, HttpServletResponse.SC_OK); + } + + public void testPostMultipleContentLength() throws Exception { + // Multiple content lengths + doTestPost(true, HttpServletResponse.SC_BAD_REQUEST); + } + + + public void doTestPost(boolean multipleCL, int expectedStatus) + throws Exception { + Tomcat tomcat = getTomcatInstance(); + // Use the normal Tomcat ROOT context + File root = new File("test/webapp-3.0"); + tomcat.addWebapp("", root.getAbsolutePath()); + tomcat.start(); + SimpleAjpClient ajpClient = new SimpleAjpClient(); + ajpClient.setPort(getPort()); + ajpClient.connect(); + + validateCpong(ajpClient.cping()); + + TesterAjpMessage forwardMessage = + ajpClient.createForwardMessage("/echo-params.jsp", 4); + forwardMessage.addHeader(0xA008, "9"); + if (multipleCL) { + forwardMessage.addHeader(0xA008, "99"); + } + forwardMessage.addHeader(0xA007, "application/x-www-form-urlencoded"); + forwardMessage.end(); + TesterAjpMessage bodyMessage = + ajpClient.createBodyMessage("test=data".getBytes()); + + TesterAjpMessage responseHeaders = + ajpClient.sendMessage(forwardMessage, bodyMessage); + + validateResponseHeaders(responseHeaders, expectedStatus); + if (expectedStatus == HttpServletResponse.SC_OK) { + // Expect 3 messages: headers, body, end for a valid request + TesterAjpMessage responseBody = ajpClient.readMessage(); + validateResponseBody(responseBody, "test - data"); + validateResponseEnd(ajpClient.readMessage(), true); + // Double check the connection is still open + validateCpong(ajpClient.cping()); + } else { + // Expect 2 messages: headers, end for an invalid request + validateResponseEnd(ajpClient.readMessage(), false); + } + + ajpClient.disconnect(); + } + + /** * Process response header packet and checks the status. Any other data is * ignored. --- test/org/apache/coyote/http11/TestAbstractHttp11Processor.java.orig 2014-03-14 17:13:52.946367000 -0400 +++ test/org/apache/coyote/http11/TestAbstractHttp11Processor.java 2014-03-14 17:13:52.925368000 -0400 @@ -87,7 +87,7 @@ "Transfer-encoding: buffered" + SimpleHttpClient.CRLF + "Content-Length: 9" + SimpleHttpClient.CRLF + "Content-Type: application/x-www-form-urlencoded" + - SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF + SimpleHttpClient.CRLF + "test=data"; @@ -99,6 +99,54 @@ assertTrue(client.isResponse501()); } + @Test + public void testWithTEChunked() throws Exception { + doTestWithTEChunked(false); + } + + @Test + public void testWithTEChunkedWithCL() throws Exception { + // Should be ignored + doTestWithTEChunked(true); + } + + private void doTestWithTEChunked(boolean withCL) + throws Exception { + + Tomcat tomcat = getTomcatInstance(); + + // Use the normal Tomcat ROOT context + File root = new File("test/webapp-3.0"); + tomcat.addWebapp("", root.getAbsolutePath()); + + tomcat.start(); + + String request = + "POST /echo-params.jsp HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: any" + SimpleHttpClient.CRLF + + (withCL ? "Content-length: 1" + SimpleHttpClient.CRLF : "") + + "Transfer-encoding: chunked" + SimpleHttpClient.CRLF + + "Content-Type: application/x-www-form-urlencoded" + + SimpleHttpClient.CRLF + + "Connection: close" + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF + + "9" + SimpleHttpClient.CRLF + + "test=data" + SimpleHttpClient.CRLF + + "0" + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF; + + Client client = new Client(tomcat.getConnector().getLocalPort()); + client.setRequest(new String[] {request}); + + client.connect(); + client.processRequest(); + assertTrue(client.isResponse200()); + assertTrue(client.getResponseBody().contains("test - data")); + } + + + + @Test public void testWithTEIdentity() throws Exception {