Skip to content

Commit f646a5a

Browse files
committed
Improve processing of chuck size from chunked headers. Avoid overflow and use a bit shift instead of a multiplication as it is marginally faster.
git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@1578337 13f79535-47bb-0310-9956-ffa450edef68
1 parent e8cc1e6 commit f646a5a

3 files changed

Lines changed: 191 additions & 10 deletions

File tree

‎java/org/apache/coyote/http11/filters/ChunkedInputFilter.java‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ protected boolean parseChunkHeader()
319319

320320
int result = 0;
321321
boolean eol = false;
322-
boolean readDigit = false;
322+
int readDigit = 0;
323323
boolean extension = false;
324324

325325
while (!eol) {
@@ -341,10 +341,9 @@ protected boolean parseChunkHeader()
341341
} else if (!extension) {
342342
//don't read data after the trailer
343343
int charValue = HexUtils.getDec(buf[pos]);
344-
if (charValue != -1) {
345-
readDigit = true;
346-
result *= 16;
347-
result += charValue;
344+
if (charValue != -1 && readDigit < 8) {
345+
readDigit++;
346+
result = (result << 4) | charValue;
348347
} else {
349348
//we shouldn't allow invalid, non hex characters
350349
//in the chunked header
@@ -367,7 +366,7 @@ protected boolean parseChunkHeader()
367366

368367
}
369368

370-
if (!readDigit)
369+
if (readDigit == 0 || result < 0)
371370
return false;
372371

373372
if (result == 0)

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

Lines changed: 181 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ private void doTestChunkingCRLF(boolean chunkHeaderUsesCRLF,
105105
Context ctx =
106106
tomcat.addContext("", System.getProperty("java.io.tmpdir"));
107107

108-
EchoHeaderServlet servlet = new EchoHeaderServlet();
108+
EchoHeaderServlet servlet = new EchoHeaderServlet(expectPass);
109109
Tomcat.addServlet(ctx, "servlet", servlet);
110110
ctx.addServletMapping("/", "servlet");
111111

@@ -169,7 +169,7 @@ public void testTrailingHeadersSizeLimit() throws Exception {
169169
Context ctx =
170170
tomcat.addContext("", System.getProperty("java.io.tmpdir"));
171171

172-
Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet());
172+
Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet(false));
173173
ctx.addServletMapping("/", "servlet");
174174

175175
// Limit the size of the trailing header
@@ -233,7 +233,7 @@ private void doTestExtensionSizeLimit(int len, boolean ok) throws Exception {
233233
Context ctx =
234234
tomcat.addContext("", System.getProperty("java.io.tmpdir"));
235235

236-
Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet());
236+
Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet(ok));
237237
ctx.addServletMapping("/", "servlet");
238238

239239
tomcat.start();
@@ -282,7 +282,7 @@ public void testNoTrailingHeaders() throws Exception {
282282
Context ctx =
283283
tomcat.addContext("", System.getProperty("java.io.tmpdir"));
284284

285-
Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet());
285+
Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet(true));
286286
ctx.addServletMapping("/", "servlet");
287287

288288
tomcat.start();
@@ -311,11 +311,136 @@ public void testNoTrailingHeaders() throws Exception {
311311
assertEquals("nullnull7nullnull", client.getResponseBody());
312312
}
313313

314+
@Test
315+
public void testChunkSizeZero() throws Exception {
316+
doTestChunkSize(true, true, "", 10, 0);
317+
}
318+
319+
@Test
320+
public void testChunkSizeAbsent() throws Exception {
321+
doTestChunkSize(false, false, SimpleHttpClient.CRLF, 10, 0);
322+
}
323+
324+
@Test
325+
public void testChunkSizeTwentyFive() throws Exception {
326+
doTestChunkSize(true, true, "19" + SimpleHttpClient.CRLF
327+
+ "Hello World!Hello World!!" + SimpleHttpClient.CRLF, 40, 25);
328+
}
329+
330+
@Test
331+
public void testChunkSizeEightDigit() throws Exception {
332+
doTestChunkSize(true, true, "0000000C" + SimpleHttpClient.CRLF
333+
+ "Hello World!" + SimpleHttpClient.CRLF, 20, 12);
334+
}
335+
336+
@Test
337+
public void testChunkSizeNineDigit() throws Exception {
338+
doTestChunkSize(false, false, "00000000C" + SimpleHttpClient.CRLF
339+
+ "Hello World!" + SimpleHttpClient.CRLF, 20, 12);
340+
}
341+
342+
@Test
343+
public void testChunkSizeLong() throws Exception {
344+
doTestChunkSize(true, false, "7fFFffFF" + SimpleHttpClient.CRLF
345+
+ "Hello World!" + SimpleHttpClient.CRLF, 10, 10);
346+
}
347+
348+
@Test
349+
public void testChunkSizeIntegerMinValue() throws Exception {
350+
doTestChunkSize(false, false, "80000000" + SimpleHttpClient.CRLF
351+
+ "Hello World!" + SimpleHttpClient.CRLF, 10, 10);
352+
}
353+
354+
@Test
355+
public void testChunkSizeMinusOne() throws Exception {
356+
doTestChunkSize(false, false, "ffffffff" + SimpleHttpClient.CRLF
357+
+ "Hello World!" + SimpleHttpClient.CRLF, 10, 10);
358+
}
359+
360+
/**
361+
* @param expectPass
362+
* If the servlet is expected to process the request
363+
* @param expectReadWholeBody
364+
* If the servlet is expected to fully read the body and reliably
365+
* deliver a response
366+
* @param chunks
367+
* Text of chunks
368+
* @param readLimit
369+
* Do not read more than this many bytes
370+
* @param expectReadCount
371+
* Expected count of read bytes
372+
* @throws Exception
373+
* Unexpected
374+
*/
375+
private void doTestChunkSize(boolean expectPass,
376+
boolean expectReadWholeBody, String chunks, int readLimit,
377+
int expectReadCount) throws Exception {
378+
// Setup Tomcat instance
379+
Tomcat tomcat = getTomcatInstance();
380+
381+
// Must have a real docBase - just use temp
382+
Context ctx = tomcat.addContext("",
383+
System.getProperty("java.io.tmpdir"));
384+
385+
BodyReadServlet servlet = new BodyReadServlet(expectPass, readLimit);
386+
Tomcat.addServlet(ctx, "servlet", servlet);
387+
ctx.addServletMapping("/", "servlet");
388+
389+
tomcat.start();
390+
391+
String request = "POST /echo-params.jsp HTTP/1.1"
392+
+ SimpleHttpClient.CRLF + "Host: any" + SimpleHttpClient.CRLF
393+
+ "Transfer-encoding: chunked" + SimpleHttpClient.CRLF
394+
+ "Content-Type: text/plain" + SimpleHttpClient.CRLF;
395+
if (expectPass) {
396+
request += "Connection: close" + SimpleHttpClient.CRLF;
397+
}
398+
request += SimpleHttpClient.CRLF + chunks + "0" + SimpleHttpClient.CRLF
399+
+ SimpleHttpClient.CRLF;
400+
401+
TrailerClient client = new TrailerClient(tomcat.getConnector()
402+
.getLocalPort());
403+
client.setRequest(new String[] { request });
404+
405+
Exception processException = null;
406+
client.connect();
407+
try {
408+
client.processRequest();
409+
} catch (Exception e) {
410+
// Socket was probably closed before client had a chance to read
411+
// response
412+
processException = e;
413+
}
414+
if (expectPass) {
415+
if (expectReadWholeBody) {
416+
assertNull(processException);
417+
}
418+
if (processException == null) {
419+
assertTrue(client.getResponseLine(), client.isResponse200());
420+
assertEquals(String.valueOf(expectReadCount),
421+
client.getResponseBody());
422+
}
423+
assertEquals(expectReadCount, servlet.getCountRead());
424+
} else {
425+
if (processException == null) {
426+
assertTrue(client.getResponseLine(), client.isResponse500());
427+
}
428+
assertEquals(0, servlet.getCountRead());
429+
assertTrue(servlet.getExceptionDuringRead());
430+
}
431+
}
432+
314433
private static class EchoHeaderServlet extends HttpServlet {
315434
private static final long serialVersionUID = 1L;
316435

317436
private boolean exceptionDuringRead = false;
318437

438+
private final boolean expectPass;
439+
440+
public EchoHeaderServlet(boolean expectPass) {
441+
this.expectPass = expectPass;
442+
}
443+
319444
@Override
320445
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
321446
throws ServletException, IOException {
@@ -334,6 +459,11 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp)
334459
}
335460
} catch (IOException ioe) {
336461
exceptionDuringRead = true;
462+
if (!expectPass) { // as expected
463+
log(ioe.toString());
464+
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
465+
return;
466+
}
337467
throw ioe;
338468
}
339469

@@ -358,6 +488,53 @@ private void dumpHeader(String headerName, HttpServletRequest req,
358488
}
359489
}
360490

491+
private static class BodyReadServlet extends HttpServlet {
492+
private static final long serialVersionUID = 1L;
493+
494+
private boolean exceptionDuringRead = false;
495+
private int countRead = 0;
496+
private final boolean expectPass;
497+
private final int readLimit;
498+
499+
public BodyReadServlet(boolean expectPass, int readLimit) {
500+
this.expectPass = expectPass;
501+
this.readLimit = readLimit;
502+
}
503+
504+
@Override
505+
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
506+
throws ServletException, IOException {
507+
resp.setContentType("text/plain");
508+
PrintWriter pw = resp.getWriter();
509+
510+
// Read the body - quick and dirty
511+
InputStream is = req.getInputStream();
512+
try {
513+
while (is.read() > -1 && countRead < readLimit) {
514+
countRead++;
515+
}
516+
} catch (IOException ioe) {
517+
exceptionDuringRead = true;
518+
if (!expectPass) { // as expected
519+
log(ioe.toString());
520+
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
521+
return;
522+
}
523+
throw ioe;
524+
}
525+
526+
pw.write(Integer.valueOf(countRead).toString());
527+
}
528+
529+
public boolean getExceptionDuringRead() {
530+
return exceptionDuringRead;
531+
}
532+
533+
public int getCountRead() {
534+
return countRead;
535+
}
536+
}
537+
361538
private static class TrailerClient extends SimpleHttpClient {
362539

363540
public TrailerClient(int port) {

‎webapps/docs/changelog.xml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@
149149
Add experimental NIO2 connector. Based on code developed by
150150
Nabil Benothman. (remm)
151151
</add>
152+
<fix>
153+
Improve processing of chuck size from chunked headers. Avoid overflow
154+
and use a bit shift instead of a multiplication as it is marginally
155+
faster. (markt/kkolinko)
156+
</fix>
152157
</changelog>
153158
</subsection>
154159
<subsection name="Jasper">

0 commit comments

Comments
 (0)