Skip to content
Open
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 @@ -41,10 +41,12 @@
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.HttpVersion;
import org.apache.hc.core5.http.Method;
import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics;
import org.apache.hc.core5.http.impl.IncomingEntityDetails;
import org.apache.hc.core5.http.impl.nio.MessageState;
import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.http.message.StatusLine;
import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler;
import org.apache.hc.core5.http.nio.AsyncPushConsumer;
Expand Down Expand Up @@ -72,6 +74,10 @@ class ClientH2StreamHandler implements H2StreamHandler {
private final AtomicBoolean failed;
private final AtomicBoolean done;

private volatile String method = null;
private volatile long declaredContentLen = -1;
private volatile long actualContentLen = 0;

ClientH2StreamHandler(
final H2StreamChannel outputChannel,
final HttpProcessor httpProcessor,
Expand Down Expand Up @@ -141,6 +147,8 @@ private void commitRequest(final HttpRequest request, final EntityDetails entity

httpProcessor.process(request, entityDetails, context);

method = request.getMethod();

final List<Header> headers = DefaultH2RequestConverter.INSTANCE.convert(request);
if (entityDetails == null) {
requestState.set(MessageState.COMPLETE);
Expand Down Expand Up @@ -207,6 +215,13 @@ public void consumeHeader(final List<Header> headers, final boolean endStream) t
return;
}

if (!Method.HEAD.isSame(method) && MessageSupport.canResponseHaveBody(method, response)) {
declaredContentLen = MessageSupport.getContentLength(response);
if (endStream) {
validateContentLength();
}
}

final EntityDetails entityDetails = endStream ? null : new IncomingEntityDetails(response, -1);
context.setResponse(response);
httpProcessor.process(response, entityDetails, context);
Expand Down Expand Up @@ -236,14 +251,22 @@ public void consumeData(final ByteBuffer src, final boolean endStream) throws Ht
throw new ProtocolException("Unexpected message data");
}
if (src != null) {
actualContentLen += src.remaining();
exchangeHandler.consume(src);
}
if (endStream) {
responseState.set(MessageState.COMPLETE);
validateContentLength();
exchangeHandler.streamEnd(null);
}
}

private void validateContentLength() throws ProtocolException {
if (declaredContentLen >= 0 && declaredContentLen != actualContentLen) {
throw new ProtocolException("Invalid content-length (does not equal the sum of data payload)");
}
}

@Override
public void handle(final HttpException ex, final boolean endStream) throws HttpException, IOException {
throw ex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics;
import org.apache.hc.core5.http.impl.IncomingEntityDetails;
import org.apache.hc.core5.http.impl.nio.MessageState;
import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.http.nio.AsyncPushConsumer;
import org.apache.hc.core5.http.nio.HandlerFactory;
import org.apache.hc.core5.http.protocol.HttpCoreContext;
Expand All @@ -67,6 +68,9 @@ class ClientPushH2StreamHandler implements H2StreamHandler {
private volatile MessageState requestState;
private volatile MessageState responseState;

private volatile long declaredContentLen = -1;
private volatile long actualContentLen = 0;

ClientPushH2StreamHandler(
final H2StreamChannel outputChannel,
final HttpProcessor httpProcessor,
Expand Down Expand Up @@ -132,6 +136,14 @@ public void consumeHeader(final List<Header> headers, final boolean endStream) t
Asserts.notNull(exchangeHandler, "Exchange handler");

final HttpResponse response = DefaultH2ResponseConverter.INSTANCE.convert(headers);

if (MessageSupport.canResponseHaveBody(response)) {
declaredContentLen = MessageSupport.getContentLength(response);
if (endStream) {
validateContentLength();
}
}

final EntityDetails entityDetails = endStream ? null : new IncomingEntityDetails(request, -1);

context.setResponse(response);
Expand Down Expand Up @@ -163,14 +175,22 @@ public void consumeData(final ByteBuffer src, final boolean endStream) throws Ht
}
Asserts.notNull(exchangeHandler, "Exchange handler");
if (src != null) {
actualContentLen += src.remaining();
exchangeHandler.consume(src);
}
if (endStream) {
responseState = MessageState.COMPLETE;
validateContentLength();
exchangeHandler.streamEnd(null);
}
}

private void validateContentLength() throws ProtocolException {
if (declaredContentLen >= 0 && declaredContentLen != actualContentLen) {
throw new ProtocolException("Invalid content-length (does not equal the sum of data payload)");
}
}

public boolean isDone() {
return responseState == MessageState.COMPLETE;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.apache.hc.core5.http.impl.IncomingEntityDetails;
import org.apache.hc.core5.http.impl.ServerSupport;
import org.apache.hc.core5.http.impl.nio.MessageState;
import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.http.nio.AsyncPushConsumer;
import org.apache.hc.core5.http.nio.AsyncPushProducer;
import org.apache.hc.core5.http.nio.AsyncResponseProducer;
Expand Down Expand Up @@ -81,6 +82,8 @@ class ServerH2StreamHandler implements H2StreamHandler {

private volatile AsyncServerExchangeHandler exchangeHandler;
private volatile HttpRequest receivedRequest;
private volatile long declaredContentLen = -1;
private volatile long actualContentLen = 0;

ServerH2StreamHandler(
final H2StreamChannel outputChannel,
Expand Down Expand Up @@ -230,6 +233,12 @@ public void consumeHeader(final List<Header> headers, final boolean endStream) t
requestState.set(endStream ? MessageState.COMPLETE : MessageState.BODY);

final HttpRequest request = DefaultH2RequestConverter.INSTANCE.convert(headers);

declaredContentLen = MessageSupport.getContentLength(request);
if (endStream) {
validateContentLength();
}

final EntityDetails requestEntityDetails = endStream ? null : new IncomingEntityDetails(request, -1);

final AsyncServerExchangeHandler handler;
Expand Down Expand Up @@ -295,14 +304,22 @@ public void consumeData(final ByteBuffer src, final boolean endStream) throws Ht
}
Asserts.notNull(exchangeHandler, "Exchange handler");
if (src != null) {
actualContentLen += src.remaining();
exchangeHandler.consume(src);
}
if (endStream) {
requestState.set(MessageState.COMPLETE);
validateContentLength();
exchangeHandler.streamEnd(null);
}
}

private void validateContentLength() throws ProtocolException {
if (declaredContentLen >= 0 && declaredContentLen != actualContentLen) {
throw new ProtocolException("Invalid content-length (does not equal the sum of data payload)");
}
}

@Override
public boolean isOutputReady() {
return responseState.get() == MessageState.BODY && exchangeHandler != null && exchangeHandler.available() > 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
package org.apache.hc.core5.http2.impl.nio;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

Expand Down Expand Up @@ -116,4 +117,34 @@ void consumeTrailersWithPseudoHeaderRejected() throws Exception {
Mockito.verify(exchangeHandler, Mockito.never()).streamEnd(Mockito.anyList());
}

@Test
void contentLengthValid() throws Exception {
final List<Header> responseHeaders = Arrays.asList(
new BasicHeader(":status", "200"),
new BasicHeader("content-length", "12"));
handler.consumeHeader(responseHeaders, false);
handler.consumeData(ByteBuffer.wrap(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }), false);
handler.consumeData(ByteBuffer.wrap(new byte[] { 0, 1 }), true);
}

@Test
void contentLengthInvalid() throws Exception {
final List<Header> responseHeaders = Arrays.asList(
new BasicHeader(":status", "200"),
new BasicHeader("content-length", "12"));
handler.consumeHeader(responseHeaders, false);
handler.consumeData(ByteBuffer.wrap(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }), false);
Assertions.assertThrows(ProtocolException.class, () ->
handler.consumeData(ByteBuffer.wrap(new byte[] { 0, 1, 2 }), true));
}

@Test
void contentLengthInvalidNoBody() throws Exception {
final List<Header> responseHeaders = Arrays.asList(
new BasicHeader(":status", "200"),
new BasicHeader("content-length", "12"));
Assertions.assertThrows(ProtocolException.class, () ->
handler.consumeHeader(responseHeaders, true));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
package org.apache.hc.core5.http2.impl.nio;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;

import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics;
import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics;
Expand All @@ -42,6 +45,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

class TestClientPushH2StreamHandler {
Expand All @@ -51,6 +55,8 @@ class TestClientPushH2StreamHandler {
@Mock
HttpProcessor httpProcessor;
@Mock
AsyncPushConsumer pushConsumer;
@Mock
HandlerFactory<AsyncPushConsumer> pushHandlerFactory;

ClientPushH2StreamHandler handler;
Expand Down Expand Up @@ -88,4 +94,55 @@ void updateCapacityFailsWithoutHandler() {
Assertions.assertThrows(IllegalStateException.class, handler::updateInputCapacity);
}

@Test
void contentLengthValid() throws Exception {
Mockito.when(pushHandlerFactory.create(Mockito.any(), Mockito.any())).thenReturn(pushConsumer);
handler.consumePromise(java.util.Arrays.asList(
new BasicHeader(":method", "GET"),
new BasicHeader(":scheme", "https"),
new BasicHeader(":authority", "example.com"),
new BasicHeader(":path", "/")));

final List<Header> responseHeaders = Arrays.asList(
new BasicHeader(":status", "200"),
new BasicHeader("content-length", "12"));
handler.consumeHeader(responseHeaders, false);
handler.consumeData(ByteBuffer.wrap(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }), false);
handler.consumeData(ByteBuffer.wrap(new byte[] { 0, 1 }), true);
}

@Test
void contentLengthInvalid() throws Exception {
Mockito.when(pushHandlerFactory.create(Mockito.any(), Mockito.any())).thenReturn(pushConsumer);
handler.consumePromise(java.util.Arrays.asList(
new BasicHeader(":method", "GET"),
new BasicHeader(":scheme", "https"),
new BasicHeader(":authority", "example.com"),
new BasicHeader(":path", "/")));

final List<Header> responseHeaders = Arrays.asList(
new BasicHeader(":status", "200"),
new BasicHeader("content-length", "12"));
handler.consumeHeader(responseHeaders, false);
handler.consumeData(ByteBuffer.wrap(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }), false);
Assertions.assertThrows(ProtocolException.class, () ->
handler.consumeData(ByteBuffer.wrap(new byte[] { 0, 1, 2 }), true));
}

@Test
void contentLengthInvalidNoBody() throws Exception {
Mockito.when(pushHandlerFactory.create(Mockito.any(), Mockito.any())).thenReturn(pushConsumer);
handler.consumePromise(java.util.Arrays.asList(
new BasicHeader(":method", "GET"),
new BasicHeader(":scheme", "https"),
new BasicHeader(":authority", "example.com"),
new BasicHeader(":path", "/")));

final List<Header> responseHeaders = Arrays.asList(
new BasicHeader(":status", "200"),
new BasicHeader("content-length", "12"));
Assertions.assertThrows(ProtocolException.class, () ->
handler.consumeHeader(responseHeaders, true));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,49 @@ void consumeTrailersWithoutEndStreamRejected() throws Exception {
Mockito.verify(exchangeHandler, Mockito.never()).streamEnd(ArgumentMatchers.anyList());
}

@Test
void contentLengthValid() throws Exception {
Mockito.when(exchangeHandlerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any()))
.thenReturn(exchangeHandler);
final List<Header> requestHeaders = Arrays.asList(
new BasicHeader(":method", "POST"),
new BasicHeader(":scheme", "https"),
new BasicHeader(":authority", "example.test"),
new BasicHeader(":path", "/upload"),
new BasicHeader("content-length", "12"));
handler.consumeHeader(requestHeaders, false);
handler.consumeData(ByteBuffer.wrap(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }), false);
handler.consumeData(ByteBuffer.wrap(new byte[] { 0, 1 }), true);
}

@Test
void contentLengthInvalid() throws Exception {
Mockito.when(exchangeHandlerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any()))
.thenReturn(exchangeHandler);
final List<Header> requestHeaders = Arrays.asList(
new BasicHeader(":method", "POST"),
new BasicHeader(":scheme", "https"),
new BasicHeader(":authority", "example.test"),
new BasicHeader(":path", "/upload"),
new BasicHeader("content-length", "12"));
handler.consumeHeader(requestHeaders, false);
handler.consumeData(ByteBuffer.wrap(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }), false);
Assertions.assertThrows(ProtocolException.class, () ->
handler.consumeData(ByteBuffer.wrap(new byte[] { 0, 1, 2 }), true));
}

@Test
void contentLengthInvalidNoBody() throws Exception {
Mockito.when(exchangeHandlerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any()))
.thenReturn(exchangeHandler);
final List<Header> requestHeaders = Arrays.asList(
new BasicHeader(":method", "POST"),
new BasicHeader(":scheme", "https"),
new BasicHeader(":authority", "example.test"),
new BasicHeader(":path", "/upload"),
new BasicHeader("content-length", "12"));
Assertions.assertThrows(ProtocolException.class, () ->
handler.consumeHeader(requestHeaders, true));
}

}
Loading