@marcel91200 / caddy Mirror · read-only
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 11, 01:00 UTC

master
go 436 lines 12.5 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package headers
16
17 import (
18 "fmt"
19 "net/http"
20 "regexp"
21 "sort"
22 "strings"
23
24 "github.com/caddyserver/caddy/v2"
25 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
26 )
27
28 func init() {
29 caddy.RegisterModule(Handler{})
30 }
31
32 // Handler is a middleware which modifies request and response headers.
33 //
34 // Changes to headers are applied immediately, except for the response
35 // headers when Deferred is true or when Required is set. In those cases,
36 // the changes are applied when the headers are written to the response.
37 // Note that deferred changes do not take effect if an error occurs later
38 // in the middleware chain.
39 //
40 // Properties in this module accept placeholders.
41 //
42 // Response header operations can be conditioned upon response status code
43 // and/or other header values.
44 type Handler struct {
45 Request *HeaderOps `json:"request,omitempty"`
46 Response *RespHeaderOps `json:"response,omitempty"`
47 }
48
49 // CaddyModule returns the Caddy module information.
50 func (Handler) CaddyModule() caddy.ModuleInfo {
51 return caddy.ModuleInfo{
52 ID: "http.handlers.headers",
53 New: func() caddy.Module { return new(Handler) },
54 }
55 }
56
57 // Provision sets up h's configuration.
58 func (h *Handler) Provision(ctx caddy.Context) error {
59 if h.Request != nil {
60 err := h.Request.Provision(ctx)
61 if err != nil {
62 return err
63 }
64 }
65 if h.Response != nil {
66 err := h.Response.Provision(ctx)
67 if err != nil {
68 return err
69 }
70 }
71 return nil
72 }
73
74 // Validate ensures h's configuration is valid.
75 func (h Handler) Validate() error {
76 if h.Request != nil {
77 err := h.Request.validate()
78 if err != nil {
79 return err
80 }
81 }
82 if h.Response != nil && h.Response.HeaderOps != nil {
83 err := h.Response.validate()
84 if err != nil {
85 return err
86 }
87 }
88 return nil
89 }
90
91 func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
92 repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
93
94 if h.Request != nil {
95 h.Request.ApplyToRequest(r)
96 }
97
98 if h.Response != nil {
99 if h.Response.Deferred || h.Response.Require != nil {
100 w = &responseWriterWrapper{
101 ResponseWriterWrapper: &caddyhttp.ResponseWriterWrapper{ResponseWriter: w},
102 replacer: repl,
103 require: h.Response.Require,
104 headerOps: h.Response.HeaderOps,
105 }
106 } else {
107 h.Response.ApplyTo(w.Header(), repl)
108 }
109 }
110
111 return next.ServeHTTP(w, r)
112 }
113
114 // HeaderOps defines manipulations for HTTP headers.
115 type HeaderOps struct {
116 // Adds HTTP headers; does not replace any existing header fields.
117 Add http.Header `json:"add,omitempty"`
118
119 // Sets HTTP headers; replaces existing header fields.
120 Set http.Header `json:"set,omitempty"`
121
122 // Names of HTTP header fields to delete. Basic wildcards are supported:
123 //
124 // - Start with `*` for all field names with the given suffix;
125 // - End with `*` for all field names with the given prefix;
126 // - Start and end with `*` for all field names containing a substring.
127 Delete []string `json:"delete,omitempty"`
128
129 // Performs in-situ substring replacements of HTTP headers.
130 // Keys are the field names on which to perform the associated replacements.
131 // If the field name is `*`, the replacements are performed on all header fields.
132 Replace map[string][]Replacement `json:"replace,omitempty"`
133 }
134
135 // Provision sets up the header operations.
136 func (ops *HeaderOps) Provision(_ caddy.Context) error {
137 if ops == nil {
138 return nil // it's possible no ops are configured; fix #6893
139 }
140 if len(ops.Set) > 0 {
141 set := make(http.Header, len(ops.Set))
142 var fields []string
143 for fieldName := range ops.Set {
144 fields = append(fields, fieldName)
145 }
146 sort.Strings(fields)
147 for _, fieldName := range fields {
148 canonical := http.CanonicalHeaderKey(fieldName)
149 set[canonical] = append(set[canonical], ops.Set[fieldName]...)
150 }
151 ops.Set = set
152 }
153 for fieldName, replacements := range ops.Replace {
154 for i, r := range replacements {
155 if r.SearchRegexp == "" {
156 continue
157 }
158
159 // Check if it contains placeholders
160 if containsPlaceholders(r.SearchRegexp) {
161 // Contains placeholders, skips precompilation, and recompiles at runtime
162 continue
163 }
164
165 // Does not contain placeholders, safe to precompile
166 re, err := regexp.Compile(r.SearchRegexp)
167 if err != nil {
168 return fmt.Errorf("replacement %d for header field '%s': %v", i, fieldName, err)
169 }
170 replacements[i].re = re
171 }
172 }
173 return nil
174 }
175
176 // containsPlaceholders checks if the string contains Caddy placeholder syntax {key}
177 func containsPlaceholders(s string) bool {
178 _, after, ok := strings.Cut(s, "{")
179 if !ok {
180 return false
181 }
182 closeIdx := strings.Index(after, "}")
183 if closeIdx == -1 {
184 return false
185 }
186 // Make sure there is content between the brackets
187 return closeIdx > 0
188 }
189
190 func (ops HeaderOps) validate() error {
191 for fieldName, replacements := range ops.Replace {
192 for _, r := range replacements {
193 if r.Search != "" && r.SearchRegexp != "" {
194 return fmt.Errorf("cannot specify both a substring search and a regular expression search for field '%s'", fieldName)
195 }
196 }
197 }
198 return nil
199 }
200
201 // Replacement describes a string replacement,
202 // either a simple and fast substring search
203 // or a slower but more powerful regex search.
204 type Replacement struct {
205 // The substring to search for.
206 Search string `json:"search,omitempty"`
207
208 // The regular expression to search with.
209 SearchRegexp string `json:"search_regexp,omitempty"`
210
211 // The string with which to replace matches.
212 Replace string `json:"replace,omitempty"`
213
214 re *regexp.Regexp
215 }
216
217 // RespHeaderOps defines manipulations for response headers.
218 type RespHeaderOps struct {
219 *HeaderOps
220
221 // If set, header operations will be deferred until
222 // they are written out and only performed if the
223 // response matches these criteria.
224 Require *caddyhttp.ResponseMatcher `json:"require,omitempty"`
225
226 // If true, header operations will be deferred until
227 // they are written out. Superseded if Require is set.
228 // Usually you will need to set this to true if any
229 // fields are being deleted.
230 Deferred bool `json:"deferred,omitempty"`
231 }
232
233 // ApplyTo applies ops to hdr using repl.
234 func (ops *HeaderOps) ApplyTo(hdr http.Header, repl *caddy.Replacer) {
235 if ops == nil {
236 return
237 }
238 // before manipulating headers in other ways, check if there
239 // is configuration to delete all headers, and do that first
240 // because if a header is to be added, we don't want to delete
241 // it also
242 for _, fieldName := range ops.Delete {
243 fieldName = repl.ReplaceKnown(fieldName, "")
244 if fieldName == "*" {
245 clear(hdr)
246 }
247 }
248
249 // add
250 for fieldName, vals := range ops.Add {
251 fieldName = repl.ReplaceKnown(fieldName, "")
252 for _, v := range vals {
253 hdr.Add(fieldName, repl.ReplaceKnown(v, ""))
254 }
255 }
256
257 // set
258 for fieldName, vals := range ops.Set {
259 if expanded := repl.ReplaceKnown(fieldName, ""); expanded != fieldName {
260 fieldName = http.CanonicalHeaderKey(expanded)
261 }
262 newVals := make([]string, len(vals))
263 for i := range vals {
264 newVals[i] = repl.ReplaceKnown(vals[i], "")
265 }
266 if fieldName == "Set-Cookie" && len(newVals) > 1 {
267 hdr.Del(fieldName)
268 for _, val := range newVals {
269 hdr.Add(fieldName, val)
270 }
271 continue
272 }
273 hdr.Set(fieldName, strings.Join(newVals, ","))
274 }
275
276 // delete
277 for _, fieldName := range ops.Delete {
278 fieldName = strings.ToLower(repl.ReplaceKnown(fieldName, ""))
279 if fieldName == "*" {
280 continue // handled above
281 }
282 switch {
283 case strings.HasPrefix(fieldName, "*") && strings.HasSuffix(fieldName, "*"):
284 for existingField := range hdr {
285 if strings.Contains(strings.ToLower(existingField), fieldName[1:len(fieldName)-1]) {
286 delete(hdr, existingField)
287 }
288 }
289 case strings.HasPrefix(fieldName, "*"):
290 for existingField := range hdr {
291 if strings.HasSuffix(strings.ToLower(existingField), fieldName[1:]) {
292 delete(hdr, existingField)
293 }
294 }
295 case strings.HasSuffix(fieldName, "*"):
296 for existingField := range hdr {
297 if strings.HasPrefix(strings.ToLower(existingField), fieldName[:len(fieldName)-1]) {
298 delete(hdr, existingField)
299 }
300 }
301 default:
302 hdr.Del(fieldName)
303 }
304 }
305
306 // replace
307 for fieldName, replacements := range ops.Replace {
308 fieldName = http.CanonicalHeaderKey(repl.ReplaceKnown(fieldName, ""))
309
310 // all fields...
311 if fieldName == "*" {
312 for _, r := range replacements {
313 search := repl.ReplaceKnown(r.Search, "")
314 replace := repl.ReplaceKnown(r.Replace, "")
315 for fieldName, vals := range hdr {
316 for i := range vals {
317 if r.re != nil {
318 // Use precompiled regular expressions
319 hdr[fieldName][i] = r.re.ReplaceAllString(hdr[fieldName][i], replace)
320 } else if r.SearchRegexp != "" {
321 // Runtime compilation of regular expressions
322 searchRegexp := repl.ReplaceKnown(r.SearchRegexp, "")
323 if re, err := regexp.Compile(searchRegexp); err == nil {
324 hdr[fieldName][i] = re.ReplaceAllString(hdr[fieldName][i], replace)
325 }
326 // If compilation fails, skip this replacement
327 } else {
328 hdr[fieldName][i] = strings.ReplaceAll(hdr[fieldName][i], search, replace)
329 }
330 }
331 }
332 }
333 continue
334 }
335
336 // ...or only with the named field
337 for _, r := range replacements {
338 search := repl.ReplaceKnown(r.Search, "")
339 replace := repl.ReplaceKnown(r.Replace, "")
340 for hdrFieldName, vals := range hdr {
341 // see issue #4330 for why we don't simply use hdr[fieldName]
342 if http.CanonicalHeaderKey(hdrFieldName) != fieldName {
343 continue
344 }
345 for i := range vals {
346 if r.re != nil {
347 hdr[hdrFieldName][i] = r.re.ReplaceAllString(hdr[hdrFieldName][i], replace)
348 } else if r.SearchRegexp != "" {
349 searchRegexp := repl.ReplaceKnown(r.SearchRegexp, "")
350 if re, err := regexp.Compile(searchRegexp); err == nil {
351 hdr[hdrFieldName][i] = re.ReplaceAllString(hdr[hdrFieldName][i], replace)
352 }
353 } else {
354 hdr[hdrFieldName][i] = strings.ReplaceAll(hdr[hdrFieldName][i], search, replace)
355 }
356 }
357 }
358 }
359 }
360 }
361
362 // ApplyToRequest applies ops to r, specially handling the Host
363 // header which the standard library does not include with the
364 // header map with all the others. This method mutates r.Host.
365 func (ops HeaderOps) ApplyToRequest(r *http.Request) {
366 repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
367
368 // capture the current Host header so we can
369 // reset to it when we're done
370 origHost, hadHost := r.Header["Host"]
371
372 // append r.Host; this way, we know that our value
373 // was last in the list, and if an Add operation
374 // appended something else after it, that's probably
375 // fine because it's weird to have multiple Host
376 // headers anyway and presumably the one they added
377 // is the one they wanted
378 r.Header["Host"] = append(r.Header["Host"], r.Host)
379
380 // apply header operations
381 ops.ApplyTo(r.Header, repl)
382
383 // retrieve the last Host value (likely the one we appended)
384 if len(r.Header["Host"]) > 0 {
385 r.Host = r.Header["Host"][len(r.Header["Host"])-1]
386 } else {
387 r.Host = ""
388 }
389
390 // reset the Host header slice
391 if hadHost {
392 r.Header["Host"] = origHost
393 } else {
394 delete(r.Header, "Host")
395 }
396 }
397
398 // responseWriterWrapper defers response header
399 // operations until WriteHeader is called.
400 type responseWriterWrapper struct {
401 *caddyhttp.ResponseWriterWrapper
402 replacer *caddy.Replacer
403 require *caddyhttp.ResponseMatcher
404 headerOps *HeaderOps
405 wroteHeader bool
406 }
407
408 func (rww *responseWriterWrapper) WriteHeader(status int) {
409 if rww.wroteHeader {
410 return
411 }
412 // 1xx responses aren't final; just informational
413 if status < 100 || status > 199 {
414 rww.wroteHeader = true
415 }
416 if rww.require == nil || rww.require.Match(status, rww.ResponseWriterWrapper.Header()) {
417 if rww.headerOps != nil {
418 rww.headerOps.ApplyTo(rww.ResponseWriterWrapper.Header(), rww.replacer)
419 }
420 }
421 rww.ResponseWriterWrapper.WriteHeader(status)
422 }
423
424 func (rww *responseWriterWrapper) Write(d []byte) (int, error) {
425 if !rww.wroteHeader {
426 rww.WriteHeader(http.StatusOK)
427 }
428 return rww.ResponseWriterWrapper.Write(d)
429 }
430
431 // Interface guards
432 var (
433 _ caddy.Provisioner = (*Handler)(nil)
434 _ caddyhttp.MiddlewareHandler = (*Handler)(nil)
435 _ http.ResponseWriter = (*responseWriterWrapper)(nil)
436 )