Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions FIX_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Fix for Issue #2240: -pr http11 Flag Ignored

## Problem Statement

When using httpx with the `-pr http11` flag to enforce HTTP/1.1-only communication, the flag is being ignored due to automatic HTTP/2 fallback in the retryablehttp-go library.

### Root Cause Analysis

1. **httpx Configuration (Lines 156-160 in common/httpx/httpx.go):**
```go
if httpx.Options.Protocol == "http11" {
// disable http2
_ = os.Setenv("GODEBUG", "http2client=0")
transport.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{}
}
```
httpx correctly disables HTTP/2 in the main HTTP client.

2. **retryablehttp-go Fallback (Lines 65-68 in retryablehttp-go/do.go):**
```go
// if err is equal to missing minor protocol version retry with http/2
if err != nil && stringsutil.ContainsAny(err.Error(), "net/http: HTTP/1.x transport connection broken: malformed HTTP version \"HTTP/2\"", "net/http: HTTP/1.x transport connection broken: malformed HTTP response") {
resp, err = c.HTTPClient2.Do(req.Request)
checkOK, checkErr = c.CheckRetry(req.Context(), resp, err)
}
```
retryablehttp-go automatically falls back to HTTP/2 on certain errors, bypassing httpx's HTTP/1.1-only configuration.

## Solution

The fix involves two components:

### Component 1: retryablehttp-go (PR #521)
Add a `DisableHTTP2` option to prevent automatic HTTP/2 fallback:

**Changes to client.go:**
- Add `DisableHTTP2 bool` field to `Options` struct
- Skip creating `HTTPClient2` when `DisableHTTP2 = true`

**Changes to do.go:**
- Check `!c.options.DisableHTTP2 && c.HTTPClient2 != nil` before HTTP/2 fallback
- Handle nil `HTTPClient2` safely in `closeIdleConnections()`

### Component 2: httpx (This PR)
Set the `DisableHTTP2` option when `-pr http11` is specified:

**Change to common/httpx/httpx.go (after line 79):**
```go
var retryablehttpOptions = retryablehttp.DefaultOptionsSpraying
retryablehttpOptions.Timeout = httpx.Options.Timeout
retryablehttpOptions.RetryMax = httpx.Options.RetryMax
retryablehttpOptions.Trace = options.Trace
// ADD THESE LINES:
// Disable HTTP/2 fallback when http11 protocol is explicitly requested
if httpx.Options.Protocol == "http11" {
retryablehttpOptions.DisableHTTP2 = true
}
```

## Implementation

### Manual Code Change Required

Due to the limitations of automated file modification, the following manual change is required:

**File:** `common/httpx/httpx.go`
**Location:** After line 79 (after `retryablehttpOptions.Trace = options.Trace`)

**Insert:**
```go
// Disable HTTP/2 fallback when http11 protocol is explicitly requested
// This prevents retryablehttp from automatically retrying with HTTP/2
// when HTTP/1.x errors occur, honoring the user's -pr http11 flag
if httpx.Options.Protocol == "http11" {
retryablehttpOptions.DisableHTTP2 = true
}
```

### Dependency Update

Update `go.mod` to use the version of retryablehttp-go that includes the `DisableHTTP2` option (after PR #521 is merged).

## Testing

### Before Fix:
```bash
$ httpx -u https://example.com -pr http11 -verbose
# Output shows HTTP/2 being used despite -pr http11 flag
```

### After Fix:
```bash
$ httpx -u https://example.com -pr http11 -verbose
# Output shows only HTTP/1.1 being used
```

## Benefits

1. ✅ **Honors User Intent:** Respects the `-pr http11` flag
2. ✅ **Backward Compatible:** Default behavior unchanged
3. ✅ **Clean Solution:** No workarounds or hacks
4. ✅ **Maintainable:** Clear, documented code

## Related Issues

- Fixes: projectdiscovery/httpx#2240
- Depends on: projectdiscovery/retryablehttp-go#521
- Related: projectdiscovery/retryablehttp-go#3

## Bounty

This issue has a **$100 bounty** from ProjectDiscovery.

## Files in This Branch

- `FIX_README.md` - This file
- `IMPLEMENTATION_GUIDE.md` - Detailed implementation steps
- `PATCH_NOTES.md` - Quick reference for the required change
- `fix-http11.patch` - Patch file showing the diff

## Next Steps

1. Wait for retryablehttp-go PR #521 to be merged
2. Apply the code change to `common/httpx/httpx.go` as documented
3. Update `go.mod` dependency
4. Test with various scenarios
5. Submit PR to projectdiscovery/httpx
85 changes: 85 additions & 0 deletions IMPLEMENTATION_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Implementation Guide: Fix for Issue #2240

## Overview
This fix ensures that httpx honors the `-pr http11` flag by preventing automatic fallback to HTTP/2 in the retryablehttp-go library.

## Prerequisites
1. The retryablehttp-go library must have the `DisableHTTP2` option available (PR #521)
2. Update go.mod to use the version of retryablehttp-go that includes this option

## Implementation Steps

### Step 1: Update common/httpx/httpx.go

**Location:** After line 79 (after `retryablehttpOptions.Trace = options.Trace`)

**Add the following code:**
```go
// Disable HTTP/2 fallback when http11 protocol is explicitly requested
// This prevents retryablehttp from automatically retrying with HTTP/2
// when HTTP/1.x errors occur, honoring the user's -pr http11 flag
if httpx.Options.Protocol == "http11" {
retryablehttpOptions.DisableHTTP2 = true
}
```

### Step 2: Update go.mod
Update the retryablehttp-go dependency to the version that includes the DisableHTTP2 option:
```
github.com/projectdiscovery/retryablehttp-go v1.0.XXX // version with DisableHTTP2 support
```

## How It Works

### Before the Fix:
1. User runs: `httpx -u https://example.com -pr http11`
2. httpx sets `GODEBUG=http2client=0` and clears `TLSNextProto`
3. Request is made with HTTP/1.1
4. If server responds with HTTP/2 error, retryablehttp-go automatically retries with HTTPClient2 (HTTP/2)
5. **Result:** HTTP/2 is used despite `-pr http11` flag ❌

### After the Fix:
1. User runs: `httpx -u https://example.com -pr http11`
2. httpx sets `GODEBUG=http2client=0`, clears `TLSNextProto`, AND sets `DisableHTTP2 = true`
3. Request is made with HTTP/1.1
4. If server responds with HTTP/2 error, retryablehttp-go does NOT fall back to HTTP/2
5. **Result:** Only HTTP/1.1 is used, honoring the `-pr http11` flag ✅

## Testing

### Test Case 1: HTTP/1.1-only mode
```bash
httpx -u https://example.com -pr http11 -verbose
```
Expected: All requests use HTTP/1.1, no HTTP/2 fallback

### Test Case 2: Default mode (HTTP/2 allowed)
```bash
httpx -u https://example.com -verbose
```
Expected: HTTP/2 fallback still works when not explicitly disabled

### Test Case 3: HTTP/2-only mode
```bash
httpx -u https://example.com -pr http2 -verbose
```
Expected: All requests use HTTP/2

## Code Changes Summary

**File:** `common/httpx/httpx.go`
**Lines:** Insert after line 79
**Lines Added:** 5
**Lines Modified:** 0
**Lines Deleted:** 0

## Dependencies
- Requires: projectdiscovery/retryablehttp-go PR #521 to be merged
- Related Issue: projectdiscovery/httpx#2240
- Related Issue: projectdiscovery/retryablehttp-go#3

## Backward Compatibility
✅ Fully backward compatible
- Default behavior unchanged (HTTP/2 fallback still enabled)
- Only affects behavior when `-pr http11` is explicitly specified
- No breaking changes to existing APIs
22 changes: 22 additions & 0 deletions PATCH_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Fix for Issue #2240: Honor -pr http11 flag

## Changes Required

This fix requires updating `common/httpx/httpx.go` to set the `DisableHTTP2` option in retryablehttp-go when the user specifies `-pr http11`.

### Location
File: `common/httpx/httpx.go`
After line 79 (where `retryablehttpOptions.Trace = options.Trace` is set)

### Code to Add
```go
// Disable HTTP/2 fallback when http11 protocol is explicitly requested
if httpx.Options.Protocol == "http11" {
retryablehttpOptions.DisableHTTP2 = true
}
```

This ensures that when users specify `-pr http11`, the retryablehttp client will not automatically fall back to HTTP/2 on protocol errors, respecting the user's explicit HTTP/1.1-only preference.

## Dependencies
This fix depends on the DisableHTTP2 option being added to retryablehttp-go (PR #521).
33 changes: 33 additions & 0 deletions apply-fix.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash

# Script to apply the fix for issue #2240
# This adds the DisableHTTP2 option when -pr http11 is specified

FILE="common/httpx/httpx.go"
BACKUP="common/httpx/httpx.go.backup"

# Create backup
cp "$FILE" "$BACKUP"

# The fix: Insert after line 79 (after retryablehttpOptions.Trace = options.Trace)
# We need to add:
# // Disable HTTP/2 fallback when http11 protocol is explicitly requested
# // This prevents retryablehttp from automatically retrying with HTTP/2
# // when HTTP/1.x errors occur, honoring the user's -pr http11 flag
# if httpx.Options.Protocol == "http11" {
# retryablehttpOptions.DisableHTTP2 = true
# }

# Using sed to insert after the line containing "retryablehttpOptions.Trace = options.Trace"
sed -i '/retryablehttpOptions.Trace = options.Trace/a\
\t// Disable HTTP/2 fallback when http11 protocol is explicitly requested\
\t// This prevents retryablehttp from automatically retrying with HTTP/2\
\t// when HTTP/1.x errors occur, honoring the user'"'"'s -pr http11 flag\
\tif httpx.Options.Protocol == "http11" {\
\t\tretryablehttpOptions.DisableHTTP2 = true\
\t}' "$FILE"
Comment on lines +22 to +28
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

sed -i is not portable across macOS and Linux.

On macOS (BSD sed), sed -i requires an empty string argument (sed -i ''), whereas GNU sed (Linux) does not. This script will fail on macOS with an error about extra characters after the command.

Proposed fix for cross-platform compatibility
-sed -i '/retryablehttpOptions.Trace = options.Trace/a\
+# Detect OS for sed compatibility
+if [[ "$OSTYPE" == "darwin"* ]]; then
+  SED_INPLACE="sed -i ''"
+else
+  SED_INPLACE="sed -i"
+fi
+
+$SED_INPLACE '/retryablehttpOptions.Trace = options.Trace/a\
 \t// Disable HTTP/2 fallback when http11 protocol is explicitly requested\
 \t// This prevents retryablehttp from automatically retrying with HTTP/2\
 \t// when HTTP/1.x errors occur, honoring the user'"'"'s -pr http11 flag\
 \tif httpx.Options.Protocol == "http11" {\
 \t\tretryablehttpOptions.DisableHTTP2 = true\
 \t}' "$FILE"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sed -i '/retryablehttpOptions.Trace = options.Trace/a\
\t// Disable HTTP/2 fallback when http11 protocol is explicitly requested\
\t// This prevents retryablehttp from automatically retrying with HTTP/2\
\t// when HTTP/1.x errors occur, honoring the user'"'"'s -pr http11 flag\
\tif httpx.Options.Protocol == "http11" {\
\t\tretryablehttpOptions.DisableHTTP2 = true\
\t}' "$FILE"
# Detect OS for sed compatibility
if [[ "$OSTYPE" == "darwin"* ]]; then
SED_INPLACE="sed -i ''"
else
SED_INPLACE="sed -i"
fi
$SED_INPLACE '/retryablehttpOptions.Trace = options.Trace/a\
\t// Disable HTTP/2 fallback when http11 protocol is explicitly requested\
\t// This prevents retryablehttp from automatically retrying with HTTP/2\
\t// when HTTP/1.x errors occur, honoring the user'"'"'s -pr http11 flag\
\tif httpx.Options.Protocol == "http11" {\
\t\tretryablehttpOptions.DisableHTTP2 = true\
\t}' "$FILE"
🤖 Prompt for AI Agents
In `@apply-fix.sh` around lines 22 - 28, The current use of sed -i is not portable
on macOS; replace the in-place sed invocation that appends the HTTP/1.1 block
with a portable tempfile pattern: create a temp file (using mktemp), run sed
with the same script (the block that checks httpx.Options.Protocol == "http11"
and sets retryablehttpOptions.DisableHTTP2 = true) redirecting output to the
temp file, then atomically mv the temp file back to FILE and clean up on
failure; ensure you preserve the exact appended lines and variable names (FILE,
httpx.Options.Protocol, retryablehttpOptions.DisableHTTP2) and handle errors
(non-zero exit) before replacing the original file.


echo "Fix applied to $FILE"
echo "Backup saved to $BACKUP"
echo ""
echo "Please review the changes with: git diff $FILE"
18 changes: 18 additions & 0 deletions fix-http11.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
diff --git a/common/httpx/httpx.go b/common/httpx/httpx.go
index original..fixed
--- a/common/httpx/httpx.go
+++ b/common/httpx/httpx.go
@@ -76,6 +76,11 @@ func New(options *Options) (*HTTPX, error) {
var retryablehttpOptions = retryablehttp.DefaultOptionsSpraying
retryablehttpOptions.Timeout = httpx.Options.Timeout
retryablehttpOptions.RetryMax = httpx.Options.RetryMax
retryablehttpOptions.Trace = options.Trace
+ // Disable HTTP/2 fallback when http11 protocol is explicitly requested
+ // This prevents retryablehttp from automatically retrying with HTTP/2
+ // when HTTP/1.x errors occur, honoring the user's -pr http11 flag
+ if httpx.Options.Protocol == "http11" {
+ retryablehttpOptions.DisableHTTP2 = true
+ }
handleHSTS := func(req *http.Request) {
if req.Response.Header.Get("Strict-Transport-Security") == "" {
return