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
| 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 | "reflect" |
| 21 | "strings" |
| 22 | |
| 23 | "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" |
| 24 | "github.com/caddyserver/caddy/v2/modules/caddyhttp" |
| 25 | ) |
| 26 | |
| 27 | func init() { |
| 28 | httpcaddyfile.RegisterDirective("header", parseCaddyfile) |
| 29 | httpcaddyfile.RegisterDirective("request_header", parseReqHdrCaddyfile) |
| 30 | } |
| 31 | |
| 32 | // parseCaddyfile sets up the handler for response headers from |
| 33 | // Caddyfile tokens. Syntax: |
| 34 | // |
| 35 | // header [<matcher>] [[+|-|?|>]<field> [<value|regexp>] [<replacement>]] { |
| 36 | // [+]<field> [<value|regexp> [<replacement>]] |
| 37 | // ?<field> <default_value> |
| 38 | // -<field> |
| 39 | // ><field> |
| 40 | // [defer] |
| 41 | // } |
| 42 | // |
| 43 | // Either a block can be opened or a single header field can be configured |
| 44 | // in the first line, but not both in the same directive. Header operations |
| 45 | // are deferred to write-time if any headers are being deleted or if the |
| 46 | // 'defer' subdirective is used. + appends a header value, - deletes a field, |
| 47 | // ? conditionally sets a value only if the header field is not already set, |
| 48 | // and > sets a field with defer enabled. |
| 49 | func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { |
| 50 | h.Next() // consume directive name |
| 51 | matcherSet, err := h.ExtractMatcherSet() |
| 52 | if err != nil { |
| 53 | return nil, err |
| 54 | } |
| 55 | h.Next() // consume the directive name again (matcher parsing resets) |
| 56 | |
| 57 | makeHandler := func() Handler { |
| 58 | return Handler{ |
| 59 | Response: &RespHeaderOps{ |
| 60 | HeaderOps: &HeaderOps{}, |
| 61 | }, |
| 62 | } |
| 63 | } |
| 64 | handler := makeHandler() |
| 65 | |
| 66 | // one handler per '?' default, each requiring only its own field to be absent |
| 67 | var defaultHandlers []Handler |
| 68 | applyOp := func(field, value string, replacement *string) error { |
| 69 | if !strings.HasPrefix(field, "?") { |
| 70 | return applyHeaderOp(handler.Response.HeaderOps, handler.Response, field, value, replacement) |
| 71 | } |
| 72 | defaultHandler := makeHandler() |
| 73 | defaultHandlers = append(defaultHandlers, defaultHandler) |
| 74 | return applyHeaderOp(defaultHandler.Response.HeaderOps, defaultHandler.Response, field, value, replacement) |
| 75 | } |
| 76 | var responseMatcher *caddyhttp.ResponseMatcher |
| 77 | |
| 78 | // first see if headers are in the initial line |
| 79 | var hasArgs bool |
| 80 | if h.NextArg() { |
| 81 | hasArgs = true |
| 82 | field := h.Val() |
| 83 | var value string |
| 84 | var replacement *string |
| 85 | if h.NextArg() { |
| 86 | value = h.Val() |
| 87 | } |
| 88 | if h.NextArg() { |
| 89 | arg := h.Val() |
| 90 | replacement = &arg |
| 91 | } |
| 92 | err := applyOp(field, value, replacement) |
| 93 | if err != nil { |
| 94 | return nil, h.Err(err.Error()) |
| 95 | } |
| 96 | if len(handler.Response.HeaderOps.Delete) > 0 { |
| 97 | handler.Response.Deferred = true |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | // if not, they should be in a block |
| 102 | for h.NextBlock(0) { |
| 103 | field := h.Val() |
| 104 | if field == "defer" { |
| 105 | handler.Response.Deferred = true |
| 106 | continue |
| 107 | } |
| 108 | if field == "match" { |
| 109 | responseMatchers := make(map[string]caddyhttp.ResponseMatcher) |
| 110 | err := caddyhttp.ParseNamedResponseMatcher(h.NewFromNextSegment(), responseMatchers) |
| 111 | if err != nil { |
| 112 | return nil, err |
| 113 | } |
| 114 | matcher := responseMatchers["match"] |
| 115 | responseMatcher = &matcher |
| 116 | continue |
| 117 | } |
| 118 | if hasArgs { |
| 119 | return nil, h.Err("cannot specify headers in both arguments and block") // because it would be weird |
| 120 | } |
| 121 | |
| 122 | // sometimes it is habitual for users to suffix a field name with a colon, |
| 123 | // as if they were writing a curl command or something; see |
| 124 | // https://caddy.community/t/v2-reverse-proxy-please-add-cors-example-to-the-docs/7349/19 |
| 125 | field = strings.TrimSuffix(field, ":") |
| 126 | |
| 127 | var value string |
| 128 | var replacement *string |
| 129 | if h.NextArg() { |
| 130 | value = h.Val() |
| 131 | } |
| 132 | if h.NextArg() { |
| 133 | arg := h.Val() |
| 134 | replacement = &arg |
| 135 | } |
| 136 | |
| 137 | err := applyOp(field, value, replacement) |
| 138 | if err != nil { |
| 139 | return nil, h.Err(err.Error()) |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | // the response matcher also applies to the defaults; a default already |
| 144 | // requires its own field to be absent, so the matcher can only ask the same |
| 145 | if responseMatcher != nil { |
| 146 | handler.Response.Require = responseMatcher |
| 147 | for _, defaultHandler := range defaultHandlers { |
| 148 | require := defaultHandler.Response.Require |
| 149 | require.StatusCode = responseMatcher.StatusCode |
| 150 | for field, vals := range responseMatcher.Headers { |
| 151 | if _, ok := defaultHandler.Response.Set[http.CanonicalHeaderKey(field)]; ok { |
| 152 | if vals == nil { |
| 153 | continue |
| 154 | } |
| 155 | return nil, h.Errf("cannot match on response header %s and also set a default for it", http.CanonicalHeaderKey(field)) |
| 156 | } |
| 157 | require.Headers[field] = vals |
| 158 | } |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | var configValues []httpcaddyfile.ConfigValue |
| 163 | if !reflect.DeepEqual(handler, makeHandler()) { |
| 164 | configValues = append(configValues, h.NewRoute(matcherSet, handler)...) |
| 165 | } |
| 166 | for _, defaultHandler := range defaultHandlers { |
| 167 | configValues = append(configValues, h.NewRoute(matcherSet, defaultHandler)...) |
| 168 | } |
| 169 | |
| 170 | return configValues, nil |
| 171 | } |
| 172 | |
| 173 | // parseReqHdrCaddyfile sets up the handler for request headers |
| 174 | // from Caddyfile tokens. Syntax: |
| 175 | // |
| 176 | // request_header [<matcher>] [[+|-]<field> [<value|regexp>] [<replacement>]] |
| 177 | func parseReqHdrCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { |
| 178 | h.Next() // consume directive name |
| 179 | matcherSet, err := h.ExtractMatcherSet() |
| 180 | if err != nil { |
| 181 | return nil, err |
| 182 | } |
| 183 | h.Next() // consume the directive name again (matcher parsing resets) |
| 184 | |
| 185 | if !h.NextArg() { |
| 186 | return nil, h.ArgErr() |
| 187 | } |
| 188 | field := h.Val() |
| 189 | |
| 190 | hdr := Handler{ |
| 191 | Request: &HeaderOps{}, |
| 192 | } |
| 193 | |
| 194 | // sometimes it is habitual for users to suffix a field name with a colon, |
| 195 | // as if they were writing a curl command or something; see |
| 196 | // https://caddy.community/t/v2-reverse-proxy-please-add-cors-example-to-the-docs/7349/19 |
| 197 | field = strings.TrimSuffix(field, ":") |
| 198 | |
| 199 | var value string |
| 200 | var replacement *string |
| 201 | if h.NextArg() { |
| 202 | value = h.Val() |
| 203 | } |
| 204 | if h.NextArg() { |
| 205 | arg := h.Val() |
| 206 | replacement = &arg |
| 207 | if h.NextArg() { |
| 208 | return nil, h.ArgErr() |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | if hdr.Request == nil { |
| 213 | hdr.Request = new(HeaderOps) |
| 214 | } |
| 215 | if err := CaddyfileHeaderOp(hdr.Request, field, value, replacement); err != nil { |
| 216 | return nil, h.Err(err.Error()) |
| 217 | } |
| 218 | |
| 219 | configValues := h.NewRoute(matcherSet, hdr) |
| 220 | |
| 221 | if h.NextArg() { |
| 222 | return nil, h.ArgErr() |
| 223 | } |
| 224 | return configValues, nil |
| 225 | } |
| 226 | |
| 227 | // CaddyfileHeaderOp applies a new header operation according to |
| 228 | // field, value, and replacement. The field can be prefixed with |
| 229 | // "+" or "-" to specify adding or removing; otherwise, the value |
| 230 | // will be set (overriding any previous value). If replacement is |
| 231 | // non-nil, value will be treated as a regular expression which |
| 232 | // will be used to search and then replacement will be used to |
| 233 | // complete the substring replacement; in that case, any + or - |
| 234 | // prefix to field will be ignored. |
| 235 | func CaddyfileHeaderOp(ops *HeaderOps, field, value string, replacement *string) error { |
| 236 | return applyHeaderOp(ops, nil, field, value, replacement) |
| 237 | } |
| 238 | |
| 239 | func applyHeaderOp(ops *HeaderOps, respHeaderOps *RespHeaderOps, field, value string, replacement *string) error { |
| 240 | switch { |
| 241 | case strings.HasPrefix(field, "+"): // append |
| 242 | if ops.Add == nil { |
| 243 | ops.Add = make(http.Header) |
| 244 | } |
| 245 | ops.Add.Add(field[1:], value) |
| 246 | |
| 247 | case strings.HasPrefix(field, "-"): // delete |
| 248 | ops.Delete = append(ops.Delete, field[1:]) |
| 249 | if respHeaderOps != nil { |
| 250 | respHeaderOps.Deferred = true |
| 251 | } |
| 252 | |
| 253 | case strings.HasPrefix(field, "?"): // default (conditional on not existing) - response headers only |
| 254 | if respHeaderOps == nil { |
| 255 | return fmt.Errorf("%v: the default header modifier ('?') can only be used on response headers; for conditional manipulation of request headers, use matchers", field) |
| 256 | } |
| 257 | if respHeaderOps.Require == nil { |
| 258 | respHeaderOps.Require = &caddyhttp.ResponseMatcher{ |
| 259 | Headers: make(http.Header), |
| 260 | } |
| 261 | } |
| 262 | field = strings.TrimPrefix(field, "?") |
| 263 | respHeaderOps.Require.Headers[field] = nil |
| 264 | if respHeaderOps.Set == nil { |
| 265 | respHeaderOps.Set = make(http.Header) |
| 266 | } |
| 267 | respHeaderOps.Set.Set(field, value) |
| 268 | |
| 269 | case replacement != nil: // replace |
| 270 | // allow defer shortcut for replace syntax |
| 271 | if strings.HasPrefix(field, ">") && respHeaderOps != nil { |
| 272 | respHeaderOps.Deferred = true |
| 273 | } |
| 274 | if ops.Replace == nil { |
| 275 | ops.Replace = make(map[string][]Replacement) |
| 276 | } |
| 277 | field = strings.TrimLeft(field, "+-?>") |
| 278 | ops.Replace[field] = append( |
| 279 | ops.Replace[field], |
| 280 | Replacement{ |
| 281 | SearchRegexp: value, |
| 282 | Replace: *replacement, |
| 283 | }, |
| 284 | ) |
| 285 | |
| 286 | case strings.HasPrefix(field, ">"): // set (overwrite) with defer |
| 287 | if ops.Set == nil { |
| 288 | ops.Set = make(http.Header) |
| 289 | } |
| 290 | ops.Set.Set(field[1:], value) |
| 291 | if respHeaderOps != nil { |
| 292 | respHeaderOps.Deferred = true |
| 293 | } |
| 294 | |
| 295 | default: // set (overwrite) |
| 296 | if ops.Set == nil { |
| 297 | ops.Set = make(http.Header) |
| 298 | } |
| 299 | ops.Set.Set(field, value) |
| 300 | } |
| 301 | |
| 302 | return nil |
| 303 | } |