Skip to content

Commit aa92971

Browse files
committed
Ensure IOException on request read always triggers error handling
1 parent 2f65f1f commit aa92971

3 files changed

Lines changed: 96 additions & 0 deletions

File tree

java/org/apache/catalina/connector/InputBuffer.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.concurrent.ConcurrentMap;
3030

3131
import javax.servlet.ReadListener;
32+
import javax.servlet.RequestDispatcher;
3233

3334
import org.apache.catalina.security.SecurityUtil;
3435
import org.apache.coyote.ActionCode;
@@ -303,6 +304,7 @@ boolean isBlocking() {
303304
*
304305
* @throws IOException An underlying IOException occurred
305306
*/
307+
@SuppressWarnings("deprecation")
306308
@Override
307309
public int realReadBytes() throws IOException {
308310
if (closed) {
@@ -316,11 +318,23 @@ public int realReadBytes() throws IOException {
316318
try {
317319
return coyoteRequest.doRead(this);
318320
} catch (BadRequestException bre) {
321+
// Set flag used by asynchronous processing to detect errors on non-container threads
319322
coyoteRequest.setErrorException(bre);
323+
// In synchronous processing, this exception may be swallowed by the application so set error flags here.
324+
coyoteRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION, bre);
325+
coyoteRequest.getResponse().setStatus(400);
326+
coyoteRequest.getResponse().setError();
327+
// Make the exception visible to the application
320328
throw bre;
321329
} catch (IOException ioe) {
330+
// Set flag used by asynchronous processing to detect errors on non-container threads
322331
coyoteRequest.setErrorException(ioe);
332+
// In synchronous processing, this exception may be swallowed by the application so set error flags here.
333+
coyoteRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
334+
coyoteRequest.getResponse().setStatus(400);
335+
coyoteRequest.getResponse().setError();
323336
// Any other IOException on a read is almost always due to the remote client aborting the request.
337+
// Make the exception visible to the application
324338
throw new ClientAbortException(ioe);
325339
}
326340
}

test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,83 @@ private void doTestChunkSize(boolean expectPass,
428428
}
429429
}
430430

431+
432+
@Test
433+
public void testTrailerHeaderNameNotTokenThrowException() throws Exception {
434+
doTestTrailerHeaderNameNotToken(false);
435+
}
436+
437+
@Test
438+
public void testTrailerHeaderNameNotTokenSwallowException() throws Exception {
439+
doTestTrailerHeaderNameNotToken(true);
440+
}
441+
442+
private void doTestTrailerHeaderNameNotToken(boolean swallowException) throws Exception {
443+
444+
// Setup Tomcat instance
445+
Tomcat tomcat = getTomcatInstance();
446+
447+
// No file system docBase required
448+
Context ctx = tomcat.addContext("", null);
449+
450+
Tomcat.addServlet(ctx, "servlet", new SwallowBodyServlet(swallowException));
451+
ctx.addServletMappingDecoded("/", "servlet");
452+
453+
tomcat.start();
454+
455+
String[] request = new String[]{
456+
"POST / HTTP/1.1" + SimpleHttpClient.CRLF +
457+
"Host: localhost" + SimpleHttpClient.CRLF +
458+
"Transfer-encoding: chunked" + SimpleHttpClient.CRLF +
459+
"Content-Type: application/x-www-form-urlencoded" + SimpleHttpClient.CRLF +
460+
"Connection: close" + SimpleHttpClient.CRLF +
461+
SimpleHttpClient.CRLF +
462+
"3" + SimpleHttpClient.CRLF +
463+
"a=0" + SimpleHttpClient.CRLF +
464+
"4" + SimpleHttpClient.CRLF +
465+
"&b=1" + SimpleHttpClient.CRLF +
466+
"0" + SimpleHttpClient.CRLF +
467+
"x@trailer: Test" + SimpleHttpClient.CRLF +
468+
SimpleHttpClient.CRLF };
469+
470+
TrailerClient client = new TrailerClient(tomcat.getConnector().getLocalPort());
471+
client.setRequest(request);
472+
473+
client.connect();
474+
client.processRequest();
475+
// Expected to fail because of invalid trailer header name
476+
Assert.assertTrue(client.getResponseLine(), client.isResponse400());
477+
}
478+
479+
private static class SwallowBodyServlet extends HttpServlet {
480+
private static final long serialVersionUID = 1L;
481+
482+
private final boolean swallowException;
483+
484+
SwallowBodyServlet(boolean swallowException) {
485+
this.swallowException = swallowException;
486+
}
487+
488+
@Override
489+
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
490+
throws ServletException, IOException {
491+
resp.setContentType("text/plain");
492+
PrintWriter pw = resp.getWriter();
493+
494+
// Read the body
495+
InputStream is = req.getInputStream();
496+
try {
497+
while (is.read() > -1) {
498+
}
499+
pw.write("OK");
500+
} catch (IOException ioe) {
501+
if (!swallowException) {
502+
throw ioe;
503+
}
504+
}
505+
}
506+
}
507+
431508
private static class EchoHeaderServlet extends HttpServlet {
432509
private static final long serialVersionUID = 1L;
433510

webapps/docs/changelog.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@
149149
Use a 400 status code to report an error due to a bad request (e.g. an
150150
invalid trailer header) rather than a 500 status code. (markt)
151151
</fix>
152+
<fix>
153+
Ensure that an <code>IOException</code> during the reading of the
154+
request triggers always error handling, regardless of whether the
155+
application swallows the exception. (markt)
156+
</fix>
152157
</changelog>
153158
</subsection>
154159
<subsection name="Coyote">

0 commit comments

Comments
 (0)