@marcel91200 / caddy Mirror · read-only / commits / c09e86fd
Fork 0

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 )