You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2014/03/17 13:31:51 UTC
svn commit: r1578337 - in /tomcat/trunk:
java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
webapps/docs/changelog.xml
Author: markt
Date: Mon Mar 17 12:31:50 2014
New Revision: 1578337
URL: http://svn.apache.org/r1578337
Log:
Improve processing of chuck size from chunked headers. Avoid overflow and use a bit shift instead of a multiplication as it is marginally faster.
Modified:
tomcat/trunk/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
tomcat/trunk/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
tomcat/trunk/webapps/docs/changelog.xml
Modified: tomcat/trunk/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java?rev=1578337&r1=1578336&r2=1578337&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java Mon Mar 17 12:31:50 2014
@@ -319,7 +319,7 @@ public class ChunkedInputFilter implemen
int result = 0;
boolean eol = false;
- boolean readDigit = false;
+ int readDigit = 0;
boolean extension = false;
while (!eol) {
@@ -341,10 +341,9 @@ public class ChunkedInputFilter implemen
} else if (!extension) {
//don't read data after the trailer
int charValue = HexUtils.getDec(buf[pos]);
- if (charValue != -1) {
- readDigit = true;
- result *= 16;
- result += charValue;
+ if (charValue != -1 && readDigit < 8) {
+ readDigit++;
+ result = (result << 4) | charValue;
} else {
//we shouldn't allow invalid, non hex characters
//in the chunked header
@@ -367,7 +366,7 @@ public class ChunkedInputFilter implemen
}
- if (!readDigit)
+ if (readDigit == 0 || result < 0)
return false;
if (result == 0)
Modified: tomcat/trunk/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java?rev=1578337&r1=1578336&r2=1578337&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java (original)
+++ tomcat/trunk/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java Mon Mar 17 12:31:50 2014
@@ -105,7 +105,7 @@ public class TestChunkedInputFilter exte
Context ctx =
tomcat.addContext("", System.getProperty("java.io.tmpdir"));
- EchoHeaderServlet servlet = new EchoHeaderServlet();
+ EchoHeaderServlet servlet = new EchoHeaderServlet(expectPass);
Tomcat.addServlet(ctx, "servlet", servlet);
ctx.addServletMapping("/", "servlet");
@@ -169,7 +169,7 @@ public class TestChunkedInputFilter exte
Context ctx =
tomcat.addContext("", System.getProperty("java.io.tmpdir"));
- Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet());
+ Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet(false));
ctx.addServletMapping("/", "servlet");
// Limit the size of the trailing header
@@ -233,7 +233,7 @@ public class TestChunkedInputFilter exte
Context ctx =
tomcat.addContext("", System.getProperty("java.io.tmpdir"));
- Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet());
+ Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet(ok));
ctx.addServletMapping("/", "servlet");
tomcat.start();
@@ -282,7 +282,7 @@ public class TestChunkedInputFilter exte
Context ctx =
tomcat.addContext("", System.getProperty("java.io.tmpdir"));
- Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet());
+ Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet(true));
ctx.addServletMapping("/", "servlet");
tomcat.start();
@@ -311,11 +311,136 @@ public class TestChunkedInputFilter exte
assertEquals("nullnull7nullnull", client.getResponseBody());
}
+ @Test
+ public void testChunkSizeZero() throws Exception {
+ doTestChunkSize(true, true, "", 10, 0);
+ }
+
+ @Test
+ public void testChunkSizeAbsent() throws Exception {
+ doTestChunkSize(false, false, SimpleHttpClient.CRLF, 10, 0);
+ }
+
+ @Test
+ public void testChunkSizeTwentyFive() throws Exception {
+ doTestChunkSize(true, true, "19" + SimpleHttpClient.CRLF
+ + "Hello World!Hello World!!" + SimpleHttpClient.CRLF, 40, 25);
+ }
+
+ @Test
+ public void testChunkSizeEightDigit() throws Exception {
+ doTestChunkSize(true, true, "0000000C" + SimpleHttpClient.CRLF
+ + "Hello World!" + SimpleHttpClient.CRLF, 20, 12);
+ }
+
+ @Test
+ public void testChunkSizeNineDigit() throws Exception {
+ doTestChunkSize(false, false, "00000000C" + SimpleHttpClient.CRLF
+ + "Hello World!" + SimpleHttpClient.CRLF, 20, 12);
+ }
+
+ @Test
+ public void testChunkSizeLong() throws Exception {
+ doTestChunkSize(true, false, "7fFFffFF" + SimpleHttpClient.CRLF
+ + "Hello World!" + SimpleHttpClient.CRLF, 10, 10);
+ }
+
+ @Test
+ public void testChunkSizeIntegerMinValue() throws Exception {
+ doTestChunkSize(false, false, "80000000" + SimpleHttpClient.CRLF
+ + "Hello World!" + SimpleHttpClient.CRLF, 10, 10);
+ }
+
+ @Test
+ public void testChunkSizeMinusOne() throws Exception {
+ doTestChunkSize(false, false, "ffffffff" + SimpleHttpClient.CRLF
+ + "Hello World!" + SimpleHttpClient.CRLF, 10, 10);
+ }
+
+ /**
+ * @param expectPass
+ * If the servlet is expected to process the request
+ * @param expectReadWholeBody
+ * If the servlet is expected to fully read the body and reliably
+ * deliver a response
+ * @param chunks
+ * Text of chunks
+ * @param readLimit
+ * Do not read more than this many bytes
+ * @param expectReadCount
+ * Expected count of read bytes
+ * @throws Exception
+ * Unexpected
+ */
+ private void doTestChunkSize(boolean expectPass,
+ boolean expectReadWholeBody, String chunks, int readLimit,
+ int expectReadCount) throws Exception {
+ // Setup Tomcat instance
+ Tomcat tomcat = getTomcatInstance();
+
+ // Must have a real docBase - just use temp
+ Context ctx = tomcat.addContext("",
+ System.getProperty("java.io.tmpdir"));
+
+ BodyReadServlet servlet = new BodyReadServlet(expectPass, readLimit);
+ Tomcat.addServlet(ctx, "servlet", servlet);
+ ctx.addServletMapping("/", "servlet");
+
+ tomcat.start();
+
+ String request = "POST /echo-params.jsp HTTP/1.1"
+ + SimpleHttpClient.CRLF + "Host: any" + SimpleHttpClient.CRLF
+ + "Transfer-encoding: chunked" + SimpleHttpClient.CRLF
+ + "Content-Type: text/plain" + SimpleHttpClient.CRLF;
+ if (expectPass) {
+ request += "Connection: close" + SimpleHttpClient.CRLF;
+ }
+ request += SimpleHttpClient.CRLF + chunks + "0" + SimpleHttpClient.CRLF
+ + SimpleHttpClient.CRLF;
+
+ TrailerClient client = new TrailerClient(tomcat.getConnector()
+ .getLocalPort());
+ client.setRequest(new String[] { request });
+
+ Exception processException = null;
+ client.connect();
+ try {
+ client.processRequest();
+ } catch (Exception e) {
+ // Socket was probably closed before client had a chance to read
+ // response
+ processException = e;
+ }
+ if (expectPass) {
+ if (expectReadWholeBody) {
+ assertNull(processException);
+ }
+ if (processException == null) {
+ assertTrue(client.getResponseLine(), client.isResponse200());
+ assertEquals(String.valueOf(expectReadCount),
+ client.getResponseBody());
+ }
+ assertEquals(expectReadCount, servlet.getCountRead());
+ } else {
+ if (processException == null) {
+ assertTrue(client.getResponseLine(), client.isResponse500());
+ }
+ assertEquals(0, servlet.getCountRead());
+ assertTrue(servlet.getExceptionDuringRead());
+ }
+ }
+
private static class EchoHeaderServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private boolean exceptionDuringRead = false;
+ private final boolean expectPass;
+
+ public EchoHeaderServlet(boolean expectPass) {
+ this.expectPass = expectPass;
+ }
+
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
@@ -334,6 +459,11 @@ public class TestChunkedInputFilter exte
}
} catch (IOException ioe) {
exceptionDuringRead = true;
+ if (!expectPass) { // as expected
+ log(ioe.toString());
+ resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
throw ioe;
}
@@ -358,6 +488,53 @@ public class TestChunkedInputFilter exte
}
}
+ private static class BodyReadServlet extends HttpServlet {
+ private static final long serialVersionUID = 1L;
+
+ private boolean exceptionDuringRead = false;
+ private int countRead = 0;
+ private final boolean expectPass;
+ private final int readLimit;
+
+ public BodyReadServlet(boolean expectPass, int readLimit) {
+ this.expectPass = expectPass;
+ this.readLimit = readLimit;
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+ resp.setContentType("text/plain");
+ PrintWriter pw = resp.getWriter();
+
+ // Read the body - quick and dirty
+ InputStream is = req.getInputStream();
+ try {
+ while (is.read() > -1 && countRead < readLimit) {
+ countRead++;
+ }
+ } catch (IOException ioe) {
+ exceptionDuringRead = true;
+ if (!expectPass) { // as expected
+ log(ioe.toString());
+ resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ throw ioe;
+ }
+
+ pw.write(Integer.valueOf(countRead).toString());
+ }
+
+ public boolean getExceptionDuringRead() {
+ return exceptionDuringRead;
+ }
+
+ public int getCountRead() {
+ return countRead;
+ }
+ }
+
private static class TrailerClient extends SimpleHttpClient {
public TrailerClient(int port) {
Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1578337&r1=1578336&r2=1578337&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Mon Mar 17 12:31:50 2014
@@ -149,6 +149,11 @@
Add experimental NIO2 connector. Based on code developed by
Nabil Benothman. (remm)
</add>
+ <fix>
+ Improve processing of chuck size from chunked headers. Avoid overflow
+ and use a bit shift instead of a multiplication as it is marginally
+ faster. (markt/kkolinko)
+ </fix>
</changelog>
</subsection>
<subsection name="Jasper">
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org