@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 303 lines 8.9 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 "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 }