diff --git a/common/httpx/httpx.go b/common/httpx/httpx.go index 039f4c4c..85cda916 100644 --- a/common/httpx/httpx.go +++ b/common/httpx/httpx.go @@ -183,6 +183,15 @@ func New(options *Options) (*HTTPX, error) { CheckRedirect: redirectFunc, }, retryablehttpOptions) + // When HTTP/1.1-only mode is enforced, prevent retryablehttp-go's HTTP/2 fallback + // from bypassing the protocol restriction. retryablehttp-go falls back to HTTPClient2 + // (an HTTP/2-capable client) when it encounters a "malformed HTTP version" error from + // a server that speaks HTTP/2. Replacing HTTPClient2 with the HTTP/1.1-only HTTPClient + // ensures the -pr http11 flag is honoured end-to-end. See: #2240 + if httpx.Options.Protocol == "http11" { + httpx.client.HTTPClient2 = httpx.client.HTTPClient + } + transport2 := &http2.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, diff --git a/common/httpx/httpx_test.go b/common/httpx/httpx_test.go index 7da6ad12..153b7dd3 100644 --- a/common/httpx/httpx_test.go +++ b/common/httpx/httpx_test.go @@ -2,12 +2,41 @@ package httpx import ( "net/http" + "os" "testing" "github.com/projectdiscovery/retryablehttp-go" "github.com/stretchr/testify/require" ) +// TestHTTP11ProtocolEnforcement verifies that -pr http11 prevents the retryablehttp-go +// HTTP/2 fallback from bypassing the HTTP/1.1-only restriction (#2240). +func TestHTTP11ProtocolEnforcement(t *testing.T) { + t.Run("http11 mode disables HTTPClient2 fallback", func(t *testing.T) { + t.Setenv("GODEBUG", os.Getenv("GODEBUG")) + opts := DefaultOptions + opts.Protocol = "http11" + ht, err := New(&opts) + require.Nil(t, err) + + // When Protocol == "http11", HTTPClient2 must point to the same underlying + // client as HTTPClient so the retryablehttp-go fallback is neutralised. + require.Same(t, ht.client.HTTPClient, ht.client.HTTPClient2, + "HTTPClient2 must equal HTTPClient in http11 mode to prevent HTTP/2 fallback") + }) + + t.Run("default mode keeps distinct HTTPClient2", func(t *testing.T) { + t.Setenv("GODEBUG", os.Getenv("GODEBUG")) + ht, err := New(&DefaultOptions) + require.Nil(t, err) + + // Without an explicit http11 restriction the two clients must differ + // (HTTPClient2 is the native HTTP/2 client used for protocol detection). + require.NotSame(t, ht.client.HTTPClient, ht.client.HTTPClient2, + "HTTPClient2 must be distinct from HTTPClient in default mode") + }) +} + func TestDo(t *testing.T) { ht, err := New(&DefaultOptions) require.Nil(t, err)