Skip to content

Commit

Permalink
implement default values for header directive
Browse files Browse the repository at this point in the history
  • Loading branch information
gilbsgilbs committed Nov 17, 2020
1 parent 99b8f44 commit cb353a7
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 10 deletions.
24 changes: 15 additions & 9 deletions modules/caddyhttp/headers/caddyfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ func init() {
// parseCaddyfile sets up the handler for response headers from
// Caddyfile tokens. Syntax:
//
// header [<matcher>] [[+|-]<field> [<value|regexp>] [<replacement>]] {
// header [<matcher>] [[+|-|?]<field> [<value|regexp>] [<replacement>]] {
// [+]<field> [<value|regexp> [<replacement>]]
// ?<field> <default_value>
// -<field>
// [defer]
// }
Expand Down Expand Up @@ -102,7 +103,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
// parseReqHdrCaddyfile sets up the handler for request headers
// from Caddyfile tokens. Syntax:
//
// request_header [<matcher>] [[+|-]<field> [<value|regexp>] [<replacement>]]
// request_header [<matcher>] [[+|-|?]<field> [<value|regexp>] [<replacement>]]
//
func parseReqHdrCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
hdr := new(Handler)
Expand Down Expand Up @@ -142,12 +143,12 @@ func parseReqHdrCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler,

// CaddyfileHeaderOp applies a new header operation according to
// field, value, and replacement. The field can be prefixed with
// "+" or "-" to specify adding or removing; otherwise, the value
// will be set (overriding any previous value). If replacement is
// non-empty, value will be treated as a regular expression which
// will be used to search and then replacement will be used to
// complete the substring replacement; in that case, any + or -
// prefix to field will be ignored.
// "+", "-" or "?" to specify adding, removing or setting a default
// value; otherwise, the value will be set (overriding any previous
// value). If replacement is non-empty, value will be treated as a
// regular expression which will be used to search and then replacement
// will be used to complete the substring replacement; in that case,
// any "+", "-" or "?" prefix to field will be ignored.
func CaddyfileHeaderOp(ops *HeaderOps, field, value, replacement string) {
if strings.HasPrefix(field, "+") {
if ops.Add == nil {
Expand All @@ -156,6 +157,11 @@ func CaddyfileHeaderOp(ops *HeaderOps, field, value, replacement string) {
ops.Add.Set(field[1:], value)
} else if strings.HasPrefix(field, "-") {
ops.Delete = append(ops.Delete, field[1:])
} else if strings.HasPrefix(field, "?") {
if ops.SetDefault == nil {
ops.SetDefault = make(http.Header)
}
ops.SetDefault.Set(field[1:], value)
} else {
if replacement == "" {
if ops.Set == nil {
Expand All @@ -166,7 +172,7 @@ func CaddyfileHeaderOp(ops *HeaderOps, field, value, replacement string) {
if ops.Replace == nil {
ops.Replace = make(map[string][]Replacement)
}
field = strings.TrimLeft(field, "+-")
field = strings.TrimLeft(field, "+-?")
ops.Replace[field] = append(
ops.Replace[field],
Replacement{
Expand Down
15 changes: 15 additions & 0 deletions modules/caddyhttp/headers/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ type HeaderOps struct {
// Sets HTTP headers; replaces existing header fields.
Set http.Header `json:"set,omitempty"`

// Sets a default value for HTTP headers.
SetDefault http.Header `json:"set_default,omitempty"`

// Names of HTTP header fields to delete.
Delete []string `json:"delete,omitempty"`

Expand Down Expand Up @@ -206,6 +209,18 @@ func (ops HeaderOps) ApplyTo(hdr http.Header, repl *caddy.Replacer) {
hdr.Set(fieldName, strings.Join(newVals, ","))
}

// set default
for fieldName, vals := range ops.SetDefault {
fieldName = repl.ReplaceAll(fieldName, "")
if hdr.Get(fieldName) != "" {
continue
}

for _, v := range vals {
hdr.Add(fieldName, repl.ReplaceAll(v, ""))
}
}

// delete
for _, fieldName := range ops.Delete {
hdr.Del(repl.ReplaceAll(fieldName, ""))
Expand Down
119 changes: 118 additions & 1 deletion modules/caddyhttp/headers/headers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,125 @@

package headers

import "testing"
import (
"context"
"net/http"
"reflect"
"testing"

"github.com/caddyserver/caddy/v2"
)

func TestReqHeaders(t *testing.T) {
// TODO: write tests
}

func TestHeaderOps(t *testing.T) {
for i, tc := range []struct {
headerOps HeaderOps
input http.Header
expected http.Header
}{
{
headerOps: HeaderOps{
Add: http.Header{
"Expose-Secrets": []string{"always"},
},
},
input: http.Header{
"Expose-Secrets": []string{"i'm serious"},
},
expected: http.Header{
"Expose-Secrets": []string{"i'm serious", "always"},
},
},
{
headerOps: HeaderOps{
Set: http.Header{
"Who-Wins": []string{"batman"},
},
},
input: http.Header{
"Who-Wins": []string{"joker"},
},
expected: http.Header{
"Who-Wins": []string{"batman"},
},
},
{
headerOps: HeaderOps{
SetDefault: http.Header{
"Cache-Control": []string{"default"},
},
},
input: http.Header{
"Not-Cache-Control": []string{"cache-cache"},
},
expected: http.Header{
"Cache-Control": []string{"default"},
"Not-Cache-Control": []string{"cache-cache"},
},
},
{
headerOps: HeaderOps{
SetDefault: http.Header{
"Cache-Control": []string{"no-store"},
},
},
input: http.Header{
"Cache-Control": []string{"max-age=3600"},
},
expected: http.Header{
"Cache-Control": []string{"max-age=3600"},
},
},
{
headerOps: HeaderOps{
Delete: []string{"Kick-Me"},
},
input: http.Header{
"Kick-Me": []string{"if you can"},
"Keep-Me": []string{"i swear i'm innocent"},
},
expected: http.Header{
"Keep-Me": []string{"i swear i'm innocent"},
},
},
{
headerOps: HeaderOps{
Replace: map[string][]Replacement{
"Best-Server": []Replacement{
Replacement{
Search: "NGINX",
Replace: "the Caddy web server",
},
Replacement{
SearchRegexp: `Apache(\d+)`,
Replace: "Caddy",
},
},
},
},
input: http.Header{
"Best-Server": []string{"it's NGINX, undoubtedly", "I love Apache2"},
},
expected: http.Header{
"Best-Server": []string{"it's the Caddy web server, undoubtedly", "I love Caddy"},
},
},
} {
req := &http.Request{Header: tc.input}
repl := caddy.NewReplacer()
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
req = req.WithContext(ctx)

tc.headerOps.Provision(caddy.Context{})
tc.headerOps.ApplyToRequest(req)
actual := req.Header

if !reflect.DeepEqual(actual, tc.expected) {
t.Errorf("Test %d: Expected %v, got %v", i, tc.expected, actual)
continue
}
}
}

0 comments on commit cb353a7

Please sign in to comment.