Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
headers: Add ability to replace substrings in header fields
This will probably be useful so the proxy can rewrite header values.
Matthew Holt committed
Sep 13, 2019 at 16:24 UTC
c09e86fddc8a6c0da59fd62a186cab1482faae2a
1 file changed
+137
-23
modules/caddyhttp/headers/headers.go
+137
-23
@@ -15,7 +15,9 @@
15
package headers
16
17
import (
18
+ "fmt"
19
"net/http"
20
+ "regexp"
21
"strings"
22
23
"github.com/caddyserver/caddy/v2"
@@ -23,43 +25,61 @@ import (
25
)
26
27
func init() {
26
- caddy.RegisterModule(Headers{})
28
+ caddy.RegisterModule(Handler{})
29
}
30
29
-// Headers is a middleware which can mutate HTTP headers.
30
-type Headers struct {
31
+// Handler is a middleware which can mutate HTTP headers.
32
+type Handler struct {
33
Request *HeaderOps `json:"request,omitempty"`
34
Response *RespHeaderOps `json:"response,omitempty"`
35
}
36
37
// CaddyModule returns the Caddy module information.
36
-func (Headers) CaddyModule() caddy.ModuleInfo {
38
+func (Handler) CaddyModule() caddy.ModuleInfo {
39
return caddy.ModuleInfo{
40
Name: "http.handlers.headers",
39
- New: func() caddy.Module { return new(Headers) },
41
+ New: func() caddy.Module { return new(Handler) },
42
}
43
}
44
43
-// HeaderOps defines some operations to
44
-// perform on HTTP headers.
45
-type HeaderOps struct {
46
- Add http.Header `json:"add,omitempty"`
47
- Set http.Header `json:"set,omitempty"`
48
- Delete []string `json:"delete,omitempty"`
45
+// Provision sets up h's configuration.
46
+func (h *Handler) Provision(_ caddy.Context) error {
47
+ if h.Request != nil {
48
+ err := h.Request.provision()
49
+ if err != nil {
50
+ return err
51
+ }
52
+ }
53
+ if h.Response != nil {
54
+ err := h.Response.provision()
55
+ if err != nil {
56
+ return err
57
+ }
58
+ }
59
+ return nil
60
}
61
51
-// RespHeaderOps is like HeaderOps, but
52
-// optionally deferred until response time.
53
-type RespHeaderOps struct {
54
- *HeaderOps
55
- Require *caddyhttp.ResponseMatcher `json:"require,omitempty"`
56
- Deferred bool `json:"deferred,omitempty"`
62
+// Validate ensures h's configuration is valid.
63
+func (h Handler) Validate() error {
64
+ if h.Request != nil {
65
+ err := h.Request.validate()
66
+ if err != nil {
67
+ return err
68
+ }
69
+ }
70
+ if h.Response != nil {
71
+ err := h.Response.validate()
72
+ if err != nil {
73
+ return err
74
+ }
75
+ }
76
+ return nil
77
}
78
59
-func (h Headers) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
79
+func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
80
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
81
62
- apply(h.Request, r.Header, repl)
82
+ h.Request.applyTo(r.Header, repl)
83
84
// request header's Host is handled specially by the
85
// Go standard library, so if that header was changed,
@@ -79,22 +99,79 @@ func (h Headers) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt
99
headerOps: h.Response.HeaderOps,
100
}
101
} else {
82
- apply(h.Response.HeaderOps, w.Header(), repl)
102
+ h.Response.applyTo(w.Header(), repl)
103
}
104
}
105
+
106
return next.ServeHTTP(w, r)
107
}
108
88
-func apply(ops *HeaderOps, hdr http.Header, repl caddy.Replacer) {
109
+// HeaderOps defines some operations to
110
+// perform on HTTP headers.
111
+type HeaderOps struct {
112
+ Add http.Header `json:"add,omitempty"`
113
+ Set http.Header `json:"set,omitempty"`
114
+ Delete []string `json:"delete,omitempty"`
115
+ Replace map[string][]Replacement `json:"replace,omitempty"`
116
+}
117
+
118
+func (ops *HeaderOps) provision() error {
119
+ for fieldName, replacements := range ops.Replace {
120
+ for i, r := range replacements {
121
+ if r.SearchRegexp != "" {
122
+ re, err := regexp.Compile(r.SearchRegexp)
123
+ if err != nil {
124
+ return fmt.Errorf("replacement %d for header field '%s': %v", i, fieldName, err)
125
+ }
126
+ replacements[i].re = re
127
+ }
128
+ }
129
+ }
130
+ return nil
131
+}
132
+
133
+func (ops HeaderOps) validate() error {
134
+ for fieldName, replacements := range ops.Replace {
135
+ for _, r := range replacements {
136
+ if r.Search != "" && r.SearchRegexp != "" {
137
+ return fmt.Errorf("cannot specify both a substring search and a regular expression search for field '%s'", fieldName)
138
+ }
139
+ }
140
+ }
141
+ return nil
142
+}
143
+
144
+// Replacement describes a string replacement,
145
+// either a simple and fast sugbstring search
146
+// or a slower but more powerful regex search.
147
+type Replacement struct {
148
+ Search string `json:"search,omitempty"`
149
+ SearchRegexp string `json:"search_regexp,omitempty"`
150
+ Replace string `json:"replace,omitempty"`
151
+
152
+ re *regexp.Regexp
153
+}
154
+
155
+// RespHeaderOps is like HeaderOps, but
156
+// optionally deferred until response time.
157
+type RespHeaderOps struct {
158
+ *HeaderOps
159
+ Require *caddyhttp.ResponseMatcher `json:"require,omitempty"`
160
+ Deferred bool `json:"deferred,omitempty"`
161
+}
162
+
163
+func (ops *HeaderOps) applyTo(hdr http.Header, repl caddy.Replacer) {
164
if ops == nil {
165
return
166
}
167
+
168
for fieldName, vals := range ops.Add {
169
fieldName = repl.ReplaceAll(fieldName, "")
170
for _, v := range vals {
171
hdr.Add(fieldName, repl.ReplaceAll(v, ""))
172
}
173
}
174
+
175
for fieldName, vals := range ops.Set {
176
fieldName = repl.ReplaceAll(fieldName, "")
177
for i := range vals {
@@ -102,9 +179,45 @@ func apply(ops *HeaderOps, hdr http.Header, repl caddy.Replacer) {
179
}
180
hdr.Set(fieldName, strings.Join(vals, ","))
181
}
182
+
183
for _, fieldName := range ops.Delete {
184
hdr.Del(repl.ReplaceAll(fieldName, ""))
185
}
186
+
187
+ for fieldName, replacements := range ops.Replace {
188
+ fieldName = repl.ReplaceAll(fieldName, "")
189
+
190
+ // perform replacements across all fields
191
+ if fieldName == "*" {
192
+ for _, r := range replacements {
193
+ search := repl.ReplaceAll(r.Search, "")
194
+ replace := repl.ReplaceAll(r.Replace, "")
195
+ for fieldName, vals := range hdr {
196
+ for i := range vals {
197
+ if r.re != nil {
198
+ hdr[fieldName][i] = r.re.ReplaceAllString(hdr[fieldName][i], replace)
199
+ } else {
200
+ hdr[fieldName][i] = strings.ReplaceAll(hdr[fieldName][i], search, replace)
201
+ }
202
+ }
203
+ }
204
+ }
205
+ continue
206
+ }
207
+
208
+ // perform replacements only with the named field
209
+ for _, r := range replacements {
210
+ search := repl.ReplaceAll(r.Search, "")
211
+ replace := repl.ReplaceAll(r.Replace, "")
212
+ for i := range hdr[fieldName] {
213
+ if r.re != nil {
214
+ hdr[fieldName][i] = r.re.ReplaceAllString(hdr[fieldName][i], replace)
215
+ } else {
216
+ hdr[fieldName][i] = strings.ReplaceAll(hdr[fieldName][i], search, replace)
217
+ }
218
+ }
219
+ }
220
+ }
221
}
222
223
// responseWriterWrapper defers response header
@@ -123,7 +236,7 @@ func (rww *responseWriterWrapper) WriteHeader(status int) {
236
}
237
rww.wroteHeader = true
238
if rww.require == nil || rww.require.Match(status, rww.ResponseWriterWrapper.Header()) {
126
- apply(rww.headerOps, rww.ResponseWriterWrapper.Header(), rww.replacer)
239
+ rww.headerOps.applyTo(rww.ResponseWriterWrapper.Header(), rww.replacer)
240
}
241
rww.ResponseWriterWrapper.WriteHeader(status)
242
}
@@ -137,6 +250,7 @@ func (rww *responseWriterWrapper) Write(d []byte) (int, error) {
250
251
// Interface guards
252
var (
140
- _ caddyhttp.MiddlewareHandler = (*Headers)(nil)
253
+ _ caddy.Provisioner = (*Handler)(nil)
254
+ _ caddyhttp.MiddlewareHandler = (*Handler)(nil)
255
_ caddyhttp.HTTPInterfaces = (*responseWriterWrapper)(nil)
256
)