You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2019/12/29 18:09:46 UTC

[httpcomponents-core] branch master updated (d05d79e -> fb0cc7f)

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git.


    from d05d79e  Clean up connection window management
     new 030347d  Enforce H2 SETTINGS_MAX_HEADER_LIST_SIZE limit for HTTP/2 messages
     new fb0cc7f  Support for status code 431 (Request Header Fields Too Large) by H2 server side protocol handler

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/hc/core5/http2/hpack/HPackDecoder.java  | 37 ++++++++--
 ...ion.java => HeaderListConstraintException.java} | 20 +++---
 .../impl/nio/AbstractH2StreamMultiplexer.java      | 18 ++++-
 .../http2/impl/nio/ClientH2StreamHandler.java      |  5 ++
 .../http2/impl/nio/ClientPushH2StreamHandler.java  |  5 ++
 .../hc/core5/http2/impl/nio/H2StreamHandler.java   |  2 +
 .../http2/impl/nio/ServerH2StreamHandler.java      | 76 +++++++++++++--------
 .../http2/impl/nio/ServerH2StreamMultiplexer.java  | 15 +++++
 .../http2/impl/nio/ServerPushH2StreamHandler.java  |  5 ++
 .../hc/core5/http2/hpack/TestHPackCoding.java      | 25 +++++++
 .../hc/core5/testing/nio/H2IntegrationTest.java    | 78 ++++++++++++++++++++++
 11 files changed, 240 insertions(+), 46 deletions(-)
 copy httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/{HPackException.java => HeaderListConstraintException.java} (78%)


[httpcomponents-core] 02/02: Support for status code 431 (Request Header Fields Too Large) by H2 server side protocol handler

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit fb0cc7f2e51ee12306532b723a3edb68890afe1c
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Wed Dec 25 13:05:00 2019 +0100

    Support for status code 431 (Request Header Fields Too Large) by H2 server side protocol handler
---
 .../impl/nio/AbstractH2StreamMultiplexer.java      | 16 ++++-
 .../http2/impl/nio/ClientH2StreamHandler.java      |  5 ++
 .../http2/impl/nio/ClientPushH2StreamHandler.java  |  5 ++
 .../hc/core5/http2/impl/nio/H2StreamHandler.java   |  2 +
 .../http2/impl/nio/ServerH2StreamHandler.java      | 76 +++++++++++++--------
 .../http2/impl/nio/ServerH2StreamMultiplexer.java  | 15 +++++
 .../http2/impl/nio/ServerPushH2StreamHandler.java  |  5 ++
 .../hc/core5/testing/nio/H2IntegrationTest.java    | 78 ++++++++++++++++++++++
 8 files changed, 173 insertions(+), 29 deletions(-)

diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java
index d16df1f..1c756fd 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java
@@ -768,6 +768,8 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
                     stream.localReset(ex);
                 } catch (final HttpStreamResetException ex) {
                     stream.localReset(ex, ex.getCause() != null ? H2Error.INTERNAL_ERROR : H2Error.CANCEL);
+                } catch (final HttpException ex) {
+                    stream.handle(ex);
                 }
 
                 if (stream.isTerminated()) {
@@ -1038,6 +1040,10 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
         }
     }
 
+    List<Header> decodeHeaders(final ByteBuffer payload) throws HttpException {
+        return hPackDecoder.decodeHeaders(payload);
+    }
+
     private void consumeHeaderFrame(final RawFrame frame, final H2Stream stream) throws HttpException, IOException {
         final int streamId = stream.getId();
         if (!frame.isFlagSet(FrameFlag.END_HEADERS)) {
@@ -1050,7 +1056,7 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
             payload.get();
         }
         if (continuation == null) {
-            final List<Header> headers = hPackDecoder.decodeHeaders(payload);
+            final List<Header> headers = decodeHeaders(payload);
             if (stream.isRemoteInitiated() && streamId > processedRemoteStreamId) {
                 processedRemoteStreamId = streamId;
             }
@@ -1080,7 +1086,7 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
         final ByteBuffer payload = frame.getPayload();
         continuation.copyPayload(payload);
         if (frame.isFlagSet(FrameFlag.END_HEADERS)) {
-            final List<Header> headers = hPackDecoder.decodeHeaders(continuation.getContent());
+            final List<Header> headers = decodeHeaders(continuation.getContent());
             if (stream.isRemoteInitiated() && streamId > processedRemoteStreamId) {
                 processedRemoteStreamId = streamId;
             }
@@ -1525,7 +1531,7 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
 
     }
 
-    private static class H2Stream {
+    static class H2Stream {
 
         private final H2StreamChannelImpl channel;
         private final H2StreamHandler handler;
@@ -1638,6 +1644,10 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
             localReset(ex, ex.getCode());
         }
 
+        void handle(final HttpException ex) throws IOException, HttpException {
+            handler.handle(ex, channel.isRemoteClosed());
+        }
+
         HandlerFactory<AsyncPushConsumer> getPushHandlerFactory() {
             return handler.getPushHandlerFactory();
         }
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamHandler.java
index 17bf977..a1dc57f 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamHandler.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamHandler.java
@@ -251,6 +251,11 @@ class ClientH2StreamHandler implements H2StreamHandler {
     }
 
     @Override
+    public void handle(final HttpException ex, final boolean endStream) throws HttpException, IOException {
+        throw ex;
+    }
+
+    @Override
     public void failed(final Exception cause) {
         try {
             if (failed.compareAndSet(false, true)) {
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientPushH2StreamHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientPushH2StreamHandler.java
index 1e7dd5b..46c3e8c 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientPushH2StreamHandler.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientPushH2StreamHandler.java
@@ -190,6 +190,11 @@ class ClientPushH2StreamHandler implements H2StreamHandler {
     }
 
     @Override
+    public void handle(final HttpException ex, final boolean endStream) throws HttpException {
+        throw ex;
+    }
+
+    @Override
     public void releaseResources() {
         if (done.compareAndSet(false, true)) {
             responseState = MessageState.COMPLETE;
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2StreamHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2StreamHandler.java
index 11c0703..488727d 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2StreamHandler.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2StreamHandler.java
@@ -54,4 +54,6 @@ interface H2StreamHandler extends ResourceHolder {
 
     void failed(Exception cause);
 
+    void handle(final HttpException ex, final boolean endStream) throws HttpException, IOException;
+
 }
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamHandler.java
index fb61bf3..27446e8 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamHandler.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamHandler.java
@@ -63,10 +63,11 @@ import org.apache.hc.core5.http2.impl.DefaultH2RequestConverter;
 import org.apache.hc.core5.http2.impl.DefaultH2ResponseConverter;
 import org.apache.hc.core5.util.Asserts;
 
-public class ServerH2StreamHandler implements H2StreamHandler {
+class ServerH2StreamHandler implements H2StreamHandler {
 
     private final H2StreamChannel outputChannel;
     private final DataStreamChannel dataChannel;
+    private final ResponseChannel responseChannel;
     private final HttpProcessor httpProcessor;
     private final BasicHttpConnectionMetrics connMetrics;
     private final HandlerFactory<AsyncServerExchangeHandler> exchangeHandlerFactory;
@@ -112,6 +113,27 @@ public class ServerH2StreamHandler implements H2StreamHandler {
             }
 
         };
+        this.responseChannel = new ResponseChannel() {
+
+            @Override
+            public void sendInformation(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException {
+                commitInformation(response);
+            }
+
+            @Override
+            public void sendResponse(
+                    final HttpResponse response, final EntityDetails responseEntityDetails, final HttpContext httpContext) throws HttpException, IOException {
+                ServerSupport.validateResponse(response, responseEntityDetails);
+                commitResponse(response, responseEntityDetails);
+            }
+
+            @Override
+            public void pushPromise(
+                    final HttpRequest promise, final AsyncPushProducer pushProducer, final HttpContext httpContext) throws HttpException, IOException {
+                commitPromise(promise, pushProducer);
+            }
+
+        };
         this.httpProcessor = httpProcessor;
         this.connMetrics = connMetrics;
         this.exchangeHandlerFactory = exchangeHandlerFactory;
@@ -154,9 +176,8 @@ public class ServerH2StreamHandler implements H2StreamHandler {
 
             final List<Header> responseHeaders = DefaultH2ResponseConverter.INSTANCE.convert(response);
 
-            Asserts.notNull(receivedRequest, "Received request");
-            final String method = receivedRequest.getMethod();
-            final boolean endStream = responseEntityDetails == null || Method.HEAD.isSame(method);
+            final boolean endStream = responseEntityDetails == null ||
+                    (receivedRequest != null && Method.HEAD.isSame(receivedRequest.getMethod()));
             outputChannel.submit(responseHeaders, endStream);
             connMetrics.incrementResponseCount();
             if (responseEntityDetails == null) {
@@ -212,28 +233,6 @@ public class ServerH2StreamHandler implements H2StreamHandler {
                 context.setProtocolVersion(HttpVersion.HTTP_2);
                 context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
 
-                final ResponseChannel responseChannel = new ResponseChannel() {
-
-                    @Override
-                    public void sendInformation(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException {
-                        commitInformation(response);
-                    }
-
-                    @Override
-                    public void sendResponse(
-                            final HttpResponse response, final EntityDetails responseEntityDetails, final HttpContext httpContext) throws HttpException, IOException {
-                        ServerSupport.validateResponse(response, responseEntityDetails);
-                        commitResponse(response, responseEntityDetails);
-                    }
-
-                    @Override
-                    public void pushPromise(
-                            final HttpRequest promise, final AsyncPushProducer pushProducer, final HttpContext httpContext) throws HttpException, IOException {
-                        commitPromise(promise, pushProducer);
-                    }
-
-                };
-
                 try {
                     httpProcessor.process(request, requestEntityDetails, context);
                     connMetrics.incrementRequestCount();
@@ -296,6 +295,31 @@ public class ServerH2StreamHandler implements H2StreamHandler {
     }
 
     @Override
+    public void handle(final HttpException ex, final boolean endStream) throws HttpException, IOException {
+        if (done.get()) {
+            throw ex;
+        }
+        switch (requestState) {
+            case HEADERS:
+                requestState = endStream ? MessageState.COMPLETE : MessageState.BODY;
+                if (!responseCommitted.get()) {
+                    final AsyncResponseProducer responseProducer = new BasicResponseProducer(
+                            ServerSupport.toStatusCode(ex),
+                            ServerSupport.toErrorMessage(ex));
+                    exchangeHandler = new ImmediateResponseExchangeHandler(responseProducer);
+                    exchangeHandler.handleRequest(null, null, responseChannel, context);
+                } else {
+                    throw ex;
+                }
+                break;
+            case BODY:
+                responseState = MessageState.COMPLETE;
+            default:
+                throw ex;
+        }
+    }
+
+    @Override
     public void failed(final Exception cause) {
         try {
             if (failed.compareAndSet(false, true)) {
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamMultiplexer.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamMultiplexer.java
index a1783a1..ecbb9d6 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamMultiplexer.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamMultiplexer.java
@@ -27,8 +27,13 @@
 package org.apache.hc.core5.http2.impl.nio;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
 
 import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.RequestHeaderFieldsTooLargeException;
 import org.apache.hc.core5.http.config.CharCodingConfig;
 import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics;
 import org.apache.hc.core5.http.nio.AsyncPushConsumer;
@@ -43,6 +48,7 @@ import org.apache.hc.core5.http2.config.H2Config;
 import org.apache.hc.core5.http2.frame.DefaultFrameFactory;
 import org.apache.hc.core5.http2.frame.FrameFactory;
 import org.apache.hc.core5.http2.frame.StreamIdGenerator;
+import org.apache.hc.core5.http2.hpack.HeaderListConstraintException;
 import org.apache.hc.core5.reactor.ProtocolIOSession;
 import org.apache.hc.core5.util.Args;
 
@@ -114,6 +120,15 @@ public class ServerH2StreamMultiplexer extends AbstractH2StreamMultiplexer {
     }
 
     @Override
+    List<Header> decodeHeaders(final ByteBuffer payload) throws HttpException {
+        try {
+            return super.decodeHeaders(payload);
+        } catch (final HeaderListConstraintException ex) {
+            throw new RequestHeaderFieldsTooLargeException(ex.getMessage(), ex);
+        }
+    }
+
+    @Override
     public String toString() {
         final StringBuilder buf = new StringBuilder();
         buf.append("[");
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerPushH2StreamHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerPushH2StreamHandler.java
index bc6b1d2..e47cdb1 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerPushH2StreamHandler.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerPushH2StreamHandler.java
@@ -228,6 +228,11 @@ class ServerPushH2StreamHandler implements H2StreamHandler {
     }
 
     @Override
+    public void handle(final HttpException ex, final boolean endStream) throws HttpException, IOException {
+        throw ex;
+    }
+
+    @Override
     public void failed(final Exception cause) {
         try {
             if (failed.compareAndSet(false, true)) {
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2IntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2IntegrationTest.java
index 2d18d27..68438d1 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2IntegrationTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2IntegrationTest.java
@@ -107,12 +107,16 @@ import org.apache.hc.core5.http.nio.support.BasicResponseProducer;
 import org.apache.hc.core5.http.nio.support.classic.AbstractClassicEntityConsumer;
 import org.apache.hc.core5.http.nio.support.classic.AbstractClassicEntityProducer;
 import org.apache.hc.core5.http.nio.support.classic.AbstractClassicServerExchangeHandler;
+import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
 import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.http2.H2Error;
 import org.apache.hc.core5.http2.H2StreamResetException;
 import org.apache.hc.core5.http2.config.H2Config;
 import org.apache.hc.core5.http2.nio.command.PingCommand;
 import org.apache.hc.core5.http2.nio.support.BasicPingHandler;
+import org.apache.hc.core5.http2.protocol.H2RequestConnControl;
+import org.apache.hc.core5.http2.protocol.H2RequestContent;
+import org.apache.hc.core5.http2.protocol.H2RequestTargetHost;
 import org.apache.hc.core5.reactor.Command;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.reactor.IOSession;
@@ -1051,4 +1055,78 @@ public class H2IntegrationTest extends InternalH2ServerTestBase {
         Assert.assertThat(endpointDetails.getRequestCount(), CoreMatchers.equalTo(0L));
     }
 
+    @Test
+    public void testHeaderTooLarge() throws Exception {
+        server.register("/hello", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new SingleLineResponseHandler("Hi there");
+            }
+
+        });
+        final InetSocketAddress serverEndpoint = server.start(H2Config.custom()
+                .setMaxHeaderListSize(100)
+                .build());
+        client.start();
+
+        final Future<ClientSessionEndpoint> connectFuture = client.connect(
+                "localhost", serverEndpoint.getPort(), TIMEOUT);
+        final ClientSessionEndpoint streamEndpoint = connectFuture.get();
+
+        final HttpRequest request1 = new BasicHttpRequest(Method.GET, createRequestURI(serverEndpoint, "/hello"));
+        request1.setHeader("big-f-header", "1234567890123456789012345678901234567890123456789012345678901234567890" +
+                "1234567890123456789012345678901234567890");
+        final Future<Message<HttpResponse, String>> future1 = streamEndpoint.execute(
+                new BasicRequestProducer(request1, null),
+                new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
+        final Message<HttpResponse, String> result1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+        Assert.assertNotNull(result1);
+        final HttpResponse response1 = result1.getHead();
+        Assert.assertNotNull(response1);
+        Assert.assertEquals(431, response1.getCode());
+        Assert.assertEquals("Maximum header list size exceeded", result1.getBody());
+    }
+
+    @Test
+    public void testHeaderTooLargePost() throws Exception {
+        server.register("/hello", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new SingleLineResponseHandler("Hi there");
+            }
+
+        });
+        final InetSocketAddress serverEndpoint = server.start(H2Config.custom()
+                .setMaxHeaderListSize(100)
+                .build());
+        client.start(
+                new DefaultHttpProcessor(new H2RequestContent(), new H2RequestTargetHost(), new H2RequestConnControl()),
+                H2Config.DEFAULT);
+
+        final Future<ClientSessionEndpoint> connectFuture = client.connect(
+                "localhost", serverEndpoint.getPort(), TIMEOUT);
+        final ClientSessionEndpoint streamEndpoint = connectFuture.get();
+
+        final HttpRequest request1 = new BasicHttpRequest(Method.POST, createRequestURI(serverEndpoint, "/hello"));
+        request1.setHeader("big-f-header", "1234567890123456789012345678901234567890123456789012345678901234567890" +
+                "1234567890123456789012345678901234567890");
+
+        final byte[] b = new byte[2048];
+        for (int i = 0; i < b.length; i++) {
+            b[i] = (byte) ('a' + i % 10);
+        }
+
+        final Future<Message<HttpResponse, String>> future1 = streamEndpoint.execute(
+                new BasicRequestProducer(request1, AsyncEntityProducers.create(b, ContentType.TEXT_PLAIN)),
+                new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
+        final Message<HttpResponse, String> result1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+        Assert.assertNotNull(result1);
+        final HttpResponse response1 = result1.getHead();
+        Assert.assertNotNull(response1);
+        Assert.assertEquals(431, response1.getCode());
+        Assert.assertEquals("Maximum header list size exceeded", result1.getBody());
+    }
+
 }


[httpcomponents-core] 01/02: Enforce H2 SETTINGS_MAX_HEADER_LIST_SIZE limit for HTTP/2 messages

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit 030347d3bdfb5a91a3b21b638668d7a94bc72249
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sun Dec 29 11:59:25 2019 +0100

    Enforce H2 SETTINGS_MAX_HEADER_LIST_SIZE limit for HTTP/2 messages
---
 .../apache/hc/core5/http2/hpack/HPackDecoder.java  | 37 ++++++++++++++---
 .../http2/hpack/HeaderListConstraintException.java | 46 ++++++++++++++++++++++
 .../impl/nio/AbstractH2StreamMultiplexer.java      |  2 +
 .../hc/core5/http2/hpack/TestHPackCoding.java      | 25 ++++++++++++
 4 files changed, 104 insertions(+), 6 deletions(-)

diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackDecoder.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackDecoder.java
index 6c82ccd..62ad3bb 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackDecoder.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackDecoder.java
@@ -59,11 +59,14 @@ public final class HPackDecoder {
     private final CharsetDecoder charsetDecoder;
     private CharBuffer tmpBuf;
     private int maxTableSize;
+    private int maxListSize;
 
     HPackDecoder(final InboundDynamicTable dynamicTable, final CharsetDecoder charsetDecoder) {
         this.dynamicTable = dynamicTable != null ? dynamicTable : new InboundDynamicTable();
         this.contentBuf = new ByteArrayBuffer(256);
         this.charsetDecoder = charsetDecoder;
+        this.maxTableSize = dynamicTable != null ? dynamicTable.getMaxSize() : Integer.MAX_VALUE;
+        this.maxListSize = Integer.MAX_VALUE;
     }
 
     HPackDecoder(final InboundDynamicTable dynamicTable, final Charset charset) {
@@ -221,7 +224,7 @@ public final class HPackDecoder {
         return binaryLen;
     }
 
-    Header decodeLiteralHeader(
+    HPackHeader decodeLiteralHeader(
             final ByteBuffer src,
             final HPackRepresentation representation) throws HPackException, CharacterCodingException {
 
@@ -248,13 +251,13 @@ public final class HPackDecoder {
         if (representation == HPackRepresentation.WITH_INDEXING) {
             this.dynamicTable.add(header);
         }
-        return new BasicHeader(header.getName(), header.getValue(), header.isSensitive());
+        return header;
     }
 
-    Header decodeIndexedHeader(final ByteBuffer src) throws HPackException, CharacterCodingException {
+    HPackHeader decodeIndexedHeader(final ByteBuffer src) throws HPackException {
 
         final int index = decodeInt(src, 7);
-        final Header existing =  this.dynamicTable.getHeader(index);
+        final HPackHeader existing =  this.dynamicTable.getHeader(index);
         if (existing == null) {
             throw new HPackException("Invalid header index");
         }
@@ -262,6 +265,11 @@ public final class HPackDecoder {
     }
 
     public Header decodeHeader(final ByteBuffer src) throws HPackException {
+        final HPackHeader header = decodeHPackHeader(src);
+        return header != null ? new BasicHeader(header.getName(), header.getValue(), header.isSensitive()) : null;
+    }
+
+    HPackHeader decodeHPackHeader(final ByteBuffer src) throws HPackException {
         try {
             while (src.hasRemaining()) {
                 final int b = peekByte(src);
@@ -287,14 +295,22 @@ public final class HPackDecoder {
     }
 
     public List<Header> decodeHeaders(final ByteBuffer src) throws HPackException {
+        final boolean enforceSizeLimit = maxListSize < Integer.MAX_VALUE;
+        int listSize = 0;
 
         final List<Header> list = new ArrayList<>();
         while (src.hasRemaining()) {
-            final Header header = decodeHeader(src);
+            final HPackHeader header = decodeHPackHeader(src);
             if (header == null) {
                 break;
             }
-            list.add(header);
+            if (enforceSizeLimit) {
+                listSize += header.getTotalSize();
+                if (listSize >= maxListSize) {
+                    throw new HeaderListConstraintException("Maximum header list size exceeded");
+                }
+            }
+            list.add(new BasicHeader(header.getName(), header.getValue(), header.isSensitive()));
         }
         return list;
     }
@@ -309,4 +325,13 @@ public final class HPackDecoder {
         this.dynamicTable.setMaxSize(maxTableSize);
     }
 
+    public int getMaxListSize() {
+        return maxListSize;
+    }
+
+    public void setMaxListSize(final int maxListSize) {
+        Args.notNegative(maxListSize, "Max list size");
+        this.maxListSize = maxListSize;
+    }
+
 }
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HeaderListConstraintException.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HeaderListConstraintException.java
new file mode 100644
index 0000000..a3f1f3a
--- /dev/null
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HeaderListConstraintException.java
@@ -0,0 +1,46 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.http2.hpack;
+
+/**
+ * Signals a header list constraint violation.
+ *
+ * @since 5.0
+ */
+public class HeaderListConstraintException extends HPackException {
+
+    /**
+     * Creates a HeaderListConstraintException with the specified detail message.
+     *
+     * @param message The exception detail message
+     */
+    public HeaderListConstraintException(final String message) {
+        super(message);
+    }
+
+}
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java
index 7b02c82..d16df1f 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java
@@ -168,6 +168,7 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
 
         this.hPackDecoder.setMaxTableSize(H2Config.INIT.getHeaderTableSize());
         this.hPackEncoder.setMaxTableSize(H2Config.INIT.getHeaderTableSize());
+        this.hPackDecoder.setMaxListSize(H2Config.INIT.getMaxHeaderListSize());
 
         this.lowMark = H2Config.INIT.getInitialWindowSize() / 2;
         this.streamListener = streamListener;
@@ -1201,6 +1202,7 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
 
     private void applyLocalSettings() throws H2ConnectionException {
         hPackDecoder.setMaxTableSize(localConfig.getHeaderTableSize());
+        hPackDecoder.setMaxListSize(localConfig.getMaxHeaderListSize());
 
         final int delta = localConfig.getInitialWindowSize() - initInputWinSize;
         initInputWinSize = localConfig.getInitialWindowSize();
diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestHPackCoding.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestHPackCoding.java
index 130009a..7d1603a 100644
--- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestHPackCoding.java
+++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestHPackCoding.java
@@ -36,6 +36,7 @@ import java.util.List;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.message.BasicHeader;
 import org.apache.hc.core5.util.ByteArrayBuffer;
+import org.hamcrest.CoreMatchers;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -1028,5 +1029,29 @@ public class TestHPackCoding {
         Assert.assertEquals(0, inboundTable2.dynamicLength());
     }
 
+    @Test(expected = HeaderListConstraintException.class)
+    public void testHeaderSizeLimit() throws Exception {
+
+        final HPackEncoder encoder = new HPackEncoder(StandardCharsets.US_ASCII);
+        final HPackDecoder decoder = new HPackDecoder(StandardCharsets.US_ASCII);
+
+        final ByteArrayBuffer buf = new ByteArrayBuffer(128);
+
+        encoder.encodeHeaders(buf,
+                Arrays.asList(
+                        new BasicHeader("regular-header", "blah"),
+                        new BasicHeader("big-f-header", "12345678901234567890123456789012345678901234567890" +
+                                "123456789012345678901234567890123456789012345678901234567890")),
+                false);
+
+        Assert.assertThat(decoder.decodeHeaders(ByteBuffer.wrap(buf.toByteArray())).size(), CoreMatchers.equalTo(2));
+
+        decoder.setMaxListSize(1000000);
+        Assert.assertThat(decoder.decodeHeaders(ByteBuffer.wrap(buf.toByteArray())).size(), CoreMatchers.equalTo(2));
+
+        decoder.setMaxListSize(200);
+        decoder.decodeHeaders(ByteBuffer.wrap(buf.toByteArray()));
+    }
+
 }