Author: markt Date: Mon Dec 15 11:54:24 2014 New Revision: 1645627 URL: http://svn.apache.org/r1645627 Log: Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=57324 If it is known that the connection is going to be closed when committing the response, send the connection: close header.
Modified: tomcat/tc8.0.x/trunk/ (props changed) tomcat/tc8.0.x/trunk/java/org/apache/coyote/http11/AbstractHttp11Processor.java tomcat/tc8.0.x/trunk/test/org/apache/catalina/startup/TomcatBaseTest.java tomcat/tc8.0.x/trunk/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml Propchange: tomcat/tc8.0.x/trunk/ ------------------------------------------------------------------------------ --- svn:mergeinfo (original) +++ svn:mergeinfo Mon Dec 15 11:54:24 2014 @@ -1 +1 @@ -/tomcat/trunk:1636524,1637156,1637176,1637188,1637331,1637684,1637695,1638720-1638725,1639653,1640010,1640083-1640084,1640088,1640275,1640322,1640347,1640361,1640365,1640403,1640410,1640652,1640655-1640658,1640688,1640700-1640883,1640903,1640976,1640978,1641000,1641026,1641038-1641039,1641051-1641052,1641058,1641064,1641300,1641369,1641374,1641380,1641486,1641634,1641656-1641692,1641704,1641707-1641718,1641720-1641722,1641735,1641981,1642233,1642280,1642554,1642564,1642595,1642606,1642668,1642679,1642697,1642699,1642766,1643002,1643045,1643054-1643055,1643066,1643121,1643128,1643206,1643209-1643210,1643216,1643249,1643270,1643283,1643309-1643310,1643323,1643365-1643366,1643370-1643371,1643465,1643474,1643536,1643570,1643634,1643649,1643651,1643654,1643675,1643731,1643733-1643734,1643761,1643766,1643814,1643937,1643963,1644017,1644169,1644201-1644203,1644321,1644323,1644516,1644523,1644529,1644535,1644730,1644768,1644784-1644785,1644790,1644793,1644815,1644884,1644886,1644890,1644892 ,1644910,1644924,1644929-1644930,1644935,1644989,1645011,1645247,1645355,1645357,1645455,1645465,1645469,1645471,1645473,1645475,1645487 +/tomcat/trunk:1636524,1637156,1637176,1637188,1637331,1637684,1637695,1638720-1638725,1639653,1640010,1640083-1640084,1640088,1640275,1640322,1640347,1640361,1640365,1640403,1640410,1640652,1640655-1640658,1640688,1640700-1640883,1640903,1640976,1640978,1641000,1641026,1641038-1641039,1641051-1641052,1641058,1641064,1641300,1641369,1641374,1641380,1641486,1641634,1641656-1641692,1641704,1641707-1641718,1641720-1641722,1641735,1641981,1642233,1642280,1642554,1642564,1642595,1642606,1642668,1642679,1642697,1642699,1642766,1643002,1643045,1643054-1643055,1643066,1643121,1643128,1643206,1643209-1643210,1643216,1643249,1643270,1643283,1643309-1643310,1643323,1643365-1643366,1643370-1643371,1643465,1643474,1643536,1643570,1643634,1643649,1643651,1643654,1643675,1643731,1643733-1643734,1643761,1643766,1643814,1643937,1643963,1644017,1644169,1644201-1644203,1644321,1644323,1644516,1644523,1644529,1644535,1644730,1644768,1644784-1644785,1644790,1644793,1644815,1644884,1644886,1644890,1644892 ,1644910,1644924,1644929-1644930,1644935,1644989,1645011,1645247,1645355,1645357,1645455,1645465,1645469,1645471,1645473,1645475,1645487,1645626 Modified: tomcat/tc8.0.x/trunk/java/org/apache/coyote/http11/AbstractHttp11Processor.java URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/coyote/http11/AbstractHttp11Processor.java?rev=1645627&r1=1645626&r2=1645627&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/java/org/apache/coyote/http11/AbstractHttp11Processor.java (original) +++ tomcat/tc8.0.x/trunk/java/org/apache/coyote/http11/AbstractHttp11Processor.java Mon Dec 15 11:54:24 2014 @@ -1129,16 +1129,11 @@ public abstract class AbstractHttp11Proc // input. This way uploading a 100GB file doesn't tie up the // thread if the servlet has rejected it. getInputBuffer().setSwallowInput(false); - } else if (expectation && - (response.getStatus() < 200 || response.getStatus() > 299)) { - // Client sent Expect: 100-continue but received a - // non-2xx final response. Disable keep-alive (if enabled) - // to ensure that the connection is closed. Some clients may - // still send the body, some may send the next request. - // No way to differentiate, so close the connection to - // force the client to send the next request. - getInputBuffer().setSwallowInput(false); - keepAlive = false; + } else { + // Need to check this again here in case the response was + // committed before the error that requires the connection + // to be closed occurred. + checkExpectationAndResponseStatus(); } endRequest(); } @@ -1200,6 +1195,20 @@ public abstract class AbstractHttp11Proc } + private void checkExpectationAndResponseStatus() { + if (expectation && (response.getStatus() < 200 || response.getStatus() > 299)) { + // Client sent Expect: 100-continue but received a + // non-2xx final response. Disable keep-alive (if enabled) + // to ensure that the connection is closed. Some clients may + // still send the body, some may send the next request. + // No way to differentiate, so close the connection to + // force the client to send the next request. + getInputBuffer().setSwallowInput(false); + keepAlive = false; + } + } + + /** * After reading the request headers, we have to setup the request filters. */ @@ -1524,6 +1533,10 @@ public abstract class AbstractHttp11Proc keepAlive = false; } + // This may disabled keep-alive to check before working out the + // Connection header. + checkExpectationAndResponseStatus(); + // If we know that the request is bad this early, add the // Connection: close header. keepAlive = keepAlive && !statusDropsConnection(statusCode); Modified: tomcat/tc8.0.x/trunk/test/org/apache/catalina/startup/TomcatBaseTest.java URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/test/org/apache/catalina/startup/TomcatBaseTest.java?rev=1645627&r1=1645626&r2=1645627&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/test/org/apache/catalina/startup/TomcatBaseTest.java (original) +++ tomcat/tc8.0.x/trunk/test/org/apache/catalina/startup/TomcatBaseTest.java Mon Dec 15 11:54:24 2014 @@ -250,6 +250,37 @@ public abstract class TomcatBaseTest ext } + /** + * Servlet that simply echos the request body back as the response body. + */ + public static class EchoBodyServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + // NO-OP - No body to echo + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + // Beware of clients that try to send the whole request body before + // reading any of the response. They may cause this test to lock up. + byte[] buffer = new byte[8096]; + int read = 0; + try (InputStream is = req.getInputStream(); + OutputStream os = resp.getOutputStream()) { + while (read > -1) { + os.write(buffer, 0, read); + read = is.read(buffer); + } + } + } + } + + /* * Wrapper for getting the response. */ @@ -407,6 +438,10 @@ public abstract class TomcatBaseTest ext os.write(next); os.flush(); } + } catch (IOException ioe) { + // Failed to write the request body. Server may have closed the + // connection. + ioe.printStackTrace(); } int rc = connection.getResponseCode(); Modified: tomcat/tc8.0.x/trunk/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java?rev=1645627&r1=1645626&r2=1645627&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java (original) +++ tomcat/tc8.0.x/trunk/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java Mon Dec 15 11:54:24 2014 @@ -23,8 +23,10 @@ import java.io.Writer; import java.net.Socket; import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -49,6 +51,8 @@ import org.apache.catalina.startup.Tomca import org.apache.catalina.startup.TomcatBaseTest; import org.apache.tomcat.util.buf.B2CConverter; import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.descriptor.web.SecurityCollection; +import org.apache.tomcat.util.descriptor.web.SecurityConstraint; public class TestAbstractHttp11Processor extends TomcatBaseTest { @@ -536,6 +540,59 @@ public class TestAbstractHttp11Processor } } + + // https://issues.apache.org/bugzilla/show_bug.cgi?id=57324 + @Test + public void testNon2xxResponseWithExpectation() throws Exception { + doTestNon2xxResponseAndExpectation(true); + } + + @Test + public void testNon2xxResponseWithoutExpectation() throws Exception { + doTestNon2xxResponseAndExpectation(false); + } + + private void doTestNon2xxResponseAndExpectation(boolean useExpectation) throws Exception { + Tomcat tomcat = getTomcatInstance(); + + // No file system docBase required + Context ctx = tomcat.addContext("", null); + + Tomcat.addServlet(ctx, "echo", new EchoBodyServlet()); + ctx.addServletMapping("/echo", "echo"); + + SecurityCollection collection = new SecurityCollection("All", ""); + collection.addPattern("/*"); + SecurityConstraint constraint = new SecurityConstraint(); + constraint.addAuthRole("Any"); + constraint.addCollection(collection); + ctx.addConstraint(constraint); + + tomcat.start(); + + byte[] requestBody = "HelloWorld".getBytes(StandardCharsets.UTF_8); + Map<String,List<String>> reqHeaders = null; + if (useExpectation) { + reqHeaders = new HashMap<>(); + List<String> expectation = new ArrayList<>(); + expectation.add("100-continue"); + reqHeaders.put("Expect", expectation); + } + ByteChunk responseBody = new ByteChunk(); + Map<String,List<String>> responseHeaders = new HashMap<>(); + int rc = postUrl(requestBody, "http://localhost:" + getPort() + "/echo", + responseBody, reqHeaders, responseHeaders); + + Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, rc); + List<String> connectionHeaders = responseHeaders.get("Connection"); + if (useExpectation) { + Assert.assertEquals(1, connectionHeaders.size()); + Assert.assertEquals("close", connectionHeaders.get(0).toLowerCase(Locale.ENGLISH)); + } else { + Assert.assertNull(connectionHeaders); + } + } + private static class Bug55772Servlet extends HttpServlet { Modified: tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml?rev=1645627&r1=1645626&r2=1645627&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml (original) +++ tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml Mon Dec 15 11:54:24 2014 @@ -195,6 +195,13 @@ and the NIO connector. (markt) </fix> <fix> + <bug>57324</bug>: If the client uses <code>Expect: 100-continue</code> + and Tomcat responds with a non-2xx response code, Tomcat also closes the + connection. If Tomcat knows the connection is going to be closed when + committing the response, Tomcat will now also send the + <code>Connection: close</code> response header. (markt) + </fix> + <fix> <bug>57347</bug>: AJP response contains wrong status reason phrase (rjung) </fix> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org