From 4b7063bb384a80562bb92ec73c5b4b43f6a5ac18 Mon Sep 17 00:00:00 2001 From: dusk5213 Date: Tue, 10 Mar 2026 17:51:46 +0800 Subject: [PATCH] fix: Non-standard X-Forwarded-For header content is not supported --- gin.go | 26 +++++++++++++- gin_test.go | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 2e033bf3..e98d5a0d 100644 --- a/gin.go +++ b/gin.go @@ -483,9 +483,33 @@ func (engine *Engine) validateHeader(header string) (clientIP string, valid bool if header == "" { return "", false } + var ipStr string items := strings.Split(header, ",") for i := len(items) - 1; i >= 0; i-- { - ipStr := strings.TrimSpace(items[i]) + item := strings.TrimSpace(items[i]) + // [IPv6] or [IPv6]:port + if strings.HasPrefix(item, "[") { + if idx := strings.IndexByte(item, ']'); idx > 0 { + ipPart := item[1:idx] + if ip := net.ParseIP(ipPart); ip != nil { + if idx == len(item)-1 || (idx+1 < len(item) && item[idx+1] == ':') { + ipStr = ipPart + } + } + } + } else if ip := net.ParseIP(item); ip != nil { + // plain IPv4 or IPv6 + ipStr = item + } else if idx := strings.LastIndexByte(item, ':'); idx > 0 { + // IPv4:port + ipPart := item[:idx] + if ip := net.ParseIP(ipPart); ip != nil { + ipStr = ipPart + } + } + if ipStr == "" { + break + } ip := net.ParseIP(ipStr) if ip == nil { break diff --git a/gin_test.go b/gin_test.go index 43c9494d..5526667a 100644 --- a/gin_test.go +++ b/gin_test.go @@ -1084,3 +1084,105 @@ func TestUpdateRouteTreesCalledOnce(t *testing.T) { assert.Equal(t, "ok", w.Body.String()) } } + +func TestValidateHeader_IssueCases(t *testing.T) { + r := New() + + err := r.SetTrustedProxies([]string{"0.0.0.0/0", "::/0"}) + if err != nil { + t.Fatalf("SetTrustedProxies failed: %v", err) + } + + tests := []struct { + name string + header string + wantIP string + wantValid bool + }{ + { + name: "IPv4 plain", + header: "127.0.0.1", + wantIP: "127.0.0.1", + wantValid: true, + }, + { + name: "IPv4 with port (ARR/IIS)", + header: "127.0.0.1:38792", + wantIP: "127.0.0.1", + wantValid: true, + }, + { + name: "IPv6 plain", + header: "240e:318:2f4a:de56::240", + wantIP: "240e:318:2f4a:de56::240", + wantValid: true, + }, + { + name: "IPv6 with brackets (IIS style)", + header: "[240e:318:2f4a:de56::240]", + wantIP: "240e:318:2f4a:de56::240", + wantValid: true, + }, + { + name: "IPv6 with brackets + port (ARR/IIS or cloud LB)", + header: "[240e:318:2f4a:de56::240]:38792", + wantIP: "240e:318:2f4a:de56::240", + wantValid: true, + }, { + name: "invalid ip", + header: "abc", + wantIP: "", + wantValid: false, + }, + { + name: "ipv6 missing closing bracket", + header: "[240e:318:2f4a:de56::240", + wantIP: "", + wantValid: false, + }, + { + name: "ipv6 bracket invalid suffix", + header: "[240e:318:2f4a:de56::240]abc", + wantIP: "", + wantValid: false, + }, + { + name: "multiple ipv4", + header: "1.1.1.1, 127.0.0.1", + wantIP: "1.1.1.1", + wantValid: true, + }, + { + name: "multiple ipv6", + header: "240e:318:2f4a:de56::111, 240e:318:2f4a:de56::240", + wantIP: "240e:318:2f4a:de56::111", + wantValid: true, + }, + { + name: "full", + header: "1.1.1.1, 127.0.0.1, 192.168.3.1:5050, 240e:318:2f4a:de56::1111, [240e:318:2f4a:de56::2222], [240e:318:2f4a:de56::3333]:7080", + wantIP: "1.1.1.1", + wantValid: true, + }, + // ---------- empty / whitespace ---------- + { + name: "empty header", + header: "", + wantIP: "", + wantValid: false, + }, + { + name: "whitespace header", + header: " ", + wantIP: "", + wantValid: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ip, valid := r.validateHeader(tt.header) + assert.Equal(t, tt.wantIP, ip) + assert.Equal(t, tt.wantValid, valid) + }) + } +}