Author: markt Date: Fri Oct 1 10:21:58 2010 New Revision: 1003461 URL: http://svn.apache.org/viewvc?rev=1003461&view=rev Log: Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=49860 Add support for trailing headers
Added: tomcat/trunk/test/org/apache/coyote/http11/filters/ tomcat/trunk/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java (with props) Modified: tomcat/trunk/java/org/apache/coyote/http11/filters/ChunkedInputFilter.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=1003461&r1=1003460&r2=1003461&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java (original) +++ tomcat/trunk/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java Fri Oct 1 10:21:58 2010 @@ -17,6 +17,7 @@ package org.apache.coyote.http11.filters; +import java.io.EOFException; import java.io.IOException; import org.apache.coyote.InputBuffer; @@ -25,6 +26,8 @@ import org.apache.coyote.http11.Constant import org.apache.coyote.http11.InputFilter; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.HexUtils; +import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.http.MimeHeaders; /** * Chunked input filter. Parses chunked data according to @@ -95,12 +98,19 @@ public class ChunkedInputFilter implemen */ protected boolean endChunk = false; + /** * Flag set to true if the next call to doRead() must parse a CRLF pair * before doing anything else. */ protected boolean needCRLFParse = false; + + /** + * Request being parsed. + */ + private Request request; + // ------------------------------------------------------------- Properties @@ -177,7 +187,7 @@ public class ChunkedInputFilter implemen */ @Override public void setRequest(Request request) { - // NOOP: Request isn't used so ignore it + this.request = request; } @@ -357,14 +367,163 @@ public class ChunkedInputFilter implemen /** * Parse end chunk data. - * FIXME: Handle trailers */ - protected boolean parseEndChunk() - throws IOException { - - return parseCRLF(); // FIXME + protected void parseEndChunk() throws IOException { + // Handle option trailer headers + while (parseHeader()) { + // Loop until we run out of headers + } } + + @SuppressWarnings("null") // headerValue cannot be null + private boolean parseHeader() throws IOException { + + MimeHeaders headers = request.getMimeHeaders(); + byte chr = 0; + while (true) { + // Read new bytes if needed + if (pos >= lastValid) { + if (readBytes() <0) + throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request"); + } + + chr = buf[pos]; + + if ((chr == Constants.CR) || (chr == Constants.LF)) { + if (chr == Constants.LF) { + pos++; + return false; + } + } else { + break; + } + + pos++; + + } + + // Mark the current buffer position + int start = pos; + + // + // Reading the header name + // Header name is always US-ASCII + // + + boolean colon = false; + MessageBytes headerValue = null; + + while (!colon) { + + // Read new bytes if needed + if (pos >= lastValid) { + if (readBytes() <0) + throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request"); + } + + if (buf[pos] == Constants.COLON) { + colon = true; + headerValue = headers.addValue(buf, start, pos - start); + } + chr = buf[pos]; + if ((chr >= Constants.A) && (chr <= Constants.Z)) { + buf[pos] = (byte) (chr - Constants.LC_OFFSET); + } + + pos++; + + } + + // Mark the current buffer position + start = pos; + int realPos = pos; + + // + // Reading the header value (which can be spanned over multiple lines) + // + + boolean eol = false; + boolean validLine = true; + + while (validLine) { + + boolean space = true; + + // Skipping spaces + while (space) { + + // Read new bytes if needed + if (pos >= lastValid) { + if (readBytes() <0) + throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request"); + } + + if ((buf[pos] == Constants.SP) || (buf[pos] == Constants.HT)) { + pos++; + } else { + space = false; + } + + } + + int lastSignificantChar = realPos; + + // Reading bytes until the end of the line + while (!eol) { + + // Read new bytes if needed + if (pos >= lastValid) { + if (readBytes() <0) + throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request"); + } + + if (buf[pos] == Constants.CR) { + // Skip + } else if (buf[pos] == Constants.LF) { + eol = true; + } else if (buf[pos] == Constants.SP) { + buf[realPos] = buf[pos]; + realPos++; + } else { + buf[realPos] = buf[pos]; + realPos++; + lastSignificantChar = realPos; + } + + pos++; + + } + + realPos = lastSignificantChar; + + // Checking the first character of the new line. If the character + // is a LWS, then it's a multiline header + + // Read new bytes if needed + if (pos >= lastValid) { + if (readBytes() <0) + throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request"); + } + + chr = buf[pos]; + if ((chr != Constants.SP) && (chr != Constants.HT)) { + validLine = false; + } else { + eol = false; + // Copying one extra space in the buffer (since there must + // be at least one space inserted between the lines) + buf[realPos] = chr; + realPos++; + } + + } + + // Set the header value + headerValue.setBytes(buf, start, realPos - start); + + return true; + } } Added: 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=1003461&view=auto ============================================================================== --- tomcat/trunk/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java (added) +++ tomcat/trunk/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java Fri Oct 1 10:21:58 2010 @@ -0,0 +1,134 @@ +package org.apache.coyote.http11.filters; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.catalina.Context; +import org.apache.catalina.startup.SimpleHttpClient; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; + +public class TestChunkedInputFilter extends TomcatBaseTest { + + public void testTrailingHeaders() 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")); + + Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet()); + 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: application/x-www-form-urlencoded" + + SimpleHttpClient.CRLF + + "Connection: close" + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF + + "3" + SimpleHttpClient.CRLF + + "a=0" + SimpleHttpClient.CRLF + + "4" + SimpleHttpClient.CRLF + + "&b=1" + SimpleHttpClient.CRLF + + "0" + SimpleHttpClient.CRLF + + "x-trailer: TestTestTest" + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF; + + TrailerClient client = new TrailerClient(); + client.setPort(getPort()); + client.setRequest(new String[] {request}); + + client.connect(); + client.processRequest(); + assertEquals("null7TestTestTest", client.getResponseBody()); + } + + public void testNoTrailingHeaders() 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")); + + Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet()); + 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: application/x-www-form-urlencoded" + + SimpleHttpClient.CRLF + + "Connection: close" + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF + + "3" + SimpleHttpClient.CRLF + + "a=0" + SimpleHttpClient.CRLF + + "4" + SimpleHttpClient.CRLF + + "&b=1" + SimpleHttpClient.CRLF + + "0" + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF; + + TrailerClient client = new TrailerClient(); + client.setPort(getPort()); + client.setRequest(new String[] {request}); + + client.connect(); + client.processRequest(); + assertEquals("null7null", client.getResponseBody()); + } + + private static class EchoHeaderServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/plain"); + PrintWriter pw = resp.getWriter(); + // Header not visible yet, body not processed + String value = req.getHeader("x-trailer"); + if (value == null) { + value = "null"; + } + pw.write(value); + + // Read the body - quick and dirty + InputStream is = req.getInputStream(); + int count = 0; + while (is.read() > -1) { + count++; + } + + pw.write(Integer.valueOf(count).toString()); + + // Header should be visible now + value = req.getHeader("x-trailer"); + if (value == null) { + value = "null"; + } + pw.write(value); + } + } + + private static class TrailerClient extends SimpleHttpClient { + + @Override + public boolean isResponseBodyOK() { + return getResponseBody().contains("TestTestTest"); + } + } +} Propchange: tomcat/trunk/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: tomcat/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1003461&r1=1003460&r2=1003461&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/changelog.xml (original) +++ tomcat/trunk/webapps/docs/changelog.xml Fri Oct 1 10:21:58 2010 @@ -230,6 +230,10 @@ Various refactorings to reduce code duplication and unnecessary code in the connectors. (markt) </update> + <fix> + <bug>49860</bug>: Add support for trailing headers in chunked HTTP + requests. (markt) + </fix> </changelog> </subsection> <subsection name="Jasper"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org