Skip to content

Commit

Permalink
Ensure IOException on request read always triggers error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
markt-asf committed Nov 8, 2023
1 parent 2f65f1f commit aa92971
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 0 deletions.
14 changes: 14 additions & 0 deletions java/org/apache/catalina/connector/InputBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.concurrent.ConcurrentMap;

import javax.servlet.ReadListener;
import javax.servlet.RequestDispatcher;

import org.apache.catalina.security.SecurityUtil;
import org.apache.coyote.ActionCode;
Expand Down Expand Up @@ -303,6 +304,7 @@ boolean isBlocking() {
*
* @throws IOException An underlying IOException occurred
*/
@SuppressWarnings("deprecation")
@Override
public int realReadBytes() throws IOException {
if (closed) {
Expand All @@ -316,11 +318,23 @@ public int realReadBytes() throws IOException {
try {
return coyoteRequest.doRead(this);
} catch (BadRequestException bre) {
// Set flag used by asynchronous processing to detect errors on non-container threads
coyoteRequest.setErrorException(bre);
// In synchronous processing, this exception may be swallowed by the application so set error flags here.
coyoteRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION, bre);
coyoteRequest.getResponse().setStatus(400);
coyoteRequest.getResponse().setError();
// Make the exception visible to the application
throw bre;
} catch (IOException ioe) {
// Set flag used by asynchronous processing to detect errors on non-container threads
coyoteRequest.setErrorException(ioe);
// In synchronous processing, this exception may be swallowed by the application so set error flags here.
coyoteRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
coyoteRequest.getResponse().setStatus(400);
coyoteRequest.getResponse().setError();
// Any other IOException on a read is almost always due to the remote client aborting the request.
// Make the exception visible to the application
throw new ClientAbortException(ioe);
}
}
Expand Down
77 changes: 77 additions & 0 deletions test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,83 @@ private void doTestChunkSize(boolean expectPass,
}
}


@Test
public void testTrailerHeaderNameNotTokenThrowException() throws Exception {
doTestTrailerHeaderNameNotToken(false);
}

@Test
public void testTrailerHeaderNameNotTokenSwallowException() throws Exception {
doTestTrailerHeaderNameNotToken(true);
}

private void doTestTrailerHeaderNameNotToken(boolean swallowException) throws Exception {

// Setup Tomcat instance
Tomcat tomcat = getTomcatInstance();

// No file system docBase required
Context ctx = tomcat.addContext("", null);

Tomcat.addServlet(ctx, "servlet", new SwallowBodyServlet(swallowException));
ctx.addServletMappingDecoded("/", "servlet");

tomcat.start();

String[] request = new String[]{
"POST / HTTP/1.1" + SimpleHttpClient.CRLF +
"Host: localhost" + 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: Test" + SimpleHttpClient.CRLF +
SimpleHttpClient.CRLF };

TrailerClient client = new TrailerClient(tomcat.getConnector().getLocalPort());
client.setRequest(request);

client.connect();
client.processRequest();
// Expected to fail because of invalid trailer header name
Assert.assertTrue(client.getResponseLine(), client.isResponse400());
}

private static class SwallowBodyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

private final boolean swallowException;

SwallowBodyServlet(boolean swallowException) {
this.swallowException = swallowException;
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/plain");
PrintWriter pw = resp.getWriter();

// Read the body
InputStream is = req.getInputStream();
try {
while (is.read() > -1) {
}
pw.write("OK");
} catch (IOException ioe) {
if (!swallowException) {
throw ioe;
}
}
}
}

private static class EchoHeaderServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

Expand Down
5 changes: 5 additions & 0 deletions webapps/docs/changelog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@
Use a 400 status code to report an error due to a bad request (e.g. an
invalid trailer header) rather than a 500 status code. (markt)
</fix>
<fix>
Ensure that an <code>IOException</code> during the reading of the
request triggers always error handling, regardless of whether the
application swallows the exception. (markt)
</fix>
</changelog>
</subsection>
<subsection name="Coyote">
Expand Down

0 comments on commit aa92971

Please sign in to comment.