Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.apache.hc.core5.http.TruncatedChunkException;
import org.apache.hc.core5.http.config.Http1Config;
import org.apache.hc.core5.http.io.SessionInputBuffer;
import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.CharArrayBuffer;

Expand Down Expand Up @@ -269,6 +270,9 @@ private long getChunkSize() throws IOException {
separator = lineBuffer.length();
}
final String s = this.lineBuffer.substringTrimmed(0, separator);
if (MessageSupport.isSignedOrEmpty(s)) {
throw new MalformedChunkCodingException("Bad chunk header: " + s);
}
try {
return Long.parseLong(s, 16);
} catch (final NumberFormatException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.apache.hc.core5.http.config.Http1Config;
import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics;
import org.apache.hc.core5.http.message.BufferedHeader;
import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.http.nio.SessionInputBuffer;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.CharArrayBuffer;
Expand Down Expand Up @@ -128,6 +129,9 @@ private void readChunkHead() throws IOException {
separator = this.lineBuf.length();
}
final String s = this.lineBuf.substringTrimmed(0, separator);
if (MessageSupport.isSignedOrEmpty(s)) {
throw new MalformedChunkCodingException("Bad chunk header: " + s);
}
try {
this.chunkSize = Long.parseLong(s, 16);
} catch (final NumberFormatException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,13 @@ public static Set<String> hopByHopConnectionSpecific(final MessageHeaders header
return HOP_BY_HOP;
}

/**
* @since 5.5
*/
public static boolean isSignedOrEmpty(final String s) {
return s == null || s.isEmpty() || s.charAt(0) == '+' || s.charAt(0) == '-';
}

/**
* @since 5.5
*/
Expand All @@ -743,12 +750,11 @@ public static long getContentLength(final HttpMessage message) throws HttpExcept
final Header contentLengthHeader = message.getFirstHeader(HttpHeaders.CONTENT_LENGTH);
if (contentLengthHeader != null) {
final String s = contentLengthHeader.getValue();
if (isSignedOrEmpty(s)) {
throw new ProtocolException("Invalid content length: " + s);
}
try {
final long len = Long.parseLong(s);
if (len < 0) {
throw new ProtocolException("Negative content length: " + s);
}
return len;
return Long.parseLong(s);
} catch (final NumberFormatException e) {
throw new ProtocolException("Invalid content length: " + s);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,15 @@ void testEntityWithNegativeContentLength() {
lenStrategy.determineLength(message));
}

@Test
void testEntityWithPlusSignedContentLength() {
final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy();
final HttpMessage message = new TestHttpMessage();
message.addHeader("Content-Length", "+5");
Assertions.assertThrows(ProtocolException.class, () ->
lenStrategy.determineLength(message));
}

@Test
void testEntityNoContentDelimiter() throws Exception {
final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,60 @@ void testCorruptChunkedInputStreamInvalidSize() throws IOException {
}
}

@Test
void testCorruptChunkedInputStreamSignedSize() throws IOException {
final String s = "+5\r\n01234\r\n0\r\n\r\n";
final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) {
Assertions.assertThrows(MalformedChunkCodingException.class, in::read);
}
}

@Test
void testCorruptChunkedInputStreamMinusZeroSize() throws IOException {
final String s = "-0\r\n\r\n";
final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) {
Assertions.assertThrows(MalformedChunkCodingException.class, in::read);
}
}

@Test
void testCorruptChunkedInputStreamOverflowSize() throws IOException {
final String s = "FFFFFFFFFFFFFFFFF\r\n01234\r\n0\r\n\r\n";
final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) {
Assertions.assertThrows(MalformedChunkCodingException.class, in::read);
}
}

@Test
void testChunkedInputStreamSurroundingWhitespaceSize() throws IOException {
final String s = " 5 \r\n01234\r\n0\r\n\r\n";
final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) {
final byte[] tmp = new byte[5];
Assertions.assertEquals(5, in.read(tmp));
Assertions.assertEquals("01234", new String(tmp, StandardCharsets.US_ASCII));
}
}

@Test
void testChunkedInputStreamLeadingZerosSize() throws IOException {
final String s = "0005\r\n01234\r\n0\r\n\r\n";
final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) {
final byte[] tmp = new byte[5];
Assertions.assertEquals(5, in.read(tmp));
Assertions.assertEquals("01234", new String(tmp, StandardCharsets.US_ASCII));
}
}

// Negative chunk size
@Test
void testCorruptChunkedInputStreamNegativeSize() throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,66 @@ void testMalformedChunk() {
Assertions.assertThrows(MalformedChunkCodingException.class, () -> decoder.read(dst));
}

private static void decode(final String s) throws Exception {
final ReadableByteChannel channel = new ReadableByteChannelMock(
new String[] {s}, StandardCharsets.US_ASCII);
final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, new BasicHttpTransportMetrics());
final ByteBuffer dst = ByteBuffer.allocate(1024);
for (int i = 0; i < 10 && !decoder.isCompleted(); i++) {
dst.clear();
if (decoder.read(dst) == -1) {
break;
}
}
}

@Test
void testChunkSizeMinusOneRejected() {
Assertions.assertThrows(MalformedChunkCodingException.class,
() -> decode("-1\r\n5\r\nhello\r\n0\r\n\r\n"));
}

@Test
void testChunkSizeNegativeRejected() {
Assertions.assertThrows(MalformedChunkCodingException.class,
() -> decode("-5\r\nhello\r\n0\r\n\r\n"));
}

@Test
void testChunkSizePlusSignRejected() {
Assertions.assertThrows(MalformedChunkCodingException.class,
() -> decode("+5\r\nhello\r\n0\r\n\r\n"));
}

@Test
void testChunkSizeMinusZeroRejected() {
Assertions.assertThrows(MalformedChunkCodingException.class,
() -> decode("-0\r\n\r\n"));
}

@Test
void testChunkSizeBlankRejected() {
Assertions.assertThrows(MalformedChunkCodingException.class,
() -> decode(" \r\nhello\r\n0\r\n\r\n"));
}

@Test
void testChunkSizeOverflowRejected() {
Assertions.assertThrows(MalformedChunkCodingException.class,
() -> decode("FFFFFFFFFFFFFFFFF\r\nhello\r\n0\r\n\r\n"));
}

@Test
void testChunkSizeSurroundingWhitespaceAccepted() throws Exception {
decode(" 5 \r\nhello\r\n0\r\n\r\n");
}

@Test
void testChunkSizeLeadingZerosAccepted() throws Exception {
decode("0005\r\nhello\r\n0\r\n\r\n");
}

@Test
void testIncompleteChunkDecoding() throws Exception {
final String[] chunks = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,57 @@ void testMessageWithNegativeContentLength() {
MessageSupport.getContentLength(message));
}

@Test
void testMessageWithPlusSignedContentLength() {
final HttpMessage message = new BasicHttpResponse(200);
message.addHeader("Content-Length", "+5");
Assertions.assertThrows(ProtocolException.class, () ->
MessageSupport.getContentLength(message));
}

@Test
void testMessageWithEmptyContentLength() {
final HttpMessage message = new BasicHttpResponse(200);
message.addHeader("Content-Length", "");
Assertions.assertThrows(ProtocolException.class, () ->
MessageSupport.getContentLength(message));
}

@Test
void testIsSignedOrEmpty() {
Assertions.assertTrue(MessageSupport.isSignedOrEmpty(null));
Assertions.assertTrue(MessageSupport.isSignedOrEmpty(""));
Assertions.assertTrue(MessageSupport.isSignedOrEmpty("+5"));
Assertions.assertTrue(MessageSupport.isSignedOrEmpty("-5"));
Assertions.assertTrue(MessageSupport.isSignedOrEmpty("-0"));
Assertions.assertFalse(MessageSupport.isSignedOrEmpty("5"));
Assertions.assertFalse(MessageSupport.isSignedOrEmpty("0005"));
Assertions.assertFalse(MessageSupport.isSignedOrEmpty("a+"));
}

@Test
void testMessageWithMinusZeroContentLength() {
final HttpMessage message = new BasicHttpResponse(200);
message.addHeader("Content-Length", "-0");
Assertions.assertThrows(ProtocolException.class, () ->
MessageSupport.getContentLength(message));
}

@Test
void testMessageWithOverflowContentLength() {
final HttpMessage message = new BasicHttpResponse(200);
message.addHeader("Content-Length", "99999999999999999999");
Assertions.assertThrows(ProtocolException.class, () ->
MessageSupport.getContentLength(message));
}

@Test
void testMessageWithLeadingZerosContentLength() throws Exception {
final HttpMessage message = new BasicHttpResponse(200);
message.addHeader("Content-Length", "005");
Assertions.assertEquals(5, MessageSupport.getContentLength(message));
}

@Test
void testMessageWithMultipleContentLength() throws Exception {
final HttpMessage message = new BasicHttpResponse(200);
Expand Down
Loading