1
0
mirror of https://github.com/umputun/reproxy.git synced 2025-11-29 22:08:14 +02:00

fix parsing headers with colons in the value, i.e. CSP

This commit is contained in:
Umputun
2025-02-20 12:00:40 -06:00
parent fee60fdb52
commit c970d116bb
2 changed files with 66 additions and 5 deletions

View File

@@ -18,20 +18,23 @@ import (
)
func headersHandler(addHeaders, dropHeaders []string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if len(addHeaders) == 0 && len(dropHeaders) == 0 {
next.ServeHTTP(w, r)
return
}
// add headers to response
for _, h := range addHeaders {
elems := strings.Split(h, ":")
if len(elems) != 2 {
continue
// split on first colon only
if i := strings.Index(h, ":"); i >= 0 {
key := strings.TrimSpace(h[:i])
value := strings.TrimSpace(h[i+1:])
if key != "" {
w.Header().Set(key, value)
}
}
w.Header().Set(strings.TrimSpace(elems[0]), strings.TrimSpace(elems[1]))
}
// drop headers from request

View File

@@ -270,3 +270,61 @@ func TestHttp_basicAuthHandler(t *testing.T) {
})
}
}
func TestHeaders_CSPParsing(t *testing.T) {
tbl := []struct {
name string
headers []string
expected map[string]string
}{
{
name: "simple headers",
headers: []string{"X-Frame-Options:SAMEORIGIN", "X-XSS-Protection:1; mode=block"},
expected: map[string]string{"X-Frame-Options": "SAMEORIGIN", "X-XSS-Protection": "1; mode=block"},
},
{
name: "CSP header with multiple directives",
headers: []string{"Content-Security-Policy:default-src 'self'; style-src 'self' 'unsafe-inline': something"},
expected: map[string]string{"Content-Security-Policy": "default-src 'self'; style-src 'self' 'unsafe-inline': something"},
},
{
name: "CSP header with quotes and colons",
headers: []string{"Content-Security-Policy:script-src 'unsafe-inline' 'unsafe-eval' 'self' https://example.com:443"},
expected: map[string]string{"Content-Security-Policy": "script-src 'unsafe-inline' 'unsafe-eval' 'self' https://example.com:443"},
},
{
name: "multiple colons in value",
headers: []string{"Custom-Header:value:with:colons"},
expected: map[string]string{"Custom-Header": "value:with:colons"},
},
{
name: "empty value after colon",
headers: []string{"Empty-Header:"},
expected: map[string]string{"Empty-Header": ""},
},
{
name: "malformed no colon",
headers: []string{"Bad-Header-No-Colon"},
expected: map[string]string{},
},
}
for _, tt := range tbl {
t.Run(tt.name, func(t *testing.T) {
wr := httptest.NewRecorder()
handler := headersHandler(tt.headers, nil)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
req := httptest.NewRequest("GET", "http://example.com", http.NoBody)
handler.ServeHTTP(wr, req)
if len(tt.expected) == 0 {
// For malformed headers, check they weren't set
assert.Equal(t, 0, len(wr.Header()))
return
}
for k, v := range tt.expected {
assert.Equal(t, v, wr.Header().Get(k), "Header %s value mismatch", k)
}
})
}
}