caddyhttp: add {http.request.proto_name} placeholder for spec-compliant protocol names (#7782)
Tests / test (./cmd/caddy/caddy, ~1.26.0, macos-14, 0, 1.26, mac) (push) Waiting to run
Tests / test (./cmd/caddy/caddy.exe, ~1.26.0, windows-latest, True, 1.26, windows) (push) Waiting to run
Tests / test (s390x on IBM Z) (push) Has been skipped
Lint / lint (macos-14, mac) (push) Waiting to run
Tests / goreleaser-check (push) Has been skipped
Lint / lint (windows-latest, windows) (push) Waiting to run
Tests / test (./cmd/caddy/caddy, ~1.26.0, ubuntu-latest, 0, 1.26, linux) (push) Failing after 2m5s
Cross-Build / build (~1.26.0, 1.26, aix) (push) Successful in 2m31s
Cross-Build / build (~1.26.0, 1.26, darwin) (push) Successful in 3m4s
Cross-Build / build (~1.26.0, 1.26, dragonfly) (push) Successful in 1m42s
Cross-Build / build (~1.26.0, 1.26, freebsd) (push) Successful in 1m50s
Cross-Build / build (~1.26.0, 1.26, illumos) (push) Successful in 2m30s
Cross-Build / build (~1.26.0, 1.26, linux) (push) Successful in 1m54s
Cross-Build / build (~1.26.0, 1.26, netbsd) (push) Successful in 1m45s
Cross-Build / build (~1.26.0, 1.26, solaris) (push) Successful in 1m47s
Cross-Build / build (~1.26.0, 1.26, windows) (push) Successful in 1m44s
Cross-Build / build (~1.26.0, 1.26, openbsd) (push) Successful in 2m37s
Lint / dependency-review (push) Failing after 1m3s
Lint / govulncheck (push) Successful in 1m59s
Lint / lint (ubuntu-latest, linux) (push) Successful in 2m27s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 6m15s

* caddyhttp: add {http.request.proto_name} placeholder for spec-compliant protocol names

{http.request.proto} exposes Go's raw http.Request.Proto field which
returns HTTP/2.0 and HTTP/3.0 for HTTP/2 and HTTP/3 respectively.
These strings are non-standard since the specs define them as HTTP/2
and HTTP/3.

To preserve backward compat (especially CGI/FastCGI expectations),
{http.request.proto} is kept as-is. A new {http.request.proto_name}
placeholder is introduced that normalises the version string to the
spec-defined form:
  HTTP/2.0 -> HTTP/2
  HTTP/3.0 -> HTTP/3
  all others returned unchanged

Closes #7734

* caddyhttp: Use ProtoMajor for proto_name normalization and update docs

---------

Co-authored-by: jalikajalika5 <105954036+jalikajalika5@users.noreply.github.com>
This commit is contained in:
Muhammad Syafri, S.Kom
2026-06-04 22:03:19 +07:00
committed by GitHub
parent fcc7860d03
commit 915793f6e0
3 changed files with 40 additions and 1 deletions
+2 -1
View File
@@ -70,7 +70,8 @@ func init() {
// `{http.request.orig_uri.query}` | The request's original query string (without `?`)
// `{http.request.orig_uri.prefixed_query}` | The request's original query string with a `?` prefix, if non-empty
// `{http.request.port}` | The port part of the request's Host header
// `{http.request.proto}` | The protocol of the request
// `{http.request.proto}` | The raw protocol of the request as returned by Go (e.g., HTTP/2.0 or HTTP/3.0)
// `{http.request.proto_name}` | The spec-defined protocol of the request (e.g., HTTP/2 or HTTP/3)
// `{http.request.local.host}` | The host (IP) part of the local address the connection arrived on
// `{http.request.local.port}` | The port part of the local address the connection arrived on
// `{http.request.local}` | The local address the connection arrived on
+8
View File
@@ -105,6 +105,14 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
return "http", true
case "http.request.proto":
return req.Proto, true
case "http.request.proto_name":
if req.ProtoMajor == 2 {
return "HTTP/2", true
}
if req.ProtoMajor == 3 {
return "HTTP/3", true
}
return req.Proto, true
case "http.request.host":
host, _, err := net.SplitHostPort(req.Host)
if err != nil {
+30
View File
@@ -266,3 +266,33 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
}
}
}
func TestHTTPProtoNameNormalization(t *testing.T) {
for _, tc := range []struct {
proto string
major int
expectRaw string
expectName string
}{
{proto: "HTTP/1.0", major: 1, expectRaw: "HTTP/1.0", expectName: "HTTP/1.0"},
{proto: "HTTP/1.1", major: 1, expectRaw: "HTTP/1.1", expectName: "HTTP/1.1"},
{proto: "HTTP/2.0", major: 2, expectRaw: "HTTP/2.0", expectName: "HTTP/2"},
{proto: "HTTP/3.0", major: 3, expectRaw: "HTTP/3.0", expectName: "HTTP/3"},
} {
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Proto = tc.proto
req.ProtoMajor = tc.major
repl := caddy.NewReplacer()
addHTTPVarsToReplacer(repl, req, nil)
gotRaw, okRaw := repl.GetString("http.request.proto")
if !okRaw || gotRaw != tc.expectRaw {
t.Errorf("proto=%s: expected http.request.proto to be %q, got %q (ok=%t)", tc.proto, tc.expectRaw, gotRaw, okRaw)
}
gotName, okName := repl.GetString("http.request.proto_name")
if !okName || gotName != tc.expectName {
t.Errorf("proto=%s: expected http.request.proto_name to be %q, got %q (ok=%t)", tc.proto, tc.expectName, gotName, okName)
}
}
}