From 4fde58d57eb3989f967d11a4fb300156af0124d5 Mon Sep 17 00:00:00 2001 From: Tanguy Herrmann Date: Thu, 3 Dec 2015 18:36:55 +0100 Subject: [PATCH 1/2] Adds RFC7239 checks to the new location middleware. The RFC7239 is the standard way to define headers about reverse proxy redirections. It replaces all the X-Forwarded-* previous way of defining them. We let the previous implementations exist to avoid breaking existant installations. --- router/middleware/location/location.go | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/router/middleware/location/location.go b/router/middleware/location/location.go index 08dc91710..9e5290113 100644 --- a/router/middleware/location/location.go +++ b/router/middleware/location/location.go @@ -15,6 +15,27 @@ func Resolve(c *gin.Context) { c.Next() } +// parseHeader parses non unique headers value +// from a http.Request and return a slice of the values +// queried from the header +func parseHeader(r *http.Request, header string, token string) (val []string) { + for _, v := range r.Header[header] { + options := strings.Split(v, ";") + for _, o := range options { + keyvalue := strings.Split(o, "=") + var key, value string + if len(keyvalue) > 1 { + key, value = strings.TrimSpace(keyvalue[0]), strings.TrimSpace(keyvalue[1]) + } + key = strings.ToLower(key) + if key == token { + val = append(val, value) + } + } + } + return +} + // resolveScheme is a helper function that evaluates the http.Request // and returns the scheme, HTTP or HTTPS. It is able to detect, // using the X-Forwarded-Proto, if the original request was HTTPS @@ -29,6 +50,8 @@ func resolveScheme(r *http.Request) string { return "https" case r.Header.Get("X-Forwarded-Proto") == "https": return "https" + case len(r.Header.Get("Forwarded")) != 0 && len(parseHeader(r, "Forwarded", "proto")) != 0 && parseHeader(r, "Forwarded", "proto")[0] == "https": + return "https" default: return "http" } @@ -46,8 +69,12 @@ func resolveHost(r *http.Request) string { return r.URL.Host case len(r.Header.Get("X-Forwarded-For")) != 0: return r.Header.Get("X-Forwarded-For") + case len(r.Header.Get("Forwarded")) != 0 && len(parseHeader(r, "Forwarded", "for")) != 0: + return parseHeader(r, "Forwarded", "for")[0] case len(r.Header.Get("X-Host")) != 0: return r.Header.Get("X-Host") + case len(r.Header.Get("Forwarded")) != 0 && len(parseHeader(r, "Forwarded", "host")) != 0: + return parseHeader(r, "Forwarded", "host")[0] case len(r.Header.Get("XFF")) != 0: return r.Header.Get("XFF") case len(r.Header.Get("X-Real-IP")) != 0: From 7ba1195edf1d5d4ae0e97d28230806e61c6f3aab Mon Sep 17 00:00:00 2001 From: Tanguy Herrmann Date: Fri, 4 Dec 2015 23:33:08 +0100 Subject: [PATCH 2/2] Adds test for RFC7239 --- router/middleware/location/location_test.go | 48 +++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 router/middleware/location/location_test.go diff --git a/router/middleware/location/location_test.go b/router/middleware/location/location_test.go new file mode 100644 index 000000000..46ddca676 --- /dev/null +++ b/router/middleware/location/location_test.go @@ -0,0 +1,48 @@ +package location + +import ( + "github.com/franela/goblin" + "net/http" + "reflect" + "testing" +) + +var mockHeader []string +var mockRequest *http.Request + +var wronglyFormedHeader []string +var wronglyFormedRequest *http.Request + +func init() { + mockHeader = []string{"For= 110.0.2.2", "for = \"[::1]\"; Host=example.com; foR=10.2.3.4; pRoto =https ; By = 127.0.0.1"} + mockRequest = &http.Request{Header: map[string][]string{"Forwarded": mockHeader}} + wronglyFormedHeader = []string{"Fro= 110.0.2.2", "for = \"[:1]\"% Host=example:.com| foR=10.278.3.4% poto =https | Bi % 127.0.0.1", ""} + wronglyFormedRequest = &http.Request{Header: map[string][]string{"Forwarded": wronglyFormedHeader}} +} + +func TestParseForwardedHeadersProto(t *testing.T) { + g := goblin.Goblin(t) + + g.Describe("Parse proto Forwarded Headers", func() { + g.It("Should parse a normal proto Forwarded header", func() { + parsedHeader := parseHeader(mockRequest, "Forwarded", "proto") + g.Assert("https" == parsedHeader[0]).IsTrue() + }) + g.It("Should parse a normal for Forwarded header", func() { + parsedHeader := parseHeader(mockRequest, "Forwarded", "for") + g.Assert(reflect.DeepEqual([]string{"110.0.2.2", "\"[::1]\"", "10.2.3.4"}, parsedHeader)).IsTrue() + }) + g.It("Should parse a normal host Forwarded header", func() { + parsedHeader := parseHeader(mockRequest, "Forwarded", "host") + g.Assert("example.com" == parsedHeader[0]).IsTrue() + }) + g.It("Should parse a normal by Forwarded header", func() { + parsedHeader := parseHeader(mockRequest, "Forwarded", "by") + g.Assert("127.0.0.1" == parsedHeader[0]).IsTrue() + }) + g.It("Should not crash if a wrongly formed Forwarder header is sent", func() { + parsedHeader := parseHeader(wronglyFormedRequest, "Forwarded", "by") + g.Assert(len(parsedHeader) == 0).IsTrue() + }) + }) +}