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 2024/10/11 13:32:12 UTC

(httpcomponents-core) branch h2_header_field_validation created (now 46fdb4646)

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

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


      at 46fdb4646 Bug fix: move H2 illegal message header check from message converters to protocol interceptors

This branch includes the following new commits:

     new 46fdb4646 Bug fix: move H2 illegal message header check from message converters to protocol interceptors

The 1 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.



(httpcomponents-core) 01/01: Bug fix: move H2 illegal message header check from message converters to protocol interceptors

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

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

commit 46fdb46465cac4ddc56e900590130b5cd415c6b7
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Thu Oct 10 15:57:28 2024 +0200

    Bug fix: move H2 illegal message header check from message converters to protocol interceptors
---
 .../http2/impl/DefaultH2RequestConverter.java      |  17 ---
 .../http2/impl/DefaultH2ResponseConverter.java     |   9 --
 .../apache/hc/core5/http2/impl/H2Processors.java   |   7 ++
 .../core5/http2/protocol/H2RequestConformance.java |  95 +++++++++++++++++
 .../http2/protocol/H2ResponseConformance.java      |  85 +++++++++++++++
 .../http2/impl/TestDefaultH2RequestConverter.java  |  84 ---------------
 .../http2/impl/TestDefaultH2ResponseConverter.java |  58 ----------
 .../core5/http2/protocol/TestH2Interceptors.java   | 117 +++++++++++++++++++++
 .../compatibility/http2/AbstractHttp2CompatIT.java |   9 +-
 .../compatibility/http2/ApacheHttpDCompatIT.java   |  28 +++--
 .../testing/compatibility/http2/NginxCompatIT.java |   5 +
 11 files changed, 334 insertions(+), 180 deletions(-)

diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/DefaultH2RequestConverter.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/DefaultH2RequestConverter.java
index 8a1287775..59066d628 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/DefaultH2RequestConverter.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/DefaultH2RequestConverter.java
@@ -34,7 +34,6 @@ import java.util.List;
 
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpException;
-import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpVersion;
 import org.apache.hc.core5.http.Method;
@@ -107,14 +106,6 @@ public final class DefaultH2RequestConverter implements H2MessageConverter<HttpR
                         throw new ProtocolException("Unsupported request header '%s'", name);
                 }
             } else {
-                if (name.equalsIgnoreCase(HttpHeaders.CONNECTION) || name.equalsIgnoreCase(HttpHeaders.KEEP_ALIVE)
-                    || name.equalsIgnoreCase(HttpHeaders.PROXY_CONNECTION) || name.equalsIgnoreCase(HttpHeaders.TRANSFER_ENCODING)
-                    || name.equalsIgnoreCase(HttpHeaders.HOST) || name.equalsIgnoreCase(HttpHeaders.UPGRADE)) {
-                    throw new ProtocolException("Header '%s: %s' is illegal for HTTP/2 messages", header.getName(), header.getValue());
-                }
-                if (name.equalsIgnoreCase(HttpHeaders.TE) && !value.equalsIgnoreCase("trailers")) {
-                    throw new ProtocolException("Header '%s: %s' is illegal for HTTP/2 messages", header.getName(), header.getValue());
-                }
                 messageHeaders.add(header);
             }
         }
@@ -196,14 +187,6 @@ public final class DefaultH2RequestConverter implements H2MessageConverter<HttpR
             if (name.startsWith(":")) {
                 throw new ProtocolException("Header name '%s' is invalid", name);
             }
-            if (name.equalsIgnoreCase(HttpHeaders.CONNECTION) || name.equalsIgnoreCase(HttpHeaders.KEEP_ALIVE)
-                || name.equalsIgnoreCase(HttpHeaders.PROXY_CONNECTION) || name.equalsIgnoreCase(HttpHeaders.TRANSFER_ENCODING)
-                || name.equalsIgnoreCase(HttpHeaders.HOST) || name.equalsIgnoreCase(HttpHeaders.UPGRADE)) {
-                throw new ProtocolException("Header '%s: %s' is illegal for HTTP/2 messages", header.getName(), header.getValue());
-            }
-            if (name.equalsIgnoreCase(HttpHeaders.TE) && !value.equalsIgnoreCase("trailers")) {
-                throw new ProtocolException("Header '%s: %s' is illegal for HTTP/2 messages", header.getName(), header.getValue());
-            }
             headers.add(new BasicHeader(TextUtils.toLowerCase(name), value));
         }
 
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/DefaultH2ResponseConverter.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/DefaultH2ResponseConverter.java
index 0b860be8b..48b2c3f5d 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/DefaultH2ResponseConverter.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/DefaultH2ResponseConverter.java
@@ -33,7 +33,6 @@ import java.util.List;
 
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpException;
-import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpVersion;
 import org.apache.hc.core5.http.ProtocolException;
@@ -82,10 +81,6 @@ public class DefaultH2ResponseConverter implements H2MessageConverter<HttpRespon
                     throw new ProtocolException("Unsupported response header '%s'", name);
                 }
             } else {
-                if (name.equalsIgnoreCase(HttpHeaders.CONNECTION) || name.equalsIgnoreCase(HttpHeaders.KEEP_ALIVE)
-                    || name.equalsIgnoreCase(HttpHeaders.TRANSFER_ENCODING) || name.equalsIgnoreCase(HttpHeaders.UPGRADE)) {
-                    throw new ProtocolException("Header '%s: %s' is illegal for HTTP/2 messages", header.getName(), header.getValue());
-                }
                 messageHeaders.add(header);
             }
 
@@ -124,10 +119,6 @@ public class DefaultH2ResponseConverter implements H2MessageConverter<HttpRespon
             if (name.startsWith(":")) {
                 throw new ProtocolException("Header name '%s' is invalid", name);
             }
-            if (name.equalsIgnoreCase(HttpHeaders.CONNECTION) || name.equalsIgnoreCase(HttpHeaders.KEEP_ALIVE)
-                || name.equalsIgnoreCase(HttpHeaders.TRANSFER_ENCODING) || name.equalsIgnoreCase(HttpHeaders.UPGRADE)) {
-                throw new ProtocolException("Header '%s: %s' is illegal for HTTP/2 messages", header.getName(), header.getValue());
-            }
             headers.add(new BasicHeader(TextUtils.toLowerCase(name), value));
         }
         return headers;
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/H2Processors.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/H2Processors.java
index 5a82d83a0..b6cf55270 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/H2Processors.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/H2Processors.java
@@ -35,10 +35,12 @@ import org.apache.hc.core5.http.protocol.RequestUserAgent;
 import org.apache.hc.core5.http.protocol.ResponseConformance;
 import org.apache.hc.core5.http.protocol.ResponseDate;
 import org.apache.hc.core5.http.protocol.ResponseServer;
+import org.apache.hc.core5.http2.protocol.H2RequestConformance;
 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.http2.protocol.H2RequestValidateHost;
+import org.apache.hc.core5.http2.protocol.H2ResponseConformance;
 import org.apache.hc.core5.http2.protocol.H2ResponseConnControl;
 import org.apache.hc.core5.http2.protocol.H2ResponseContent;
 import org.apache.hc.core5.util.TextUtils;
@@ -55,6 +57,7 @@ public final class H2Processors {
         return HttpProcessorBuilder.create()
                 .addAll(
                         ResponseConformance.INSTANCE,
+                        H2ResponseConformance.INSTANCE,
                         ResponseDate.INSTANCE,
                         new ResponseServer(!TextUtils.isBlank(serverInfo) ? serverInfo :
                                 VersionInfo.getSoftwareInfo(SOFTWARE, "org.apache.hc.core5", H2Processors.class)),
@@ -62,6 +65,7 @@ public final class H2Processors {
                         H2ResponseConnControl.INSTANCE)
                 .addAll(
                         H2RequestValidateHost.INSTANCE,
+                        H2RequestConformance.INSTANCE,
                         RequestConformance.INSTANCE);
     }
 
@@ -76,6 +80,9 @@ public final class H2Processors {
     public static HttpProcessorBuilder customClient(final String agentInfo) {
         return HttpProcessorBuilder.create()
                 .addAll(
+                        H2ResponseConformance.INSTANCE)
+                .addAll(
+                        H2RequestConformance.INSTANCE,
                         H2RequestTargetHost.INSTANCE,
                         H2RequestContent.INSTANCE,
                         H2RequestConnControl.INSTANCE,
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestConformance.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestConformance.java
new file mode 100644
index 000000000..a2d010b14
--- /dev/null
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestConformance.java
@@ -0,0 +1,95 @@
+/*
+ * ====================================================================
+ * 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.protocol;
+
+import java.io.IOException;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.Args;
+
+/**
+ * This request interceptor is responsible for execution of the protocol conformance
+ * checks on incoming or outgoing HTTP/2 request messages.
+ *
+ * @since 5.4
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+public class H2RequestConformance implements HttpRequestInterceptor {
+
+    public static final H2RequestConformance INSTANCE = new H2RequestConformance();
+
+    private final String[] illegalHeaderNames;
+
+    @Internal
+    public H2RequestConformance(final String... illegalHeaderNames) {
+        super();
+        this.illegalHeaderNames = illegalHeaderNames;
+    }
+
+    public H2RequestConformance() {
+        this(
+                HttpHeaders.CONNECTION,
+                HttpHeaders.KEEP_ALIVE,
+                HttpHeaders.PROXY_CONNECTION,
+                HttpHeaders.TRANSFER_ENCODING,
+                HttpHeaders.HOST,
+                HttpHeaders.UPGRADE,
+                HttpHeaders.TE);
+    }
+
+    @Override
+    public void process(final HttpRequest request, final EntityDetails entity, final HttpContext localContext)
+            throws HttpException, IOException {
+        Args.notNull(request, "HTTP request");
+        for (int i = 0; i < illegalHeaderNames.length; i++) {
+            final String headerName = illegalHeaderNames[i];
+            final Header header = request.getFirstHeader(headerName);
+            if (header != null) {
+                if (headerName.equalsIgnoreCase(HttpHeaders.TE)) {
+                    final String value = header.getValue();
+                    if (!"trailers".equalsIgnoreCase(value)) {
+                        throw new ProtocolException("Header '%s: %s' is illegal for HTTP/2 messages", HttpHeaders.TE, value);
+                    }
+                } else {
+                    throw new ProtocolException("Header '%s' is illegal for HTTP/2 messages", headerName);
+                }
+            }
+        }
+    }
+
+}
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2ResponseConformance.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2ResponseConformance.java
new file mode 100644
index 000000000..61e9dc3bb
--- /dev/null
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2ResponseConformance.java
@@ -0,0 +1,85 @@
+/*
+ * ====================================================================
+ * 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.protocol;
+
+import java.io.IOException;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpResponseInterceptor;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.Args;
+
+/**
+ * This response interceptor is responsible for making the protocol conformance checks
+ * of incoming or outgoing HTTP/2 response messages.
+ *
+ * @since 5.4
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+public class H2ResponseConformance implements HttpResponseInterceptor {
+
+    public static final H2ResponseConformance INSTANCE = new H2ResponseConformance();
+
+    private final String[] illegalHeaderNames;
+
+    @Internal
+    public H2ResponseConformance(final String... illegalHeaderNames) {
+        super();
+        this.illegalHeaderNames = illegalHeaderNames;
+    }
+
+    public H2ResponseConformance() {
+        this(
+                HttpHeaders.CONNECTION,
+                HttpHeaders.KEEP_ALIVE,
+                HttpHeaders.TRANSFER_ENCODING,
+                HttpHeaders.UPGRADE);
+    }
+
+    @Override
+    public void process(final HttpResponse response, final EntityDetails entity, final HttpContext context)
+            throws HttpException, IOException {
+        Args.notNull(response, "HTTP response");
+        for (int i = 0; i < illegalHeaderNames.length; i++) {
+            final String headerName = illegalHeaderNames[i];
+            final Header header = response.getFirstHeader(headerName);
+            if (header != null) {
+                throw new ProtocolException("Header '%s' is illegal for HTTP/2 messages", headerName);
+            }
+        }
+    }
+
+}
diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2RequestConverter.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2RequestConverter.java
index f985dcf18..3c1a03fb7 100644
--- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2RequestConverter.java
+++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2RequestConverter.java
@@ -80,20 +80,6 @@ class TestDefaultH2RequestConverter {
                 "Header name ':Path' is invalid (header name contains uppercase characters)");
     }
 
-    @Test
-    void testConvertFromFieldsConnectionHeader() {
-        final List<Header> headers = Arrays.asList(
-                new BasicHeader(":method", "GET"),
-                new BasicHeader(":scheme", "http"),
-                new BasicHeader(":authority", "www.example.com"),
-                new BasicHeader(":path", "/"),
-                new BasicHeader("connection", "keep-alive"));
-
-        final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
-        Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
-                "Header 'connection: keep-alive' is illegal for HTTP/2 messages");
-    }
-
     @Test
     void testConvertFromFieldsPseudoHeaderSequence() {
         final List<Header> headers = Arrays.asList(
@@ -346,76 +332,6 @@ class TestDefaultH2RequestConverter {
                 "CONNECT request path must be null");
     }
 
-    @Test
-    void testConvertFromMessageConnectionHeader() {
-        final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
-        request.addHeader("Connection", "Keep-Alive");
-
-        final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
-        Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
-                "Header 'Connection: Keep-Alive' is illegal for HTTP/2 messages");
-    }
-
-    @Test
-    void testConvertFromFieldsKeepAliveHeader() {
-        final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
-        request.addHeader("Keep-Alive", "timeout=5, max=1000");
-
-        final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
-        Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
-                "Header 'Keep-Alive: timeout=5, max=1000' is illegal for HTTP/2 messages");
-    }
-
-    @Test
-    void testConvertFromFieldsProxyConnectionHeader() {
-        final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
-        request.addHeader("Proxy-Connection", "keep-alive");
-
-        final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
-        Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
-                "Header 'Proxy-Connection: Keep-Alive' is illegal for HTTP/2 messages");
-    }
-
-    @Test
-    void testConvertFromFieldsTransferEncodingHeader() {
-        final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
-        request.addHeader("Transfer-Encoding", "gzip");
-
-        final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
-        Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
-                "Header 'Transfer-Encoding: gzip' is illegal for HTTP/2 messages");
-    }
-
-    @Test
-    void testConvertFromFieldsHostHeader() {
-        final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
-        request.addHeader("Host", "host");
-
-        final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
-        Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
-                "Header 'Host: host' is illegal for HTTP/2 messages");
-    }
-
-    @Test
-    void testConvertFromFieldsUpgradeHeader() {
-        final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
-        request.addHeader("Upgrade", "example/1, foo/2");
-
-        final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
-        Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
-                "Header 'Upgrade: example/1, foo/2' is illegal for HTTP/2 messages");
-    }
-
-    @Test
-    void testConvertFromFieldsTEHeader() {
-        final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
-        request.addHeader("TE", "gzip");
-
-        final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
-        Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
-                "Header 'TE: gzip' is illegal for HTTP/2 messages");
-    }
-
     @Test
     void testConvertFromFieldsTETrailerHeader() throws Exception {
 
diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2ResponseConverter.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2ResponseConverter.java
index 2be82e2c5..97b774cf6 100644
--- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2ResponseConverter.java
+++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2ResponseConverter.java
@@ -83,54 +83,6 @@ class TestDefaultH2ResponseConverter {
         Assertions.assertThrows(HttpException.class, () -> converter.convert(headers));
     }
 
-    @Test
-    void testConvertFromFieldsConnectionHeader() {
-        final List<Header> headers = Arrays.asList(
-                new BasicHeader(":status", "200"),
-                new BasicHeader("location", "http://www.example.com/"),
-                new BasicHeader("connection", "keep-alive"));
-
-        final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter();
-        Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
-                "Header 'connection: keep-alive' is illegal for HTTP/2 messages");
-    }
-
-    @Test
-    void testConvertFromFieldsKeepAliveHeader() {
-        final List<Header> headers = Arrays.asList(
-            new BasicHeader(":status", "200"),
-            new BasicHeader("location", "http://www.example.com/"),
-            new BasicHeader("keep-alive", "timeout=5, max=1000"));
-
-        final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter();
-        Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
-                "Header 'keep-alive: timeout=5, max=1000' is illegal for HTTP/2 messages");
-    }
-
-    @Test
-    void testConvertFromFieldsTransferEncodingHeader() {
-        final List<Header> headers = Arrays.asList(
-            new BasicHeader(":status", "200"),
-            new BasicHeader("location", "http://www.example.com/"),
-            new BasicHeader("transfer-encoding", "gzip"));
-
-        final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter();
-        Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
-                "Header 'transfer-encoding: gzip' is illegal for HTTP/2 messages");
-    }
-
-    @Test
-    void testConvertFromFieldsUpgradeHeader() {
-        final List<Header> headers = Arrays.asList(
-            new BasicHeader(":status", "200"),
-            new BasicHeader("location", "http://www.example.com/"),
-            new BasicHeader("upgrade", "example/1, foo/2"));
-
-        final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter();
-        Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
-                "Header 'upgrade: example/1, foo/2' is illegal for HTTP/2 messages");
-    }
-
     @Test
     void testConvertFromFieldsMissingStatus() {
         final List<Header> headers = Arrays.asList(
@@ -197,16 +149,6 @@ class TestDefaultH2ResponseConverter {
                 "Response status 99 is invalid");
     }
 
-    @Test
-    void testConvertFromMessageConnectionHeader() {
-        final HttpResponse response = new BasicHttpResponse(200);
-        response.addHeader("Connection", "Keep-Alive");
-
-        final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter();
-        Assertions.assertThrows(HttpException.class, () -> converter.convert(response),
-                "Header 'Connection: Keep-Alive' is illegal for HTTP/2 messages");
-    }
-
     @Test
     void testConvertFromMessageInvalidHeader() {
         final HttpResponse response = new BasicHttpResponse(200);
diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/protocol/TestH2Interceptors.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/protocol/TestH2Interceptors.java
index 17ac94817..ef2e84aaa 100644
--- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/protocol/TestH2Interceptors.java
+++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/protocol/TestH2Interceptors.java
@@ -30,8 +30,13 @@ package org.apache.hc.core5.http2.protocol;
 import java.nio.charset.StandardCharsets;
 
 import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpRequestInterceptor;
+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;
@@ -39,6 +44,8 @@ import org.apache.hc.core5.http.io.entity.HttpEntities;
 import org.apache.hc.core5.http.io.entity.StringEntity;
 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
 import org.apache.hc.core5.http.message.BasicHeader;
+import org.apache.hc.core5.http.message.BasicHttpRequest;
+import org.apache.hc.core5.http.message.BasicHttpResponse;
 import org.apache.hc.core5.http.protocol.HttpCoreContext;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -141,4 +148,114 @@ class TestH2Interceptors {
         Assertions.assertNotNull(header1);
     }
 
+    @Test
+    void testH2RequestConformanceConnectionHeader() {
+        final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
+        request.addHeader("Connection", "Keep-Alive");
+
+        final H2RequestConformance interceptor = new H2RequestConformance();
+        Assertions.assertThrows(HttpException.class, () -> interceptor.process(request, null, context),
+                "Header 'Connection: Keep-Alive' is illegal for HTTP/2 messages");
+    }
+
+    @Test
+    void testH2RequestConformanceKeepAliveHeader() {
+        final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
+        request.addHeader("Keep-Alive", "timeout=5, max=1000");
+
+        final H2RequestConformance interceptor = new H2RequestConformance();
+        Assertions.assertThrows(HttpException.class, () -> interceptor.process(request, null, context),
+                "Header 'Keep-Alive: timeout=5, max=1000' is illegal for HTTP/2 messages");
+    }
+
+    @Test
+    void testH2RequestConformanceProxyConnectionHeader() {
+        final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
+        request.addHeader("Proxy-Connection", "keep-alive");
+
+        final H2RequestConformance interceptor = new H2RequestConformance();
+        Assertions.assertThrows(HttpException.class, () -> interceptor.process(request, null, context),
+                "Header 'Proxy-Connection: Keep-Alive' is illegal for HTTP/2 messages");
+    }
+
+    @Test
+    void testH2RequestConformanceTransferEncodingHeader() {
+        final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
+        request.addHeader("Transfer-Encoding", "gzip");
+
+        final H2RequestConformance interceptor = new H2RequestConformance();
+        Assertions.assertThrows(HttpException.class, () -> interceptor.process(request, null, context),
+                "Header 'Transfer-Encoding: gzip' is illegal for HTTP/2 messages");
+    }
+
+    @Test
+    void testH2RequestConformanceHostHeader() {
+        final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
+        request.addHeader("Host", "host");
+
+        final H2RequestConformance interceptor = new H2RequestConformance();
+        Assertions.assertThrows(HttpException.class, () -> interceptor.process(request, null, context),
+                "Header 'Host: host' is illegal for HTTP/2 messages");
+    }
+
+    @Test
+    void testH2RequestConformanceUpgradeHeader() {
+        final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
+        request.addHeader("Upgrade", "example/1, foo/2");
+
+        final H2RequestConformance interceptor = new H2RequestConformance();
+        Assertions.assertThrows(HttpException.class, () -> interceptor.process(request, null, context),
+                "Header 'Upgrade: example/1, foo/2' is illegal for HTTP/2 messages");
+    }
+
+    @Test
+    void testH2RequestConformanceTEHeader() {
+        final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
+        request.addHeader("TE", "gzip");
+
+        final H2RequestConformance interceptor = new H2RequestConformance();
+        Assertions.assertThrows(HttpException.class, () -> interceptor.process(request, null, context),
+                "Header 'TE: gzip' is illegal for HTTP/2 messages");
+    }
+
+    @Test
+    void testH2ResponseConformanceConnectionHeader() {
+        final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
+        response.addHeader("Connection", "Keep-Alive");
+
+        final H2ResponseConformance interceptor = new H2ResponseConformance();
+        Assertions.assertThrows(HttpException.class, () -> interceptor.process(response, null, context),
+                "Header 'connection: keep-alive' is illegal for HTTP/2 messages");
+    }
+
+    @Test
+    void testH2ResponseConformanceKeepAliveHeader() {
+        final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
+        response.addHeader("keep-alive", "timeout=5, max=1000");
+
+        final H2ResponseConformance interceptor = new H2ResponseConformance();
+        Assertions.assertThrows(HttpException.class, () -> interceptor.process(response, null, context),
+                "Header 'keep-alive: timeout=5, max=1000' is illegal for HTTP/2 messages");
+    }
+
+    @Test
+    void testH2ResponseConformanceTransferEncodingHeader() {
+        final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
+        response.addHeader("transfer-encoding", "gzip");
+
+        final H2ResponseConformance interceptor = new H2ResponseConformance();
+        Assertions.assertThrows(HttpException.class, () -> interceptor.process(response, null, context),
+                "Header 'transfer-encoding: gzip' is illegal for HTTP/2 messages");
+    }
+
+    @Test
+    void testH2ResponseConformanceUpgradeHeader() {
+        final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
+        response.addHeader("upgrade", "example/1, foo/2");
+
+        final H2ResponseConformance interceptor = new H2ResponseConformance();
+        Assertions.assertThrows(HttpException.class, () -> interceptor.process(response, null, context),
+                "Header 'upgrade: example/1, foo/2' is illegal for HTTP/2 messages");
+    }
+
 }
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/AbstractHttp2CompatIT.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/AbstractHttp2CompatIT.java
index 6b42f6611..a2c80620f 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/AbstractHttp2CompatIT.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/AbstractHttp2CompatIT.java
@@ -75,9 +75,11 @@ abstract class AbstractHttp2CompatIT {
 
     HttpAsyncRequester client;
 
+    abstract void configure(H2RequesterBootstrap bootstrap);
+
     @BeforeEach
     void start() throws Exception {
-        client = H2RequesterBootstrap.bootstrap()
+        final H2RequesterBootstrap bootstrap = H2RequesterBootstrap.bootstrap()
                 .setIOReactorConfig(IOReactorConfig.custom()
                         .setSoTimeout(TIMEOUT)
                         .build())
@@ -90,8 +92,9 @@ abstract class AbstractHttp2CompatIT {
                 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
                 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
                 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
-                .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE)
-                .create();
+                .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE);
+        configure(bootstrap);
+        client = bootstrap.create();
         client.start();
     }
 
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/ApacheHttpDCompatIT.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/ApacheHttpDCompatIT.java
index 8ee2cd5fb..815fd0bbb 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/ApacheHttpDCompatIT.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/ApacheHttpDCompatIT.java
@@ -27,10 +27,12 @@
 
 package org.apache.hc.core5.testing.compatibility.http2;
 
+import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http2.impl.H2Processors;
+import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
+import org.apache.hc.core5.http2.protocol.H2RequestConformance;
 import org.apache.hc.core5.testing.compatibility.ContainerImages;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
 import org.testcontainers.containers.GenericContainer;
 import org.testcontainers.junit.jupiter.Container;
 import org.testcontainers.junit.jupiter.Testcontainers;
@@ -41,17 +43,25 @@ class ApacheHttpDCompatIT extends AbstractHttp2CompatIT {
     @Container
     static final GenericContainer<?> CONTAINER = ContainerImages.apacheHttpD();
 
+    @Override
+    void configure(final H2RequesterBootstrap bootstrap) {
+        bootstrap.setHttpProcessor(H2Processors.customClient(null)
+                .addFirst(new H2RequestConformance(
+                        HttpHeaders.CONNECTION,
+                        HttpHeaders.KEEP_ALIVE,
+                        HttpHeaders.PROXY_CONNECTION,
+                        HttpHeaders.TRANSFER_ENCODING,
+// For some reason Apache HTTPD includes Host header in HTTP/2 promise messages
+// though it should not
+//                        HttpHeaders.HOST,
+                        HttpHeaders.UPGRADE))
+                .build());
+    }
+
     HttpHost targetHost() {
         return new HttpHost("http",
                 CONTAINER.getHost(),
                 CONTAINER.getMappedPort(ContainerImages.HTTP_PORT));
     }
 
-    @Override
-    @Test
-    @Disabled
-    void test_request_execution_with_push() throws Exception {
-        super.test_request_execution_with_push();
-    }
-
 }
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/NginxCompatIT.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/NginxCompatIT.java
index f6481d226..93de4cd71 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/NginxCompatIT.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/NginxCompatIT.java
@@ -28,6 +28,7 @@
 package org.apache.hc.core5.testing.compatibility.http2;
 
 import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
 import org.apache.hc.core5.testing.compatibility.ContainerImages;
 import org.testcontainers.containers.GenericContainer;
 import org.testcontainers.junit.jupiter.Container;
@@ -39,6 +40,10 @@ class NginxCompatIT extends AbstractHttp2CompatIT {
     @Container
     static final GenericContainer<?> CONTAINER = ContainerImages.ngnix();
 
+    @Override
+    void configure(final H2RequesterBootstrap bootstrap) {
+    }
+
     HttpHost targetHost() {
         return new HttpHost("http",
                 CONTAINER.getHost(),