Remove content-length header leniency

Motivation:

If the content-length does not parse as a number, leniency causes this
to instead be parsed as the default value. This leads to bodies being
silently ignored on requests which can be incredibly dangerous. Instead,
if the content-length header is invalid, an exception should be thrown
for upstream handling.

Modifications:

This commit removes the leniency in parsing the content-length header by
allowing a number format exception, if thrown, to escape from the method
rather than falling back to the default value.

Result:

In invalid content-length header will not be silently ignored.
This commit is contained in:
Jason Tedor 2017-06-19 11:16:39 -04:00 committed by Scott Mitchell
parent 6cd086050f
commit 9ad74e72e6
3 changed files with 49 additions and 18 deletions

View File

@ -151,7 +151,11 @@ public class HttpObjectAggregator
@Override @Override
protected boolean isContentLengthInvalid(HttpMessage start, int maxContentLength) { protected boolean isContentLengthInvalid(HttpMessage start, int maxContentLength) {
try {
return getContentLength(start, -1L) > maxContentLength; return getContentLength(start, -1L) > maxContentLength;
} catch (final NumberFormatException e) {
return false;
}
} }
@Override @Override

View File

@ -172,23 +172,19 @@ public final class HttpUtil {
} }
/** /**
* Returns the length of the content. Please note that this value is * Returns the length of the content or the specified default value if the message does not have the {@code
* not retrieved from {@link HttpContent#content()} but from the * "Content-Length" header}. Please note that this value is not retrieved from {@link HttpContent#content()} but
* {@code "Content-Length"} header, and thus they are independent from each * from the {@code "Content-Length"} header, and thus they are independent from each other.
* other.
* *
* @return the content length or {@code defaultValue} if this message does * @param message the message
* not have the {@code "Content-Length"} header or its value is not * @param defaultValue the default value
* a number * @return the content length or the specified default value
* @throws NumberFormatException if the {@code "Content-Length"} header does not parse as a long
*/ */
public static long getContentLength(HttpMessage message, long defaultValue) { public static long getContentLength(HttpMessage message, long defaultValue) {
String value = message.headers().get(HttpHeaderNames.CONTENT_LENGTH); String value = message.headers().get(HttpHeaderNames.CONTENT_LENGTH);
if (value != null) { if (value != null) {
try {
return Long.parseLong(value); return Long.parseLong(value);
} catch (NumberFormatException ignore) {
return defaultValue;
}
} }
// We know the content length if it's a Web Socket message even if // We know the content length if it's a Web Socket message even if

View File

@ -25,10 +25,14 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import static io.netty.handler.codec.http.HttpHeadersTestUtils.of; import static io.netty.handler.codec.http.HttpHeadersTestUtils.of;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasToString;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class HttpUtilTest { public class HttpUtilTest {
@ -130,12 +134,39 @@ public class HttpUtilTest {
} }
@Test @Test
public void testGetContentLengthDefaultValue() { public void testGetContentLengthThrowsNumberFormatException() {
HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); final HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
assertNull(message.headers().get(HttpHeaderNames.CONTENT_LENGTH));
message.headers().set(HttpHeaderNames.CONTENT_LENGTH, "bar"); message.headers().set(HttpHeaderNames.CONTENT_LENGTH, "bar");
assertEquals("bar", message.headers().get(HttpHeaderNames.CONTENT_LENGTH)); try {
assertEquals(1L, HttpUtil.getContentLength(message, 1L)); HttpUtil.getContentLength(message);
fail();
} catch (final NumberFormatException e) {
// a number format exception is expected here
}
}
@Test
public void testGetContentLengthIntDefaultValueThrowsNumberFormatException() {
final HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
message.headers().set(HttpHeaderNames.CONTENT_LENGTH, "bar");
try {
HttpUtil.getContentLength(message, 1);
fail();
} catch (final NumberFormatException e) {
// a number format exception is expected here
}
}
@Test
public void testGetContentLengthLongDefaultValueThrowsNumberFormatException() {
final HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
message.headers().set(HttpHeaderNames.CONTENT_LENGTH, "bar");
try {
HttpUtil.getContentLength(message, 1L);
fail();
} catch (final NumberFormatException e) {
// a number format exception is expected here
}
} }
@Test @Test