You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ab...@apache.org on 2026/03/13 14:59:36 UTC

(httpcomponents-core) branch master updated: Validate HEADERS priority self-dependency (#652)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new e1ebe30b7 Validate HEADERS priority self-dependency (#652)
e1ebe30b7 is described below

commit e1ebe30b796a52e3b839ddc3a1de79444a9145be
Author: Arturo Bernal <ab...@apache.org>
AuthorDate: Fri Mar 13 14:59:30 2026 +0000

    Validate HEADERS priority self-dependency (#652)
    
    Treat HEADERS with PRIORITY that depends on the same stream as a stream-level PROTOCOL_ERROR
---
 .../impl/nio/AbstractH2StreamMultiplexer.java      |  7 ++-
 .../impl/nio/TestAbstractH2StreamMultiplexer.java  | 52 ++++++++++++++++++++++
 2 files changed, 57 insertions(+), 2 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 11c5c39f7..8e9171b57 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
@@ -1185,8 +1185,11 @@ private void consumeHeaderFrame(final RawFrame frame, final H2Stream stream) thr
             if (payload == null || payload.remaining() < 5) {
                 throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Invalid HEADERS priority payload");
             }
-            payload.getInt();
-            payload.get();
+            final int dependency = payload.getInt() & 0x7fffffff;
+            if (dependency == streamId) {
+                throw new H2StreamResetException(H2Error.PROTOCOL_ERROR, "Stream cannot depend on itself");
+            }
+            payload.get(); // weight
         }
         if (continuation == null) {
             final List<Header> headers = decodeHeaders(payload);
diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java
index e792f0405..6c9e88e4f 100644
--- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java
+++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java
@@ -1965,5 +1965,57 @@ void testInputRstStreamWithInvalidLengthOnUnseenStreamRejected() throws Exceptio
         Assertions.assertEquals(H2Error.FRAME_SIZE_ERROR, H2Error.getByCode(ex.getCode()));
     }
 
+    @Test
+    void testHeadersWithPrioritySelfDependencyIsStreamProtocolError() throws Exception {
+        final H2Config h2Config = H2Config.custom().build();
+
+        final AbstractH2StreamMultiplexer mux = new H2StreamMultiplexerImpl(
+                protocolIOSession,
+                FRAME_FACTORY,
+                StreamIdGenerator.ODD,
+                httpProcessor,
+                CharCodingConfig.DEFAULT,
+                h2Config,
+                h2StreamListener,
+                () -> streamHandler);
+
+        final ByteArrayBuffer headerBuf = new ByteArrayBuffer(128);
+        final HPackEncoder encoder = new HPackEncoder(
+                h2Config.getHeaderTableSize(),
+                CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT));
+
+        final List<Header> headers = Arrays.asList(
+                new BasicHeader(":method", "GET"),
+                new BasicHeader(":scheme", "https"),
+                new BasicHeader(":path", "/"),
+                new BasicHeader(":authority", "example.test"));
+
+        encoder.encodeHeaders(headerBuf, headers, h2Config.isCompressionEnabled());
+
+        final ByteBuffer payload = ByteBuffer.allocate(5 + headerBuf.length());
+        payload.putInt(0x80000002); // exclusive bit set, dependency = stream 2 (self-dependency)
+        payload.put((byte) 16);     // weight
+        payload.put(headerBuf.array(), 0, headerBuf.length());
+        payload.flip();
+
+        final RawFrame headersFrame = new RawFrame(
+                FrameType.HEADERS.getValue(),
+                FrameFlag.PRIORITY.getValue() | FrameFlag.END_HEADERS.getValue(),
+                2,
+                payload);
+
+        Assertions.assertDoesNotThrow(() -> mux.onInput(ByteBuffer.wrap(encodeFrame(headersFrame))));
+
+        Mockito.verify(streamHandler).failed(exceptionCaptor.capture());
+        final Exception cause = exceptionCaptor.getValue();
+        Assertions.assertInstanceOf(H2StreamResetException.class, cause);
+        Assertions.assertEquals(
+                H2Error.PROTOCOL_ERROR,
+                H2Error.getByCode(((H2StreamResetException) cause).getCode()));
+
+        Mockito.verify(streamHandler, Mockito.never())
+                .consumeHeader(ArgumentMatchers.anyList(), ArgumentMatchers.anyBoolean());
+    }
+
 
 }
\ No newline at end of file