Skip to content

Commit 7520f0b

Browse files
bpowersbradfitz
authored andcommitted
httputil: accumulate X-Forwarded-For header info
If the X-Forwarded-For header already exists on a request, we should append our client's IP to it after a comma+space instead of overwriting it. Fixes #3846. R=golang-dev, bradfitz CC=golang-dev https://golang.org/cl/6448053
1 parent ad058ca commit 7520f0b

2 files changed

Lines changed: 50 additions & 2 deletions

File tree

‎src/pkg/net/http/httputil/reverseproxy.go‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,14 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
106106
outreq.Header.Del("Connection")
107107
}
108108

109-
if clientIp, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
110-
outreq.Header.Set("X-Forwarded-For", clientIp)
109+
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
110+
// If we aren't the first proxy retain prior
111+
// X-Forwarded-For information as a comma+space
112+
// separated list and fold multiple headers into one.
113+
if prior, ok := outreq.Header["X-Forwarded-For"]; ok {
114+
clientIP = strings.Join(prior, ", ") + ", " + clientIP
115+
}
116+
outreq.Header.Set("X-Forwarded-For", clientIP)
111117
}
112118

113119
res, err := transport.RoundTrip(outreq)

‎src/pkg/net/http/httputil/reverseproxy_test.go‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"net/http"
1212
"net/http/httptest"
1313
"net/url"
14+
"strings"
1415
"testing"
1516
"time"
1617
)
@@ -71,6 +72,47 @@ func TestReverseProxy(t *testing.T) {
7172
}
7273
}
7374

75+
func TestXForwardedFor(t *testing.T) {
76+
const prevForwardedFor = "client ip"
77+
const backendResponse = "I am the backend"
78+
const backendStatus = 404
79+
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
80+
if r.Header.Get("X-Forwarded-For") == "" {
81+
t.Errorf("didn't get X-Forwarded-For header")
82+
}
83+
if !strings.Contains(r.Header.Get("X-Forwarded-For"), prevForwardedFor) {
84+
t.Errorf("X-Forwarded-For didn't contain prior data")
85+
}
86+
w.WriteHeader(backendStatus)
87+
w.Write([]byte(backendResponse))
88+
}))
89+
defer backend.Close()
90+
backendURL, err := url.Parse(backend.URL)
91+
if err != nil {
92+
t.Fatal(err)
93+
}
94+
proxyHandler := NewSingleHostReverseProxy(backendURL)
95+
frontend := httptest.NewServer(proxyHandler)
96+
defer frontend.Close()
97+
98+
getReq, _ := http.NewRequest("GET", frontend.URL, nil)
99+
getReq.Host = "some-name"
100+
getReq.Header.Set("Connection", "close")
101+
getReq.Header.Set("X-Forwarded-For", prevForwardedFor)
102+
getReq.Close = true
103+
res, err := http.DefaultClient.Do(getReq)
104+
if err != nil {
105+
t.Fatalf("Get: %v", err)
106+
}
107+
if g, e := res.StatusCode, backendStatus; g != e {
108+
t.Errorf("got res.StatusCode %d; expected %d", g, e)
109+
}
110+
bodyBytes, _ := ioutil.ReadAll(res.Body)
111+
if g, e := string(bodyBytes), backendResponse; g != e {
112+
t.Errorf("got body %q; expected %q", g, e)
113+
}
114+
}
115+
74116
var proxyQueryTests = []struct {
75117
baseSuffix string // suffix to add to backend URL
76118
reqSuffix string // suffix to add to frontend's request URL

0 commit comments

Comments
 (0)