diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttpProtocolNegotiationStarter.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttpProtocolNegotiationStarter.java index 4ba1562c4..178fb9a6a 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttpProtocolNegotiationStarter.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttpProtocolNegotiationStarter.java @@ -94,8 +94,11 @@ public HttpConnectionEventHandler createHandler(final ProtocolIOSession ioSessio ioSession.registerProtocol(ApplicationProtocol.HTTP_2.id, new ClientH2UpgradeHandler(http2StreamHandlerFactory, exceptionCallback)); switch (endpointPolicy) { - case FORCE_HTTP_2: - return new ClientH2PrefaceHandler(ioSession, http2StreamHandlerFactory, false, exceptionCallback); + case FORCE_HTTP_2: { + // In forced HTTP/2 mode, require ALPN negotiation on TLS sessions. + final boolean strictAlpn = ioSession.getTlsDetails() != null; + return new ClientH2PrefaceHandler(ioSession, http2StreamHandlerFactory, strictAlpn, exceptionCallback); + } case FORCE_HTTP_1: return new ClientHttp1IOEventHandler(http1StreamHandlerFactory.create(ioSession)); default: diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestClientHttpProtocolNegotiationStarter.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestClientHttpProtocolNegotiationStarter.java index c0c6101bc..6cb911215 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestClientHttpProtocolNegotiationStarter.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestClientHttpProtocolNegotiationStarter.java @@ -40,6 +40,7 @@ import org.apache.hc.core5.http2.ssl.ApplicationProtocol; import org.apache.hc.core5.reactor.EndpointParameters; import org.apache.hc.core5.reactor.ProtocolIOSession; +import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -116,4 +117,38 @@ void negotiateUsesProtocolNegotiator() { Assertions.assertTrue(handler instanceof HttpProtocolNegotiator); } + @Test + void forceHttp2TlsMissingAlpnFailsStrictHandshake() throws Exception { + final ClientHttpProtocolNegotiationStarter starter = new ClientHttpProtocolNegotiationStarter( + http1Factory(), + http2Factory(), + HttpVersionPolicy.FORCE_HTTP_2, + null, + null, + null); + + final ProtocolIOSession ioSession = Mockito.mock(ProtocolIOSession.class); + Mockito.when(ioSession.getTlsDetails()).thenReturn(new TlsDetails(null, null)); + final ClientH2PrefaceHandler handler = (ClientH2PrefaceHandler) starter.createHandler(ioSession, null); + + Assertions.assertThrows(ProtocolNegotiationException.class, () -> handler.connected(ioSession)); + } + + @Test + void forceHttp2TlsUnexpectedAlpnFailsHandshake() throws Exception { + final ClientHttpProtocolNegotiationStarter starter = new ClientHttpProtocolNegotiationStarter( + http1Factory(), + http2Factory(), + HttpVersionPolicy.FORCE_HTTP_2, + null, + null, + null); + + final ProtocolIOSession ioSession = Mockito.mock(ProtocolIOSession.class); + Mockito.when(ioSession.getTlsDetails()).thenReturn(new TlsDetails(null, ApplicationProtocol.HTTP_1_1.id)); + final ClientH2PrefaceHandler handler = (ClientH2PrefaceHandler) starter.createHandler(ioSession, null); + + Assertions.assertThrows(ProtocolNegotiationException.class, () -> handler.connected(ioSession)); + } + }