--- 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-04-16 12:21:12.524505000 -0400
@@ -1277,10 +1277,30 @@
// Parse content-length header
long contentLength = request.getContentLengthLong();
- if (contentLength >= 0 && !contentDelimitation) {
- getInputBuffer().addActiveFilter
- (inputFilters[Constants.IDENTITY_FILTER]);
- contentDelimitation = true;
+ if (contentLength >= 0) {
+ if (getLog().isDebugEnabled()) {
+ getLog().debug("ContentLength="+contentLength);
+ getLog().debug(request.toString());
+ }
+ 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);
+ if (getLog().isDebugEnabled()) {
+ getLog().debug("ContentLength=-1");
+ }
+ } else {
+ getInputBuffer().addActiveFilter
+ (inputFilters[Constants.IDENTITY_FILTER]);
+ contentDelimitation = true;
+ if (getLog().isDebugEnabled()) {
+ getLog().debug("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 {