Skip to content

Commit d1d8ad3

Browse files
authored
Context.Scheme should validate values taken from header (#2953)
1 parent 0143b9d commit d1d8ad3

2 files changed

Lines changed: 163 additions & 36 deletions

File tree

context.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,23 +158,35 @@ func (c *Context) IsWebSocket() bool {
158158
return strings.EqualFold(upgrade, "websocket") && strings.Contains(strings.ToLower(connection), "upgrade")
159159
}
160160

161+
func isValidProto(proto string) bool {
162+
if proto == "" {
163+
return false
164+
}
165+
for _, p := range []string{"http", "https", "ws", "wss"} {
166+
if strings.EqualFold(proto, p) {
167+
return true
168+
}
169+
}
170+
return false
171+
}
172+
161173
// Scheme returns the HTTP protocol scheme, `http` or `https`.
162174
func (c *Context) Scheme() string {
163175
// Can't use `r.Request.URL.Scheme`
164176
// See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0
165177
if c.IsTLS() {
166178
return "https"
167179
}
168-
if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" {
180+
if scheme := c.request.Header.Get(HeaderXForwardedProto); isValidProto(scheme) {
169181
return scheme
170182
}
171-
if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" {
183+
if scheme := c.request.Header.Get(HeaderXForwardedProtocol); isValidProto(scheme) {
172184
return scheme
173185
}
174186
if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" {
175187
return "https"
176188
}
177-
if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" {
189+
if scheme := c.request.Header.Get(HeaderXUrlScheme); isValidProto(scheme) {
178190
return scheme
179191
}
180192
return "http"

context_test.go

Lines changed: 148 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,60 +1090,175 @@ func TestContext_Request(t *testing.T) {
10901090
}
10911091

10921092
func TestContext_Scheme(t *testing.T) {
1093-
tests := []struct {
1094-
c *Context
1095-
s string
1093+
var testCases = []struct {
1094+
name string
1095+
givenIsTLS bool
1096+
givenHeaders http.Header
1097+
expect string
10961098
}{
10971099
{
1098-
&Context{
1099-
request: &http.Request{
1100-
TLS: &tls.ConnectionState{},
1101-
},
1100+
name: "defaults to http without TLS or headers",
1101+
givenIsTLS: false,
1102+
givenHeaders: nil,
1103+
expect: "http",
1104+
},
1105+
{
1106+
name: "returns https when TLS is enabled",
1107+
givenIsTLS: true,
1108+
givenHeaders: nil,
1109+
expect: "https",
1110+
},
1111+
{
1112+
name: "TLS takes precedence over forwarded proto",
1113+
givenIsTLS: true,
1114+
givenHeaders: http.Header{
1115+
HeaderXForwardedProto: []string{"http"},
11021116
},
1103-
"https",
1117+
expect: "https",
11041118
},
11051119
{
1106-
&Context{
1107-
request: &http.Request{
1108-
Header: http.Header{HeaderXForwardedProto: []string{"https"}},
1109-
},
1120+
name: "uses X-Forwarded-Proto http",
1121+
givenIsTLS: false,
1122+
givenHeaders: http.Header{
1123+
HeaderXForwardedProto: []string{"http"},
11101124
},
1111-
"https",
1125+
expect: "http",
11121126
},
11131127
{
1114-
&Context{
1115-
request: &http.Request{
1116-
Header: http.Header{HeaderXForwardedProtocol: []string{"http"}},
1117-
},
1128+
name: "uses X-Forwarded-Proto https",
1129+
givenIsTLS: false,
1130+
givenHeaders: http.Header{
1131+
HeaderXForwardedProto: []string{"https"},
11181132
},
1119-
"http",
1133+
expect: "https",
11201134
},
11211135
{
1122-
&Context{
1123-
request: &http.Request{
1124-
Header: http.Header{HeaderXForwardedSsl: []string{"on"}},
1125-
},
1136+
name: "X-Forwarded-Proto is case insensitive",
1137+
givenIsTLS: false,
1138+
givenHeaders: http.Header{
1139+
HeaderXForwardedProto: []string{"HTTPS"},
11261140
},
1127-
"https",
1141+
expect: "HTTPS",
11281142
},
11291143
{
1130-
&Context{
1131-
request: &http.Request{
1132-
Header: http.Header{HeaderXUrlScheme: []string{"https"}},
1133-
},
1144+
name: "uses X-Forwarded-Proto ws",
1145+
givenIsTLS: false,
1146+
givenHeaders: http.Header{
1147+
HeaderXForwardedProto: []string{"ws"},
11341148
},
1135-
"https",
1149+
expect: "ws",
11361150
},
11371151
{
1138-
&Context{
1139-
request: &http.Request{},
1152+
name: "uses X-Forwarded-Proto wss",
1153+
givenIsTLS: false,
1154+
givenHeaders: http.Header{
1155+
HeaderXForwardedProto: []string{"wss"},
11401156
},
1141-
"http",
1157+
expect: "wss",
1158+
},
1159+
{
1160+
name: "ignores invalid X-Forwarded-Proto and uses X-Forwarded-Protocol",
1161+
givenIsTLS: false,
1162+
givenHeaders: http.Header{
1163+
HeaderXForwardedProto: []string{"ftp"},
1164+
HeaderXForwardedProtocol: []string{"https"},
1165+
},
1166+
expect: "https",
1167+
},
1168+
{
1169+
name: "uses X-Forwarded-Protocol",
1170+
givenIsTLS: false,
1171+
givenHeaders: http.Header{
1172+
HeaderXForwardedProtocol: []string{"https"},
1173+
},
1174+
expect: "https",
1175+
},
1176+
{
1177+
name: "X-Forwarded-Proto takes precedence over X-Forwarded-Protocol",
1178+
givenIsTLS: false,
1179+
givenHeaders: http.Header{
1180+
HeaderXForwardedProto: []string{"http"},
1181+
HeaderXForwardedProtocol: []string{"https"},
1182+
},
1183+
expect: "http",
1184+
},
1185+
{
1186+
name: "uses X-Forwarded-Ssl on",
1187+
givenIsTLS: false,
1188+
givenHeaders: http.Header{
1189+
HeaderXForwardedSsl: []string{"on"},
1190+
},
1191+
expect: "https",
1192+
},
1193+
{
1194+
name: "X-Forwarded-Ssl on is case sensitive",
1195+
givenIsTLS: false,
1196+
givenHeaders: http.Header{
1197+
HeaderXForwardedSsl: []string{"ON"},
1198+
},
1199+
expect: "http",
1200+
},
1201+
{
1202+
name: "X-Forwarded-Protocol takes precedence over X-Forwarded-Ssl",
1203+
givenIsTLS: false,
1204+
givenHeaders: http.Header{
1205+
HeaderXForwardedProtocol: []string{"http"},
1206+
HeaderXForwardedSsl: []string{"on"},
1207+
},
1208+
expect: "http",
1209+
},
1210+
{
1211+
name: "uses X-Url-Scheme",
1212+
givenIsTLS: false,
1213+
givenHeaders: http.Header{
1214+
HeaderXUrlScheme: []string{"https"},
1215+
},
1216+
expect: "https",
1217+
},
1218+
{
1219+
name: "X-Forwarded-Ssl takes precedence over X-Url-Scheme",
1220+
givenIsTLS: false,
1221+
givenHeaders: http.Header{
1222+
HeaderXForwardedSsl: []string{"on"},
1223+
HeaderXUrlScheme: []string{"http"},
1224+
},
1225+
expect: "https",
1226+
},
1227+
{
1228+
name: "ignores invalid forwarded headers and falls back to http",
1229+
givenIsTLS: false,
1230+
givenHeaders: http.Header{
1231+
HeaderXForwardedProto: []string{"ftp"},
1232+
HeaderXForwardedProtocol: []string{"smtp"},
1233+
HeaderXForwardedSsl: []string{"off"},
1234+
HeaderXUrlScheme: []string{"file"},
1235+
},
1236+
expect: "http",
1237+
},
1238+
{
1239+
name: "ignores empty forwarded proto and uses X-Url-Scheme",
1240+
givenIsTLS: false,
1241+
givenHeaders: http.Header{
1242+
HeaderXForwardedProto: []string{""},
1243+
HeaderXUrlScheme: []string{"https"},
1244+
},
1245+
expect: "https",
11421246
},
11431247
}
11441248

1145-
for _, tt := range tests {
1146-
assert.Equal(t, tt.s, tt.c.Scheme())
1249+
for _, tc := range testCases {
1250+
t.Run(tc.name, func(t *testing.T) {
1251+
req := httptest.NewRequest(http.MethodGet, "/", nil)
1252+
if tc.givenHeaders != nil {
1253+
req.Header = tc.givenHeaders
1254+
}
1255+
c := NewContext(req, nil)
1256+
if tc.givenIsTLS {
1257+
c.request.TLS = &tls.ConnectionState{}
1258+
}
1259+
1260+
assert.Equal(t, tc.expect, c.Scheme())
1261+
})
11471262
}
11481263
}
11491264

0 commit comments

Comments
 (0)