@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 1,938 lines 62.7 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 caddyhttp
16
17 import (
18 "encoding/json"
19 "errors"
20 "fmt"
21 "net"
22 "net/http"
23 "net/textproto"
24 "net/url"
25 "path"
26 "regexp"
27 "runtime"
28 "slices"
29 "sort"
30 "strconv"
31 "strings"
32 "unicode/utf8"
33
34 "cel.dev/cel-go/cel"
35 "cel.dev/cel-go/common"
36 "cel.dev/cel-go/common/ast"
37 "cel.dev/cel-go/common/types"
38 "cel.dev/cel-go/common/types/ref"
39 "cel.dev/cel-go/parser"
40 "golang.org/x/net/idna"
41
42 "github.com/caddyserver/caddy/v2"
43 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
44 )
45
46 type (
47 // MatchHost matches requests by the Host value (case-insensitive).
48 //
49 // When used in a top-level HTTP route,
50 // [qualifying domain names](/docs/automatic-https#hostname-requirements)
51 // may trigger [automatic HTTPS](/docs/automatic-https), which automatically
52 // provisions and renews certificates for you. Before doing this, you
53 // should ensure that DNS records for these domains are properly configured,
54 // especially A/AAAA pointed at your server.
55 //
56 // Automatic HTTPS can be
57 // [customized or disabled](/docs/modules/http#servers/automatic_https).
58 //
59 // Wildcards (`*`) may be used to represent exactly one label of the
60 // hostname, in accordance with RFC 1034 (because host matchers are also
61 // used for automatic HTTPS which influences TLS certificates). Thus,
62 // a host of `*` matches hosts like `localhost` or `internal` but not
63 // `example.com`. To catch all hosts, omit the host matcher entirely.
64 //
65 // The wildcard can be useful for matching all subdomains, for example:
66 // `*.example.com` matches `foo.example.com` but not `foo.bar.example.com`.
67 //
68 // Duplicate entries will return an error.
69 MatchHost []string
70
71 // MatchPath case-insensitively matches requests by the URI's path. Path
72 // matching is exact, not prefix-based, giving you more control and clarity
73 // over matching. Wildcards (`*`) may be used:
74 //
75 // - At the end only, for a prefix match (`/prefix/*`)
76 // - At the beginning only, for a suffix match (`*.suffix`)
77 // - On both sides only, for a substring match (`*/contains/*`)
78 // - In the middle, for a globular match (`/accounts/*/info`)
79 //
80 // Slashes are significant; i.e. `/foo*` matches `/foo`, `/foo/`, `/foo/bar`,
81 // and `/foobar`; but `/foo/*` does not match `/foo` or `/foobar`. Valid
82 // paths start with a slash `/`.
83 //
84 // Because there are, in general, multiple possible escaped forms of any
85 // path, path matchers operate in unescaped space; that is, path matchers
86 // should be written in their unescaped form to prevent ambiguities and
87 // possible security issues, as all request paths will be normalized to
88 // their unescaped forms before matcher evaluation.
89 //
90 // However, escape sequences in a match pattern are supported; they are
91 // compared with the request's raw/escaped path for those bytes only.
92 // In other words, a matcher of `/foo%2Fbar` will match a request path
93 // of precisely `/foo%2Fbar`, but not `/foo/bar`. It follows that matching
94 // the literal percent sign (%) in normalized space can be done using the
95 // escaped form, `%25`.
96 //
97 // Even though wildcards (`*`) operate in the normalized space, the special
98 // escaped wildcard (`%*`), which is not a valid escape sequence, may be
99 // used in place of a span that should NOT be decoded; that is, `/bands/%*/`
100 // will match `/bands/AC%2fDC/` whereas `/bands/*/` will not. Note that a
101 // pattern ending in a bare trailing wildcard, such as `/bands/*`, is a
102 // prefix match in normalized space, so it does match `/bands/AC%2fDC`.
103 //
104 // This matcher is fast, so it does not support regular expressions or
105 // capture groups. For slower but more powerful matching, use the
106 // path_regexp matcher. (Note that due to the special treatment of
107 // escape sequences in matcher patterns, they may perform slightly slower
108 // in high-traffic environments.)
109 MatchPath []string
110
111 // MatchPathRE matches requests by a regular expression on the URI's path.
112 // Path matching is performed in the unescaped (decoded) form of the path.
113 //
114 // Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}`
115 // where `name` is the regular expression's name, and `capture_group` is either
116 // the named or positional capture group from the expression itself. If no name
117 // is given, then the placeholder omits the name: `{http.regexp.capture_group}`
118 // (potentially leading to collisions).
119 MatchPathRE struct{ MatchRegexp }
120
121 // MatchMethod matches requests by the method.
122 MatchMethod []string
123
124 // MatchQuery matches requests by the URI's query string. It takes a JSON object
125 // keyed by the query keys, with an array of string values to match for that key.
126 // Query key matches are exact, but wildcards may be used for value matches. Both
127 // keys and values may be placeholders.
128 //
129 // An example of the structure to match `?key=value&topic=api&query=something` is:
130 //
131 // ```json
132 // {
133 // "key": ["value"],
134 // "topic": ["api"],
135 // "query": ["*"]
136 // }
137 // ```
138 //
139 // Invalid query strings, including those with bad escapings or illegal characters
140 // like semicolons, will fail to parse and thus fail to match.
141 //
142 // **NOTE:** Notice that query string values are arrays, not singular values. This is
143 // because repeated keys are valid in query strings, and each one may have a
144 // different value. This matcher will match for a key if any one of its configured
145 // values is assigned in the query string. Backend applications relying on query
146 // strings MUST take into consideration that query string values are arrays and can
147 // have multiple values.
148 MatchQuery url.Values
149
150 // MatchHeader matches requests by header fields. The key is the field
151 // name and the array is the list of field values. It performs fast,
152 // exact string comparisons of the field values. Fast prefix, suffix,
153 // and substring matches can also be done by suffixing, prefixing, or
154 // surrounding the value with the wildcard `*` character, respectively.
155 // If a list is null, the header must not exist. If the list is empty,
156 // the field must simply exist, regardless of its value.
157 //
158 // **NOTE:** Notice that header values are arrays, not singular values. This is
159 // because repeated fields are valid in headers, and each one may have a
160 // different value. This matcher will match for a field if any one of its configured
161 // values matches in the header. Backend applications relying on headers MUST take
162 // into consideration that header field values are arrays and can have multiple
163 // values.
164 MatchHeader http.Header
165
166 // MatchHeaderRE matches requests by a regular expression on header fields.
167 //
168 // Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}`
169 // where `name` is the regular expression's name, and `capture_group` is either
170 // the named or positional capture group from the expression itself. If no name
171 // is given, then the placeholder omits the name: `{http.regexp.capture_group}`
172 // (potentially leading to collisions).
173 MatchHeaderRE map[string]*MatchRegexp
174
175 // MatchProtocol matches requests by protocol. Recognized values are
176 // "http", "https", and "grpc" for broad protocol matches, or specific
177 // HTTP versions can be specified like so: "http/1", "http/1.1",
178 // "http/2", "http/3", or minimum versions: "http/2+", etc.
179 MatchProtocol string
180
181 // MatchTLS matches HTTP requests based on the underlying
182 // TLS connection state. If this matcher is specified but
183 // the request did not come over TLS, it will never match.
184 // If this matcher is specified but is empty and the request
185 // did come in over TLS, it will always match.
186 MatchTLS struct {
187 // Matches if the TLS handshake has completed. QUIC 0-RTT early
188 // data may arrive before the handshake completes. Generally, it
189 // is unsafe to replay these requests if they are not idempotent;
190 // additionally, the remote IP of early data packets can more
191 // easily be spoofed. It is conventional to respond with HTTP 425
192 // Too Early if the request cannot risk being processed in this
193 // state.
194 HandshakeComplete *bool `json:"handshake_complete,omitempty"`
195 }
196
197 // MatchNot matches requests by negating the results of its matcher
198 // sets. A single "not" matcher takes one or more matcher sets. Each
199 // matcher set is OR'ed; in other words, if any matcher set returns
200 // true, the final result of the "not" matcher is false. Individual
201 // matchers within a set work the same (i.e. different matchers in
202 // the same set are AND'ed).
203 //
204 // NOTE: The generated docs which describe the structure of this
205 // module are wrong because of how this type unmarshals JSON in a
206 // custom way. The correct structure is:
207 //
208 // ```json
209 // [
210 // {},
211 // {}
212 // ]
213 // ```
214 //
215 // where each of the array elements is a matcher set, i.e. an
216 // object keyed by matcher name.
217 MatchNot struct {
218 MatcherSetsRaw []caddy.ModuleMap `json:"-" caddy:"namespace=http.matchers"`
219 MatcherSets []MatcherSet `json:"-"`
220 }
221 )
222
223 func init() {
224 caddy.RegisterModule(MatchHost{})
225 caddy.RegisterModule(MatchPath{})
226 caddy.RegisterModule(MatchPathRE{})
227 caddy.RegisterModule(MatchMethod{})
228 caddy.RegisterModule(MatchQuery{})
229 caddy.RegisterModule(MatchHeader{})
230 caddy.RegisterModule(MatchHeaderRE{})
231 caddy.RegisterModule(new(MatchProtocol))
232 caddy.RegisterModule(MatchTLS{})
233 caddy.RegisterModule(MatchNot{})
234 }
235
236 // CaddyModule returns the Caddy module information.
237 func (MatchHost) CaddyModule() caddy.ModuleInfo {
238 return caddy.ModuleInfo{
239 ID: "http.matchers.host",
240 New: func() caddy.Module { return new(MatchHost) },
241 }
242 }
243
244 // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
245 func (m *MatchHost) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
246 // iterate to merge multiple matchers into one
247 for d.Next() {
248 *m = append(*m, d.RemainingArgs()...)
249 if d.NextBlock(0) {
250 return d.Err("malformed host matcher: blocks are not supported")
251 }
252 }
253 return nil
254 }
255
256 // Provision sets up and validates m, including making it more efficient for large lists.
257 func (m MatchHost) Provision(_ caddy.Context) error {
258 // check for duplicates; they are nonsensical and reduce efficiency
259 // (we could just remove them, but the user should know their config is erroneous)
260 seen := make(map[string]int, len(m))
261 for i, host := range m {
262 asciiHost, err := idna.ToASCII(host)
263 if err != nil {
264 return fmt.Errorf("converting hostname '%s' to ASCII: %v", host, err)
265 }
266 normalizedHost := strings.ToLower(asciiHost)
267 if firstI, ok := seen[normalizedHost]; ok {
268 return fmt.Errorf("host at index %d is repeated at index %d: %s", firstI, i, host)
269 }
270 // Normalize exact hosts for standardized comparison in large-list fastpath later on.
271 // Keep wildcards/placeholders untouched.
272 if m.fuzzy(asciiHost) {
273 m[i] = asciiHost
274 } else {
275 m[i] = normalizedHost
276 }
277 seen[normalizedHost] = i
278 }
279
280 if m.large() {
281 // sort the slice lexicographically, grouping "fuzzy" entries (wildcards and placeholders)
282 // at the front of the list; this allows us to use binary search for exact matches, which
283 // we have seen from experience is the most common kind of value in large lists; and any
284 // other kinds of values (wildcards and placeholders) are grouped in front so the linear
285 // search should find a match fairly quickly
286 sort.Slice(m, func(i, j int) bool {
287 iInexact, jInexact := m.fuzzy(m[i]), m.fuzzy(m[j])
288 if iInexact && !jInexact {
289 return true
290 }
291 if !iInexact && jInexact {
292 return false
293 }
294 return m[i] < m[j]
295 })
296 }
297
298 return nil
299 }
300
301 // Match returns true if r matches m.
302 func (m MatchHost) Match(r *http.Request) bool {
303 match, _ := m.MatchWithError(r)
304 return match
305 }
306
307 // MatchWithError returns true if r matches m.
308 func (m MatchHost) MatchWithError(r *http.Request) (bool, error) {
309 reqHost, _, err := net.SplitHostPort(r.Host)
310 if err != nil {
311 // OK; probably didn't have a port
312 reqHost = r.Host
313
314 // make sure we strip the brackets from IPv6 addresses
315 reqHost = strings.TrimPrefix(reqHost, "[")
316 reqHost = strings.TrimSuffix(reqHost, "]")
317 }
318
319 if m.large() {
320 reqHostLower := strings.ToLower(reqHost)
321 // fast path: locate exact match using binary search (about 100-1000x faster for large lists)
322 pos := sort.Search(len(m), func(i int) bool {
323 if m.fuzzy(m[i]) {
324 return false
325 }
326 return m[i] >= reqHostLower
327 })
328 if pos < len(m) && m[pos] == reqHostLower {
329 return true, nil
330 }
331 }
332
333 repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
334
335 outer:
336 for _, host := range m {
337 // fast path: if matcher is large, we already know we don't have an exact
338 // match, so we're only looking for fuzzy match now, which should be at the
339 // front of the list; if we have reached a value that is not fuzzy, there
340 // will be no match and we can short-circuit for efficiency
341 if m.large() && !m.fuzzy(host) {
342 break
343 }
344
345 host = repl.ReplaceAll(host, "")
346 if strings.Contains(host, "*") {
347 patternParts := strings.Split(host, ".")
348 incomingParts := strings.Split(reqHost, ".")
349 if len(patternParts) != len(incomingParts) {
350 continue
351 }
352 for i := range patternParts {
353 if patternParts[i] == "*" {
354 continue
355 }
356 if !strings.EqualFold(patternParts[i], incomingParts[i]) {
357 continue outer
358 }
359 }
360 return true, nil
361 } else if strings.EqualFold(reqHost, host) {
362 return true, nil
363 }
364 }
365
366 return false, nil
367 }
368
369 // CELLibrary produces options that expose this matcher for use in CEL
370 // expression matchers.
371 //
372 // Example:
373 //
374 // expression host('localhost')
375 func (MatchHost) CELLibrary(ctx caddy.Context) (cel.Library, error) {
376 return CELMatcherImpl(
377 "host",
378 "host_match_request_list",
379 []*cel.Type{cel.ListType(cel.StringType)},
380 func(data ref.Val) (RequestMatcherWithError, error) {
381 refStringList := stringSliceType
382 strList, err := data.ConvertToNative(refStringList)
383 if err != nil {
384 return nil, err
385 }
386 matcher := MatchHost(strList.([]string))
387 err = matcher.Provision(ctx)
388 return matcher, err
389 },
390 )
391 }
392
393 // fuzzy returns true if the given hostname h is not a specific
394 // hostname, e.g. has placeholders or wildcards.
395 func (MatchHost) fuzzy(h string) bool { return strings.ContainsAny(h, "{*") }
396
397 // large returns true if m is considered to be large. Optimizing
398 // the matcher for smaller lists has diminishing returns.
399 // See related benchmark function in test file to conduct experiments.
400 func (m MatchHost) large() bool { return len(m) > 100 }
401
402 // CaddyModule returns the Caddy module information.
403 func (MatchPath) CaddyModule() caddy.ModuleInfo {
404 return caddy.ModuleInfo{
405 ID: "http.matchers.path",
406 New: func() caddy.Module { return new(MatchPath) },
407 }
408 }
409
410 // Provision lower-cases the static paths in m to ensure case-insensitive
411 // matching. Paths with placeholders are left untouched, since placeholder
412 // names can be case-sensitive (e.g. environment variables); they are
413 // lower-cased after replacement, at request time.
414 func (m MatchPath) Provision(_ caddy.Context) error {
415 for i := range m {
416 if m[i] == "*" && i > 0 {
417 // will always match, so just put it first
418 m[0] = m[i]
419 break
420 }
421 if !strings.Contains(m[i], "{") {
422 m[i] = strings.ToLower(m[i])
423 }
424 }
425 return nil
426 }
427
428 // Match returns true if r matches m.
429 func (m MatchPath) Match(r *http.Request) bool {
430 match, _ := m.MatchWithError(r)
431 return match
432 }
433
434 // MatchWithError returns true if r matches m.
435 func (m MatchPath) MatchWithError(r *http.Request) (bool, error) {
436 // Even though RFC 9110 says that path matching is case-sensitive
437 // (https://www.rfc-editor.org/rfc/rfc9110.html#section-4.2.3),
438 // we do case-insensitive matching to mitigate security issues
439 // related to differences between operating systems, applications,
440 // etc; if case-sensitive matching is needed, the regex matcher
441 // can be used instead.
442 reqPath := normalizeWindowsPath(strings.ToLower(r.URL.Path))
443
444 repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
445
446 for _, matchPattern := range m {
447 if strings.Contains(matchPattern, "{") {
448 matchPattern = strings.ToLower(repl.ReplaceAll(matchPattern, ""))
449 }
450
451 // special case: whole path is wildcard; this is unnecessary
452 // as it matches all requests, which is the same as no matcher
453 if matchPattern == "*" {
454 return true, nil
455 }
456
457 // Clean the path, merge doubled slashes, etc.
458 // This ensures maliciously crafted requests can't bypass
459 // the path matcher. See #4407. Good security posture
460 // requires that we should do all we can to reduce any
461 // funny-looking paths into "normalized" forms such that
462 // weird variants can't sneak by.
463 //
464 // How we clean the path depends on the kind of pattern:
465 // we either merge slashes or we don't. If the pattern
466 // has double slashes, we preserve them in the path.
467 //
468 // TODO: Despite the fact that the *vast* majority of path
469 // matchers have only 1 pattern, a possible optimization is
470 // to remember the cleaned form of the path for future
471 // iterations; it's just that the way we clean depends on
472 // the kind of pattern.
473
474 mergeSlashes := !strings.Contains(matchPattern, "//")
475
476 // if '%' appears in the match pattern, we interpret that to mean
477 // the intent is to compare that part of the path in raw/escaped
478 // space; i.e. "%40"=="%40", not "@", and "%2F"=="%2F", not "/"
479 if strings.Contains(matchPattern, "%") {
480 escapedPath := r.URL.EscapedPath()
481 if runtime.GOOS == "windows" {
482 escapedPath = windowsEscapedPathSeparatorRepl.Replace(escapedPath)
483 matchPattern = windowsEscapedPathSeparatorRepl.Replace(matchPattern)
484 escapedPath = normalizeWindowsEscapedPath(escapedPath)
485 }
486 reqPathForPattern := CleanPath(escapedPath, mergeSlashes)
487 if m.matchPatternWithEscapeSequence(reqPathForPattern, matchPattern) {
488 return true, nil
489 }
490
491 // doing prefix/suffix/substring matches doesn't make sense
492 continue
493 }
494
495 reqPathForPattern := CleanPath(reqPath, mergeSlashes)
496
497 // for substring, prefix, and suffix matching, only perform those
498 // special, fast matches if they are the only wildcards in the pattern;
499 // otherwise we assume a globular match if any * appears in the middle
500
501 // special case: first and last characters are wildcard,
502 // treat it as a fast substring match
503 if strings.Count(matchPattern, "*") == 2 &&
504 strings.HasPrefix(matchPattern, "*") &&
505 strings.HasSuffix(matchPattern, "*") {
506 if strings.Contains(reqPathForPattern, matchPattern[1:len(matchPattern)-1]) {
507 return true, nil
508 }
509 continue
510 }
511
512 // only perform prefix/suffix match if it is the only wildcard...
513 // I think that is more correct most of the time
514 if strings.Count(matchPattern, "*") == 1 {
515 // special case: first character is a wildcard,
516 // treat it as a fast suffix match
517 if strings.HasPrefix(matchPattern, "*") {
518 if strings.HasSuffix(reqPathForPattern, matchPattern[1:]) {
519 return true, nil
520 }
521 continue
522 }
523
524 // special case: last character is a wildcard,
525 // treat it as a fast prefix match
526 if strings.HasSuffix(matchPattern, "*") {
527 if strings.HasPrefix(reqPathForPattern, matchPattern[:len(matchPattern)-1]) {
528 return true, nil
529 }
530 continue
531 }
532 }
533
534 // at last, use globular matching, which also is exact matching
535 // if there are no glob/wildcard chars; we ignore the error here
536 // because we can't handle it anyway
537 matches, _ := path.Match(matchPattern, reqPathForPattern)
538 if matches {
539 return true, nil
540 }
541 }
542 return false, nil
543 }
544
545 func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath string) bool {
546 escapedPath = strings.ToLower(escapedPath)
547 // We would just compare the pattern against r.URL.Path,
548 // but the pattern contains %, indicating that we should
549 // compare at least some part of the path in raw/escaped
550 // space, not normalized space; so we build the string we
551 // will compare against by adding the normalized parts
552 // of the path, then switching to the escaped parts where
553 // the pattern hints to us wherever % is present.
554 var sb strings.Builder
555 sb.Grow(len(escapedPath))
556
557 // iterate the pattern and escaped path in lock-step;
558 // increment iPattern every time we consume a char from the pattern,
559 // increment iPath every time we consume a char from the path;
560 // iPattern and iPath are our cursors/iterator positions for each string
561 var iPattern, iPath int
562 for {
563 if iPattern >= len(matchPath) || iPath >= len(escapedPath) {
564 break
565 }
566 // get the next character from the request path
567
568 pathCh := string(escapedPath[iPath])
569 var escapedPathCh string
570
571 // normalize (decode) escape sequences
572 if pathCh == "%" && len(escapedPath) >= iPath+3 {
573 // hold onto this in case we find out the intent is to match in escaped space here;
574 // we lowercase it even though technically the spec says: "For consistency, URI
575 // producers and normalizers should use uppercase hexadecimal digits for all percent-
576 // encodings" (RFC 3986 section 2.1) - we lowercased the matcher pattern earlier in
577 // provisioning so we do the same here to gain case-insensitivity in equivalence;
578 // besides, this string is never shown visibly
579 escapedPathCh = strings.ToLower(escapedPath[iPath : iPath+3])
580
581 var err error
582 pathCh, err = url.PathUnescape(escapedPathCh)
583 if err != nil {
584 // should be impossible unless EscapedPath() is giving us an invalid sequence!
585 return false
586 }
587 iPath += 2 // escape sequence is 2 bytes longer than normal char
588 }
589
590 // now get the next character from the pattern
591
592 normalize := true
593 switch matchPath[iPattern] {
594 case '%':
595 // escape sequence
596
597 // if not a wildcard ("%*"), compare literally; consume next two bytes of pattern
598 if len(matchPath) >= iPattern+3 && matchPath[iPattern+1] != '*' {
599 sb.WriteString(escapedPathCh)
600 iPath++
601 iPattern += 2
602 break
603 }
604
605 // escaped wildcard sequence; consume next byte only ('*')
606 iPattern++
607 normalize = false
608
609 fallthrough
610 case '*':
611 // wildcard, so consume until next matching character
612 remaining := escapedPath[iPath:]
613 until := len(escapedPath) - iPath // go until end of string...
614 if iPattern < len(matchPath)-1 { // ...unless the * is not at the end
615 nextCh := matchPath[iPattern+1]
616 until = strings.IndexByte(remaining, nextCh)
617 if until == -1 {
618 // terminating char of wildcard span not found, so definitely no match
619 return false
620 }
621 }
622 if until == 0 {
623 // empty span; nothing to add on this iteration
624 break
625 }
626 next := remaining[:until]
627 if normalize {
628 var err error
629 next, err = url.PathUnescape(next)
630 if err != nil {
631 return false // should be impossible anyway
632 }
633 }
634 sb.WriteString(next)
635 iPath += until
636 default:
637 sb.WriteString(pathCh)
638 iPath++
639 }
640
641 iPattern++
642 }
643
644 // if the pattern was fully consumed before the path, the remaining path
645 // bytes still have to be part of the string we match against; otherwise a
646 // pattern with no trailing wildcard (e.g. "/foo%2fbar") would behave like a
647 // prefix match and incorrectly match longer paths (e.g. "/foo%2fbarbaz").
648 // the pattern has no more escape hints here, so compare in normalised space.
649 if iPath < len(escapedPath) {
650 remaining := escapedPath[iPath:]
651 if unescaped, err := url.PathUnescape(remaining); err == nil {
652 sb.WriteString(unescaped)
653 } else {
654 sb.WriteString(remaining)
655 }
656 }
657
658 // we can now treat rawpath globs (%*) as regular globs (*)
659 matchPath = strings.ReplaceAll(matchPath, "%*", "*")
660
661 // ignore error here because we can't handle it anyway
662 matches, _ := path.Match(matchPath, strings.ToLower(sb.String()))
663 return matches
664 }
665
666 // windowsEscapedPathSeparatorRepl normalizes Windows backslash separators
667 // while preserving escaped-path matching semantics.
668 var windowsEscapedPathSeparatorRepl = strings.NewReplacer(
669 `\`, "%2f",
670 "%5c", "%2f",
671 "%5C", "%2f",
672 )
673
674 // NormalizedPath returns a decoded request path in the form the path matchers
675 // evaluate it: rewritten the way the Windows filesystem resolves it (a no-op on
676 // other platforms), then cleaned. Consumers that act on a path a matcher has
677 // already judged should derive it with this, so the two cannot disagree about
678 // which resource a request names. An empty path is returned unchanged.
679 func NormalizedPath(p string, mergeSlashes bool) string {
680 if p == "" {
681 return ""
682 }
683 return CleanPath(normalizeWindowsPath(p), mergeSlashes)
684 }
685
686 // normalizeWindowsPath rewrites a decoded request path the way the Windows
687 // filesystem resolves it, so that path matchers (both `path` and `path_regexp`)
688 // cannot be bypassed on Windows: backslashes are treated as path separators,
689 // and trailing dots and spaces are ignored on EVERY path component (not only at
690 // the end of the whole path — e.g. a guard on `/private/` must also cover
691 // `/private./secret`). Path navigation segments ("." and "..") are left intact
692 // for cleanPath to resolve. It is a no-op on non-Windows systems. See #5613.
693 func normalizeWindowsPath(p string) string {
694 if runtime.GOOS != "windows" {
695 return p
696 }
697 p = strings.ReplaceAll(p, `\`, "/")
698 segments := strings.Split(p, "/")
699 for i, s := range segments {
700 if s == "." || s == ".." {
701 continue
702 }
703 segments[i] = strings.TrimRight(s, ". ")
704 }
705 return strings.Join(segments, "/")
706 }
707
708 const windowsShortNamePunctuation = "$%'-_@~`!(){}^#&"
709
710 // possibleWindowsShortNamePart reports whether s could be part of an 8.3
711 // alias. Extended characters are allowed when Windows' allowextchar behavior
712 // is enabled, so non-ASCII characters must be accepted here to fail closed.
713 func possibleWindowsShortNamePart(s string) bool {
714 for _, c := range s {
715 if c >= utf8.RuneSelf ||
716 (c >= 'a' && c <= 'z') ||
717 (c >= 'A' && c <= 'Z') ||
718 (c >= '0' && c <= '9') ||
719 strings.ContainsRune(windowsShortNamePunctuation, c) {
720 continue
721 }
722 return false
723 }
724 return true
725 }
726
727 // HasWindowsShortName reports whether any component of p has the syntax of an
728 // NTFS 8.3 short name. Both slash types separate components on Windows, which
729 // also ignores trailing dots and spaces in each component.
730 func HasWindowsShortName(p string) bool {
731 for _, component := range strings.FieldsFunc(p, func(r rune) bool {
732 return r == '/' || r == '\\'
733 }) {
734 component = strings.TrimRight(component, ". ")
735 base, extension, hasExtension := strings.Cut(component, ".")
736 if utf8.RuneCountInString(base) > 8 || !possibleWindowsShortNamePart(base) ||
737 (hasExtension && (utf8.RuneCountInString(extension) > 3 || !possibleWindowsShortNamePart(extension))) {
738 continue
739 }
740
741 tilde := strings.LastIndexByte(base, '~')
742 if tilde <= 0 || tilde == len(base)-1 {
743 continue
744 }
745 shortNameNumber := base[tilde+1:]
746 for _, c := range shortNameNumber {
747 if c < '0' || c > '9' {
748 shortNameNumber = ""
749 break
750 }
751 }
752 if shortNameNumber != "" {
753 return true
754 }
755 }
756 return false
757 }
758
759 // normalizeWindowsEscapedPath applies the same Windows normalization as
760 // normalizeWindowsPath, but in raw/escaped space (used when a matcher pattern
761 // contains '%'), where path separators may be percent-encoded ("%2f") and a
762 // component's trailing dots and spaces may be literal ("." / " ") or
763 // percent-encoded ("%2e" / "%20"). Without this, a '%'-containing matcher such
764 // as `/private%2f*` is bypassed on Windows by e.g. GET /private.%5csecret.txt,
765 // because only backslash separators — not the per-component trailing dots and
766 // spaces — were folded. The caller must have already folded backslash
767 // separators to "%2f" via windowsEscapedPathSeparatorRepl. Navigation segments
768 // ("." and "..") are left intact for CleanPath to resolve. Only the request
769 // path is normalized, not the matcher pattern (mirroring normalizeWindowsPath).
770 // It is a no-op on non-Windows systems. See #5613.
771 func normalizeWindowsEscapedPath(p string) string {
772 if runtime.GOOS != "windows" {
773 return p
774 }
775 var sb strings.Builder
776 sb.Grow(len(p))
777 // walk the path, splitting on separators that are meaningful on Windows:
778 // the literal '/' and the encoded '%2f'/'%2F' (backslashes were already
779 // folded to "%2f" by the caller). Trim each component, preserving the
780 // separators exactly so escaped-space comparison still lines up.
781 start := 0
782 flush := func(end int) {
783 seg := p[start:end]
784 if seg != "." && seg != ".." {
785 seg = trimWindowsEscapedTrailingDotSpace(seg)
786 }
787 sb.WriteString(seg)
788 }
789 for i := 0; i < len(p); {
790 switch {
791 case p[i] == '/':
792 flush(i)
793 sb.WriteByte('/')
794 i++
795 start = i
796 case p[i] == '%' && i+3 <= len(p) && p[i+1] == '2' && (p[i+2] == 'f' || p[i+2] == 'F'):
797 flush(i)
798 sb.WriteString(p[i : i+3])
799 i += 3
800 start = i
801 default:
802 i++
803 }
804 }
805 flush(len(p))
806 return sb.String()
807 }
808
809 // trimWindowsEscapedTrailingDotSpace strips trailing dots and spaces — whether
810 // literal ("." / " ") or percent-encoded ("%2e"/"%2E" / "%20") — from a single
811 // escaped-space path component, matching how Windows ignores them.
812 func trimWindowsEscapedTrailingDotSpace(s string) string {
813 for len(s) > 0 {
814 if c := s[len(s)-1]; c == '.' || c == ' ' {
815 s = s[:len(s)-1]
816 continue
817 }
818 if len(s) >= 3 {
819 if tail := s[len(s)-3:]; strings.EqualFold(tail, "%2e") || tail == "%20" {
820 s = s[:len(s)-3]
821 continue
822 }
823 }
824 return s
825 }
826 return s
827 }
828
829 // CELLibrary produces options that expose this matcher for use in CEL
830 // expression matchers.
831 //
832 // Example:
833 //
834 // expression path('*substring*', '*suffix')
835 func (MatchPath) CELLibrary(ctx caddy.Context) (cel.Library, error) {
836 return CELMatcherImpl(
837 // name of the macro, this is the function name that users see when writing expressions.
838 "path",
839 // name of the function that the macro will be rewritten to call.
840 "path_match_request_list",
841 // internal data type of the MatchPath value.
842 []*cel.Type{cel.ListType(cel.StringType)},
843 // function to convert a constant list of strings to a MatchPath instance.
844 func(data ref.Val) (RequestMatcherWithError, error) {
845 refStringList := stringSliceType
846 strList, err := data.ConvertToNative(refStringList)
847 if err != nil {
848 return nil, err
849 }
850 matcher := MatchPath(strList.([]string))
851 err = matcher.Provision(ctx)
852 return matcher, err
853 },
854 )
855 }
856
857 // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
858 func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
859 // iterate to merge multiple matchers into one
860 for d.Next() {
861 *m = append(*m, d.RemainingArgs()...)
862 if d.NextBlock(0) {
863 return d.Err("malformed path matcher: blocks are not supported")
864 }
865 }
866 return nil
867 }
868
869 // CaddyModule returns the Caddy module information.
870 func (MatchPathRE) CaddyModule() caddy.ModuleInfo {
871 return caddy.ModuleInfo{
872 ID: "http.matchers.path_regexp",
873 New: func() caddy.Module { return new(MatchPathRE) },
874 }
875 }
876
877 // Match returns true if r matches m.
878 func (m MatchPathRE) Match(r *http.Request) bool {
879 match, _ := m.MatchWithError(r)
880 return match
881 }
882
883 // MatchWithError returns true if r matches m.
884 func (m MatchPathRE) MatchWithError(r *http.Request) (bool, error) {
885 repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
886
887 // Clean the path, merges doubled slashes, etc.
888 // This ensures maliciously crafted requests can't bypass
889 // the path matcher. See #4407 (path cleaning) and #5613
890 // (Windows backslash / trailing dot-and-space normalization,
891 // shared with the path matcher).
892 cleanedPath := cleanPath(normalizeWindowsPath(r.URL.Path))
893
894 return m.MatchRegexp.Match(cleanedPath, repl), nil
895 }
896
897 // CELLibrary produces options that expose this matcher for use in CEL
898 // expression matchers.
899 //
900 // Example:
901 //
902 // expression path_regexp('^/bar')
903 //
904 // The pattern is a CEL string literal, so CEL's own escape sequences
905 // (e.g. \b, \n, \t) are interpreted before the result reaches the regexp
906 // engine. A pattern that needs a literal backslash escape meant for the
907 // regexp engine, such as \b for a word boundary, should be written as a
908 // CEL raw string (prefixed with r) so CEL passes the backslash through
909 // unchanged: expression path_regexp(r'^/bar\b').
910 func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
911 unnamedPattern, err := CELMatcherImpl(
912 "path_regexp",
913 "path_regexp_request_string",
914 []*cel.Type{cel.StringType},
915 func(data ref.Val) (RequestMatcherWithError, error) {
916 pattern := data.(types.String)
917 matcher := MatchPathRE{MatchRegexp{
918 Name: ctx.Value(MatcherNameCtxKey).(string),
919 Pattern: string(pattern),
920 }}
921 err := matcher.Provision(ctx)
922 return matcher, err
923 },
924 )
925 if err != nil {
926 return nil, err
927 }
928 namedPattern, err := CELMatcherImpl(
929 "path_regexp",
930 "path_regexp_request_string_string",
931 []*cel.Type{cel.StringType, cel.StringType},
932 func(data ref.Val) (RequestMatcherWithError, error) {
933 refStringList := stringSliceType
934 params, err := data.ConvertToNative(refStringList)
935 if err != nil {
936 return nil, err
937 }
938 strParams := params.([]string)
939 name := strParams[0]
940 if name == "" {
941 name = ctx.Value(MatcherNameCtxKey).(string)
942 }
943 matcher := MatchPathRE{MatchRegexp{
944 Name: name,
945 Pattern: strParams[1],
946 }}
947 err = matcher.Provision(ctx)
948 return matcher, err
949 },
950 )
951 if err != nil {
952 return nil, err
953 }
954 envOpts := append(unnamedPattern.CompileOptions(), namedPattern.CompileOptions()...)
955 prgOpts := append(unnamedPattern.ProgramOptions(), namedPattern.ProgramOptions()...)
956 return NewMatcherCELLibrary(envOpts, prgOpts), nil
957 }
958
959 // CaddyModule returns the Caddy module information.
960 func (MatchMethod) CaddyModule() caddy.ModuleInfo {
961 return caddy.ModuleInfo{
962 ID: "http.matchers.method",
963 New: func() caddy.Module { return new(MatchMethod) },
964 }
965 }
966
967 // Provision uppercases all configured methods to ensure case-insensitive
968 // matching against incoming requests, whose methods are always uppercase.
969 func (m MatchMethod) Provision(_ caddy.Context) error {
970 for i := range m {
971 m[i] = strings.ToUpper(m[i])
972 }
973 return nil
974 }
975
976 // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
977 func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
978 // iterate to merge multiple matchers into one
979 for d.Next() {
980 *m = append(*m, d.RemainingArgs()...)
981 if d.NextBlock(0) {
982 return d.Err("malformed method matcher: blocks are not supported")
983 }
984 }
985 return nil
986 }
987
988 // Match returns true if r matches m.
989 func (m MatchMethod) Match(r *http.Request) bool {
990 match, _ := m.MatchWithError(r)
991 return match
992 }
993
994 // MatchWithError returns true if r matches m.
995 func (m MatchMethod) MatchWithError(r *http.Request) (bool, error) {
996 return slices.Contains(m, r.Method), nil
997 }
998
999 // CELLibrary produces options that expose this matcher for use in CEL
1000 // expression matchers.
1001 //
1002 // Example:
1003 //
1004 // expression method('PUT', 'POST')
1005 func (MatchMethod) CELLibrary(_ caddy.Context) (cel.Library, error) {
1006 return CELMatcherImpl(
1007 "method",
1008 "method_request_list",
1009 []*cel.Type{cel.ListType(cel.StringType)},
1010 func(data ref.Val) (RequestMatcherWithError, error) {
1011 refStringList := stringSliceType
1012 strList, err := data.ConvertToNative(refStringList)
1013 if err != nil {
1014 return nil, err
1015 }
1016 return MatchMethod(strList.([]string)), nil
1017 },
1018 )
1019 }
1020
1021 // CaddyModule returns the Caddy module information.
1022 func (MatchQuery) CaddyModule() caddy.ModuleInfo {
1023 return caddy.ModuleInfo{
1024 ID: "http.matchers.query",
1025 New: func() caddy.Module { return new(MatchQuery) },
1026 }
1027 }
1028
1029 // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
1030 func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
1031 if *m == nil {
1032 *m = make(map[string][]string)
1033 }
1034 // iterate to merge multiple matchers into one
1035 for d.Next() {
1036 for _, query := range d.RemainingArgs() {
1037 if query == "" {
1038 continue
1039 }
1040 before, after, found := strings.Cut(query, "=")
1041 if !found {
1042 return d.Errf("malformed query matcher token: %s; must be in param=val format", d.Val())
1043 }
1044 url.Values(*m).Add(before, after)
1045 }
1046 if d.NextBlock(0) {
1047 return d.Err("malformed query matcher: blocks are not supported")
1048 }
1049 }
1050 return nil
1051 }
1052
1053 // Match returns true if r matches m. An empty m matches an empty query string.
1054 func (m MatchQuery) Match(r *http.Request) bool {
1055 match, _ := m.MatchWithError(r)
1056 return match
1057 }
1058
1059 // MatchWithError returns true if r matches m.
1060 // An empty m matches an empty query string.
1061 func (m MatchQuery) MatchWithError(r *http.Request) (bool, error) {
1062 // If no query keys are configured, this only
1063 // matches an empty query string.
1064 if len(m) == 0 {
1065 return len(r.URL.Query()) == 0, nil
1066 }
1067
1068 repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
1069
1070 // parse query string just once, for efficiency
1071 parsed, err := url.ParseQuery(r.URL.RawQuery)
1072 if err != nil {
1073 // Illegal query string. Likely bad escape sequence or unescaped literals.
1074 // Note that semicolons in query string have a controversial history. Summaries:
1075 // - https://github.com/golang/go/issues/50034
1076 // - https://github.com/golang/go/issues/25192
1077 // Despite the URL WHATWG spec mandating the use of & separators for query strings,
1078 // every URL parser implementation is different, and Filippo Valsorda rightly wrote:
1079 // "Relying on parser alignment for security is doomed." Overall conclusion is that
1080 // splitting on & and rejecting ; in key=value pairs is safer than accepting raw ;.
1081 // We regard the Go team's decision as sound and thus reject malformed query strings.
1082 return false, nil
1083 }
1084
1085 // Count the amount of matched keys, to ensure we AND
1086 // between all configured query keys; all keys must
1087 // match at least one value.
1088 matchedKeys := 0
1089 for param, vals := range m {
1090 param = repl.ReplaceAll(param, "")
1091 paramVal, found := parsed[param]
1092 if !found {
1093 return false, nil
1094 }
1095 for _, v := range vals {
1096 v = repl.ReplaceAll(v, "")
1097 if slices.Contains(paramVal, v) || v == "*" {
1098 matchedKeys++
1099 break
1100 }
1101 }
1102 }
1103 return matchedKeys == len(m), nil
1104 }
1105
1106 // CELLibrary produces options that expose this matcher for use in CEL
1107 // expression matchers.
1108 //
1109 // Example:
1110 //
1111 // expression query({'sort': 'asc'}) || query({'foo': ['*bar*', 'baz']})
1112 func (MatchQuery) CELLibrary(_ caddy.Context) (cel.Library, error) {
1113 return CELMatcherImpl(
1114 "query",
1115 "query_matcher_request_map",
1116 []*cel.Type{CELTypeJSON},
1117 func(data ref.Val) (RequestMatcherWithError, error) {
1118 mapStrListStr, err := CELValueToMapStrList(data)
1119 if err != nil {
1120 return nil, err
1121 }
1122 return MatchQuery(url.Values(mapStrListStr)), nil
1123 },
1124 )
1125 }
1126
1127 // CaddyModule returns the Caddy module information.
1128 func (MatchHeader) CaddyModule() caddy.ModuleInfo {
1129 return caddy.ModuleInfo{
1130 ID: "http.matchers.header",
1131 New: func() caddy.Module { return new(MatchHeader) },
1132 }
1133 }
1134
1135 // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
1136 func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
1137 if *m == nil {
1138 *m = make(map[string][]string)
1139 }
1140 // iterate to merge multiple matchers into one
1141 for d.Next() {
1142 var field, val string
1143 if !d.Args(&field) {
1144 return d.Errf("malformed header matcher: expected field")
1145 }
1146
1147 if strings.HasPrefix(field, "!") {
1148 if len(field) == 1 {
1149 return d.Errf("malformed header matcher: must have field name following ! character")
1150 }
1151
1152 field = field[1:]
1153 headers := *m
1154 headers[field] = nil
1155 m = &headers
1156 if d.NextArg() {
1157 return d.Errf("malformed header matcher: null matching headers cannot have a field value")
1158 }
1159 } else {
1160 if !d.NextArg() {
1161 return d.Errf("malformed header matcher: expected both field and value")
1162 }
1163
1164 // If multiple header matchers with the same header field are defined,
1165 // we want to add the existing to the list of headers (will be OR'ed)
1166 val = d.Val()
1167 http.Header(*m).Add(field, val)
1168 }
1169
1170 if d.NextBlock(0) {
1171 return d.Err("malformed header matcher: blocks are not supported")
1172 }
1173 }
1174 return nil
1175 }
1176
1177 // Match returns true if r matches m.
1178 func (m MatchHeader) Match(r *http.Request) bool {
1179 match, _ := m.MatchWithError(r)
1180 return match
1181 }
1182
1183 // MatchWithError returns true if r matches m.
1184 func (m MatchHeader) MatchWithError(r *http.Request) (bool, error) {
1185 repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
1186 return matchHeaders(r.Header, http.Header(m), r.Host, r.TransferEncoding, repl), nil
1187 }
1188
1189 // CELLibrary produces options that expose this matcher for use in CEL
1190 // expression matchers.
1191 //
1192 // Example:
1193 //
1194 // expression header({'content-type': 'image/png'})
1195 // expression header({'foo': ['bar', 'baz']}) // match bar or baz
1196 func (MatchHeader) CELLibrary(_ caddy.Context) (cel.Library, error) {
1197 return CELMatcherImpl(
1198 "header",
1199 "header_matcher_request_map",
1200 []*cel.Type{CELTypeJSON},
1201 func(data ref.Val) (RequestMatcherWithError, error) {
1202 mapStrListStr, err := CELValueToMapStrList(data)
1203 if err != nil {
1204 return nil, err
1205 }
1206 return MatchHeader(http.Header(mapStrListStr)), nil
1207 },
1208 )
1209 }
1210
1211 // getHeaderFieldVals returns the field values for the given fieldName from input.
1212 // The host parameter should be obtained from the http.Request.Host field, and the
1213 // transferEncoding from http.Request.TransferEncoding, since net/http removes them
1214 // from the header map.
1215 func getHeaderFieldVals(input http.Header, fieldName, host string, transferEncoding []string) []string {
1216 fieldName = textproto.CanonicalMIMEHeaderKey(fieldName)
1217 if fieldName == "Host" && host != "" {
1218 return []string{host}
1219 }
1220 if fieldName == "Transfer-Encoding" && input[fieldName] == nil {
1221 return transferEncoding
1222 }
1223 return input[fieldName]
1224 }
1225
1226 // matchHeaders returns true if input matches the criteria in against without regex.
1227 // The host parameter should be obtained from the http.Request.Host field since
1228 // net/http removes it from the header map.
1229 func matchHeaders(input, against http.Header, host string, transferEncoding []string, repl *caddy.Replacer) bool {
1230 for field, allowedFieldVals := range against {
1231 actualFieldVals := getHeaderFieldVals(input, field, host, transferEncoding)
1232 if allowedFieldVals != nil && len(allowedFieldVals) == 0 && actualFieldVals != nil {
1233 // a non-nil but empty list of allowed values means
1234 // match if the header field exists at all
1235 continue
1236 }
1237 if allowedFieldVals == nil && actualFieldVals == nil {
1238 // a nil list means match if the header does not exist at all
1239 continue
1240 }
1241 var match bool
1242 fieldVals:
1243 for _, actualFieldVal := range actualFieldVals {
1244 for _, allowedFieldVal := range allowedFieldVals {
1245 if repl != nil {
1246 allowedFieldVal = repl.ReplaceAll(allowedFieldVal, "")
1247 }
1248 switch {
1249 case allowedFieldVal == "*":
1250 match = true
1251 case strings.HasPrefix(allowedFieldVal, "*") && strings.HasSuffix(allowedFieldVal, "*"):
1252 match = strings.Contains(actualFieldVal, allowedFieldVal[1:len(allowedFieldVal)-1])
1253 case strings.HasPrefix(allowedFieldVal, "*"):
1254 match = strings.HasSuffix(actualFieldVal, allowedFieldVal[1:])
1255 case strings.HasSuffix(allowedFieldVal, "*"):
1256 match = strings.HasPrefix(actualFieldVal, allowedFieldVal[:len(allowedFieldVal)-1])
1257 default:
1258 match = actualFieldVal == allowedFieldVal
1259 }
1260 if match {
1261 break fieldVals
1262 }
1263 }
1264 }
1265 if !match {
1266 return false
1267 }
1268 }
1269 return true
1270 }
1271
1272 // CaddyModule returns the Caddy module information.
1273 func (MatchHeaderRE) CaddyModule() caddy.ModuleInfo {
1274 return caddy.ModuleInfo{
1275 ID: "http.matchers.header_regexp",
1276 New: func() caddy.Module { return new(MatchHeaderRE) },
1277 }
1278 }
1279
1280 // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
1281 func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
1282 if *m == nil {
1283 *m = make(map[string]*MatchRegexp)
1284 }
1285 // iterate to merge multiple matchers into one
1286 for d.Next() {
1287 var first, second, third string
1288 if !d.Args(&first, &second) {
1289 return d.ArgErr()
1290 }
1291
1292 var name, field, val string
1293 if d.Args(&third) {
1294 name = first
1295 field = second
1296 val = third
1297 } else {
1298 field = first
1299 val = second
1300 }
1301
1302 // Default to the named matcher's name, if no regexp name is provided
1303 if name == "" {
1304 name = d.GetContextString(caddyfile.MatcherNameCtxKey)
1305 }
1306
1307 // If there's already a pattern for this field
1308 // then we would end up overwriting the old one
1309 if (*m)[field] != nil {
1310 return d.Errf("header_regexp matcher can only be used once per named matcher, per header field: %s", field)
1311 }
1312
1313 (*m)[field] = &MatchRegexp{Pattern: val, Name: name}
1314
1315 if d.NextBlock(0) {
1316 return d.Err("malformed header_regexp matcher: blocks are not supported")
1317 }
1318 }
1319 return nil
1320 }
1321
1322 // Match returns true if r matches m.
1323 func (m MatchHeaderRE) Match(r *http.Request) bool {
1324 match, _ := m.MatchWithError(r)
1325 return match
1326 }
1327
1328 // MatchWithError returns true if r matches m.
1329 func (m MatchHeaderRE) MatchWithError(r *http.Request) (bool, error) {
1330 for field, rm := range m {
1331 actualFieldVals := getHeaderFieldVals(r.Header, field, r.Host, r.TransferEncoding)
1332 match := false
1333 fieldVal:
1334 for _, actualFieldVal := range actualFieldVals {
1335 repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
1336 if rm.Match(actualFieldVal, repl) {
1337 match = true
1338 break fieldVal
1339 }
1340 }
1341 if !match {
1342 return false, nil
1343 }
1344 }
1345 return true, nil
1346 }
1347
1348 // Provision compiles m's regular expressions.
1349 func (m MatchHeaderRE) Provision(ctx caddy.Context) error {
1350 for _, rm := range m {
1351 err := rm.Provision(ctx)
1352 if err != nil {
1353 return err
1354 }
1355 }
1356 return nil
1357 }
1358
1359 // Validate validates m's regular expressions.
1360 func (m MatchHeaderRE) Validate() error {
1361 for _, rm := range m {
1362 err := rm.Validate()
1363 if err != nil {
1364 return err
1365 }
1366 }
1367 return nil
1368 }
1369
1370 // CELLibrary produces options that expose this matcher for use in CEL
1371 // expression matchers.
1372 //
1373 // Example:
1374 //
1375 // expression header_regexp('foo', 'Field', 'fo+')
1376 //
1377 // The pattern is a CEL string literal, so CEL's own escape sequences
1378 // (e.g. \b, \n, \t) are interpreted before the result reaches the regexp
1379 // engine. A pattern that needs a literal backslash escape meant for the
1380 // regexp engine, such as \b for a word boundary, should be written as a
1381 // CEL raw string (prefixed with r) so CEL passes the backslash through
1382 // unchanged: expression header_regexp('foo', 'User-Agent', r'bot\b').
1383 // Without the r prefix, \b is interpreted by CEL as a backspace
1384 // character before the regexp engine ever sees the pattern.
1385 func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
1386 unnamedPattern, err := CELMatcherImpl(
1387 "header_regexp",
1388 "header_regexp_request_string_string",
1389 []*cel.Type{cel.StringType, cel.StringType},
1390 func(data ref.Val) (RequestMatcherWithError, error) {
1391 refStringList := stringSliceType
1392 params, err := data.ConvertToNative(refStringList)
1393 if err != nil {
1394 return nil, err
1395 }
1396 strParams := params.([]string)
1397 matcher := MatchHeaderRE{}
1398 matcher[strParams[0]] = &MatchRegexp{
1399 Pattern: strParams[1],
1400 Name: ctx.Value(MatcherNameCtxKey).(string),
1401 }
1402 err = matcher.Provision(ctx)
1403 return matcher, err
1404 },
1405 )
1406 if err != nil {
1407 return nil, err
1408 }
1409 namedPattern, err := CELMatcherImpl(
1410 "header_regexp",
1411 "header_regexp_request_string_string_string",
1412 []*cel.Type{cel.StringType, cel.StringType, cel.StringType},
1413 func(data ref.Val) (RequestMatcherWithError, error) {
1414 refStringList := stringSliceType
1415 params, err := data.ConvertToNative(refStringList)
1416 if err != nil {
1417 return nil, err
1418 }
1419 strParams := params.([]string)
1420 name := strParams[0]
1421 if name == "" {
1422 name = ctx.Value(MatcherNameCtxKey).(string)
1423 }
1424 matcher := MatchHeaderRE{}
1425 matcher[strParams[1]] = &MatchRegexp{
1426 Pattern: strParams[2],
1427 Name: name,
1428 }
1429 err = matcher.Provision(ctx)
1430 return matcher, err
1431 },
1432 )
1433 if err != nil {
1434 return nil, err
1435 }
1436 envOpts := append(unnamedPattern.CompileOptions(), namedPattern.CompileOptions()...)
1437 prgOpts := append(unnamedPattern.ProgramOptions(), namedPattern.ProgramOptions()...)
1438 return NewMatcherCELLibrary(envOpts, prgOpts), nil
1439 }
1440
1441 // CaddyModule returns the Caddy module information.
1442 func (MatchProtocol) CaddyModule() caddy.ModuleInfo {
1443 return caddy.ModuleInfo{
1444 ID: "http.matchers.protocol",
1445 New: func() caddy.Module { return new(MatchProtocol) },
1446 }
1447 }
1448
1449 // Match returns true if r matches m.
1450 func (m MatchProtocol) Match(r *http.Request) bool {
1451 match, _ := m.MatchWithError(r)
1452 return match
1453 }
1454
1455 // MatchWithError returns true if r matches m.
1456 func (m MatchProtocol) MatchWithError(r *http.Request) (bool, error) {
1457 switch string(m) {
1458 case "grpc":
1459 return strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc"), nil
1460 case "https":
1461 return r.TLS != nil, nil
1462 case "http":
1463 return r.TLS == nil, nil
1464 case "http/1.0":
1465 return r.ProtoMajor == 1 && r.ProtoMinor == 0, nil
1466 case "http/1.0+":
1467 return r.ProtoAtLeast(1, 0), nil
1468 case "http/1.1":
1469 return r.ProtoMajor == 1 && r.ProtoMinor == 1, nil
1470 case "http/1.1+":
1471 return r.ProtoAtLeast(1, 1), nil
1472 case "http/2":
1473 return r.ProtoMajor == 2, nil
1474 case "http/2+":
1475 return r.ProtoAtLeast(2, 0), nil
1476 case "http/3":
1477 return r.ProtoMajor == 3, nil
1478 case "http/3+":
1479 return r.ProtoAtLeast(3, 0), nil
1480 }
1481 return false, nil
1482 }
1483
1484 // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
1485 func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
1486 // iterate to merge multiple matchers into one
1487 for d.Next() {
1488 var proto string
1489 if !d.Args(&proto) {
1490 return d.Err("expected exactly one protocol")
1491 }
1492 *m = MatchProtocol(proto)
1493 }
1494 return nil
1495 }
1496
1497 // CELLibrary produces options that expose this matcher for use in CEL
1498 // expression matchers.
1499 //
1500 // Example:
1501 //
1502 // expression protocol('https')
1503 func (MatchProtocol) CELLibrary(_ caddy.Context) (cel.Library, error) {
1504 return CELMatcherImpl(
1505 "protocol",
1506 "protocol_request_string",
1507 []*cel.Type{cel.StringType},
1508 func(data ref.Val) (RequestMatcherWithError, error) {
1509 protocolStr, ok := data.(types.String)
1510 if !ok {
1511 return nil, errors.New("protocol argument was not a string")
1512 }
1513 return MatchProtocol(strings.ToLower(string(protocolStr))), nil
1514 },
1515 )
1516 }
1517
1518 // CaddyModule returns the Caddy module information.
1519 func (MatchTLS) CaddyModule() caddy.ModuleInfo {
1520 return caddy.ModuleInfo{
1521 ID: "http.matchers.tls",
1522 New: func() caddy.Module { return new(MatchTLS) },
1523 }
1524 }
1525
1526 // Match returns true if r matches m.
1527 func (m MatchTLS) Match(r *http.Request) bool {
1528 match, _ := m.MatchWithError(r)
1529 return match
1530 }
1531
1532 // MatchWithError returns true if r matches m.
1533 func (m MatchTLS) MatchWithError(r *http.Request) (bool, error) {
1534 if r.TLS == nil {
1535 return false, nil
1536 }
1537 if m.HandshakeComplete != nil {
1538 if (!*m.HandshakeComplete && r.TLS.HandshakeComplete) ||
1539 (*m.HandshakeComplete && !r.TLS.HandshakeComplete) {
1540 return false, nil
1541 }
1542 }
1543 return true, nil
1544 }
1545
1546 // UnmarshalCaddyfile parses Caddyfile tokens for this matcher. Syntax:
1547 //
1548 // ... tls [early_data]
1549 //
1550 // EXPERIMENTAL SYNTAX: Subject to change.
1551 func (m *MatchTLS) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
1552 // iterate to merge multiple matchers into one
1553 for d.Next() {
1554 if d.NextArg() {
1555 switch d.Val() {
1556 case "early_data":
1557 var false bool
1558 m.HandshakeComplete = &false
1559 default:
1560 return d.Errf("unrecognized option '%s'", d.Val())
1561 }
1562 }
1563 if d.NextArg() {
1564 return d.ArgErr()
1565 }
1566 if d.NextBlock(0) {
1567 return d.Err("malformed tls matcher: blocks are not supported yet")
1568 }
1569 }
1570 return nil
1571 }
1572
1573 // CELLibrary produces options that expose this matcher for use in CEL
1574 // expression matchers. It mirrors the three states the Caddyfile `tls`
1575 // matcher itself supports: bare `tls` (any TLS connection, regardless of
1576 // handshake completion), `tls early_data` (handshake not yet complete), and
1577 // the otherwise-implicit "handshake complete" case. `tls()` is sugar for
1578 // `tls('any')`; both spellings are equivalent and supported.
1579 //
1580 // Example:
1581 //
1582 // expression tls() && !tls('early_data')
1583 func (MatchTLS) CELLibrary(ctx caddy.Context) (cel.Library, error) {
1584 const funcName = "tls_request_string"
1585 factory := func(data ref.Val) (RequestMatcherWithError, error) {
1586 str, ok := data.(types.String)
1587 if !ok {
1588 return nil, errors.New("tls argument was not a string")
1589 }
1590 switch string(str) {
1591 case "any":
1592 // Also reachable directly as tls('any'), not just via the
1593 // zero-arg tls() macro below -- both are valid and equivalent.
1594 return MatchTLS{}, nil
1595 case "complete":
1596 complete := true
1597 return MatchTLS{HandshakeComplete: &complete}, nil
1598 case "early_data":
1599 complete := false
1600 return MatchTLS{HandshakeComplete: &complete}, nil
1601 default:
1602 return nil, fmt.Errorf("tls argument must be 'any', 'complete', or 'early_data', got %q", str)
1603 }
1604 }
1605
1606 // tls() is sugar for tls('any'): it expands to the same 2-arg call the
1607 // one-argument form produces, just with the argument injected by the
1608 // macro itself instead of typed out by the caller.
1609 zeroArgMacro := parser.NewGlobalMacro("tls", 0,
1610 func(eh cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *common.Error) {
1611 return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), eh.NewLiteral(types.String("any"))), nil
1612 })
1613 oneArgMacro := parser.NewGlobalMacro("tls", 1, celMatcherStringMacroExpander(funcName))
1614
1615 envOptions := []cel.EnvOption{
1616 cel.Macros(zeroArgMacro, oneArgMacro),
1617 cel.Function(funcName,
1618 cel.Overload(funcName, []*cel.Type{cel.ObjectType("http.Request"), cel.StringType}, cel.BoolType),
1619 cel.SingletonBinaryBinding(CELMatcherRuntimeFunction(funcName, factory))),
1620 }
1621 programOptions := []cel.ProgramOption{
1622 cel.CustomDecorator(CELMatcherDecorator(funcName, factory)),
1623 }
1624 return NewMatcherCELLibrary(envOptions, programOptions), nil
1625 }
1626
1627 // CaddyModule returns the Caddy module information.
1628 func (MatchNot) CaddyModule() caddy.ModuleInfo {
1629 return caddy.ModuleInfo{
1630 ID: "http.matchers.not",
1631 New: func() caddy.Module { return new(MatchNot) },
1632 }
1633 }
1634
1635 // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
1636 func (m *MatchNot) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
1637 // iterate to merge multiple matchers into one
1638 for d.Next() {
1639 matcherSet, err := ParseCaddyfileNestedMatcherSet(d)
1640 if err != nil {
1641 return err
1642 }
1643 m.MatcherSetsRaw = append(m.MatcherSetsRaw, matcherSet)
1644 }
1645 return nil
1646 }
1647
1648 // UnmarshalJSON satisfies json.Unmarshaler. It puts the JSON
1649 // bytes directly into m's MatcherSetsRaw field.
1650 func (m *MatchNot) UnmarshalJSON(data []byte) error {
1651 return json.Unmarshal(data, &m.MatcherSetsRaw)
1652 }
1653
1654 // MarshalJSON satisfies json.Marshaler by marshaling
1655 // m's raw matcher sets.
1656 func (m MatchNot) MarshalJSON() ([]byte, error) {
1657 return json.Marshal(m.MatcherSetsRaw)
1658 }
1659
1660 // Provision loads the matcher modules to be negated.
1661 func (m *MatchNot) Provision(ctx caddy.Context) error {
1662 matcherSets, err := ctx.LoadModule(m, "MatcherSetsRaw")
1663 if err != nil {
1664 return fmt.Errorf("loading matcher sets: %v", err)
1665 }
1666 for _, modMap := range matcherSets.([]map[string]any) {
1667 var ms MatcherSet
1668 for _, modIface := range modMap {
1669 if mod, ok := modIface.(RequestMatcherWithError); ok {
1670 ms = append(ms, mod)
1671 continue
1672 }
1673 if mod, ok := modIface.(RequestMatcher); ok {
1674 ms = append(ms, mod)
1675 continue
1676 }
1677 return fmt.Errorf("module is not a request matcher: %T", modIface)
1678 }
1679 m.MatcherSets = append(m.MatcherSets, ms)
1680 }
1681 return nil
1682 }
1683
1684 // Match returns true if r matches m. Since this matcher negates
1685 // the embedded matchers, false is returned if any of its matcher
1686 // sets return true.
1687 func (m MatchNot) Match(r *http.Request) bool {
1688 match, _ := m.MatchWithError(r)
1689 return match
1690 }
1691
1692 // MatchWithError returns true if r matches m. Since this matcher
1693 // negates the embedded matchers, false is returned if any of its
1694 // matcher sets return true.
1695 func (m MatchNot) MatchWithError(r *http.Request) (bool, error) {
1696 for _, ms := range m.MatcherSets {
1697 matches, err := ms.MatchWithError(r)
1698 if err != nil {
1699 return false, err
1700 }
1701 if matches {
1702 return false, nil
1703 }
1704 }
1705 return true, nil
1706 }
1707
1708 // MatchRegexp is an embedable type for matching
1709 // using regular expressions. It adds placeholders
1710 // to the request's replacer.
1711 type MatchRegexp struct {
1712 // A unique name for this regular expression. Optional,
1713 // but useful to prevent overwriting captures from other
1714 // regexp matchers.
1715 Name string `json:"name,omitempty"`
1716
1717 // The regular expression to evaluate, in RE2 syntax,
1718 // which is the same general syntax used by Go, Perl,
1719 // and Python. For details, see
1720 // [Go's regexp package](https://golang.org/pkg/regexp/).
1721 // Captures are accessible via placeholders. Unnamed
1722 // capture groups are exposed as their numeric, 1-based
1723 // index, while named capture groups are available by
1724 // the capture group name.
1725 Pattern string `json:"pattern"`
1726
1727 compiled *regexp.Regexp
1728 }
1729
1730 // Provision compiles the regular expression.
1731 func (mre *MatchRegexp) Provision(caddy.Context) error {
1732 re, err := regexp.Compile(mre.Pattern)
1733 if err != nil {
1734 return fmt.Errorf("compiling matcher regexp %s: %v", mre.Pattern, err)
1735 }
1736 mre.compiled = re
1737 return nil
1738 }
1739
1740 // Validate ensures mre is set up correctly.
1741 func (mre *MatchRegexp) Validate() error {
1742 if mre.Name != "" && !wordRE.MatchString(mre.Name) {
1743 return fmt.Errorf("invalid regexp name (must contain only word characters): %s", mre.Name)
1744 }
1745 return nil
1746 }
1747
1748 // Match returns true if input matches the compiled regular
1749 // expression in mre. It sets values on the replacer repl
1750 // associated with capture groups, using the given scope
1751 // (namespace).
1752 func (mre *MatchRegexp) Match(input string, repl *caddy.Replacer) bool {
1753 matches := mre.compiled.FindStringSubmatch(input)
1754 if matches == nil {
1755 return false
1756 }
1757
1758 // save all capture groups, first by index
1759 for i, match := range matches {
1760 keySuffix := "." + strconv.Itoa(i)
1761 if mre.Name != "" {
1762 repl.Set(regexpPlaceholderPrefix+"."+mre.Name+keySuffix, match)
1763 }
1764 repl.Set(regexpPlaceholderPrefix+keySuffix, match)
1765 }
1766
1767 // then by name
1768 for i, name := range mre.compiled.SubexpNames() {
1769 // skip the first element (the full match), and empty names
1770 if i == 0 || name == "" {
1771 continue
1772 }
1773
1774 keySuffix := "." + name
1775 if mre.Name != "" {
1776 repl.Set(regexpPlaceholderPrefix+"."+mre.Name+keySuffix, matches[i])
1777 }
1778 repl.Set(regexpPlaceholderPrefix+keySuffix, matches[i])
1779 }
1780
1781 return true
1782 }
1783
1784 // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
1785 func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
1786 // iterate to merge multiple matchers into one
1787 for d.Next() {
1788 // If this is the second iteration of the loop
1789 // then there's more than one path_regexp matcher
1790 // and we would end up overwriting the old one
1791 if mre.Pattern != "" {
1792 return d.Err("regular expression can only be used once per named matcher")
1793 }
1794
1795 args := d.RemainingArgs()
1796 switch len(args) {
1797 case 1:
1798 mre.Pattern = args[0]
1799 case 2:
1800 mre.Name = args[0]
1801 mre.Pattern = args[1]
1802 default:
1803 return d.ArgErr()
1804 }
1805
1806 // Default to the named matcher's name, if no regexp name is provided
1807 if mre.Name == "" {
1808 mre.Name = d.GetContextString(caddyfile.MatcherNameCtxKey)
1809 }
1810
1811 if d.NextBlock(0) {
1812 return d.Err("malformed path_regexp matcher: blocks are not supported")
1813 }
1814 }
1815 return nil
1816 }
1817
1818 // ParseCaddyfileNestedMatcherSet parses the Caddyfile tokens for a nested
1819 // matcher set, and returns its raw module map value.
1820 func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
1821 matcherMap := make(map[string]any)
1822
1823 // in case there are multiple instances of the same matcher, concatenate
1824 // their tokens (we expect that UnmarshalCaddyfile should be able to
1825 // handle more than one segment); otherwise, we'd overwrite other
1826 // instances of the matcher in this set
1827 tokensByMatcherName := make(map[string][]caddyfile.Token)
1828 for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
1829 // if the token is quoted (backtick), treat it as a shorthand
1830 // for an expression matcher, same as @named matcher parsing
1831 if d.Token().Quoted() {
1832 expressionToken := d.Token().Clone()
1833 expressionToken.Text = "expression"
1834 tokensByMatcherName["expression"] = append(tokensByMatcherName["expression"], expressionToken, d.Token())
1835 continue
1836 }
1837 matcherName := d.Val()
1838 tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
1839 }
1840
1841 for matcherName, tokens := range tokensByMatcherName {
1842 mod, err := caddy.GetModule("http.matchers." + matcherName)
1843 if err != nil {
1844 return nil, d.Errf("getting matcher module '%s': %v", matcherName, err)
1845 }
1846 unm, ok := mod.New().(caddyfile.Unmarshaler)
1847 if !ok {
1848 return nil, d.Errf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName)
1849 }
1850 err = unm.UnmarshalCaddyfile(caddyfile.NewDispenser(tokens))
1851 if err != nil {
1852 return nil, err
1853 }
1854 if rm, ok := unm.(RequestMatcherWithError); ok {
1855 matcherMap[matcherName] = rm
1856 continue
1857 }
1858 if rm, ok := unm.(RequestMatcher); ok {
1859 matcherMap[matcherName] = rm
1860 continue
1861 }
1862 return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
1863 }
1864
1865 // we should now have a functional matcher, but we also
1866 // need to be able to marshal as JSON, otherwise config
1867 // adaptation will be missing the matchers!
1868 matcherSet := make(caddy.ModuleMap)
1869 for name, matcher := range matcherMap {
1870 jsonBytes, err := json.Marshal(matcher)
1871 if err != nil {
1872 return nil, fmt.Errorf("marshaling %T matcher: %v", matcher, err)
1873 }
1874 matcherSet[name] = jsonBytes
1875 }
1876
1877 return matcherSet, nil
1878 }
1879
1880 var wordRE = regexp.MustCompile(`\w+`)
1881
1882 const regexpPlaceholderPrefix = "http.regexp"
1883
1884 // MatcherErrorVarKey is the key used for the variable that
1885 // holds an optional error emitted from a request matcher,
1886 // to short-circuit the handler chain, since matchers cannot
1887 // return errors via the RequestMatcher interface.
1888 //
1889 // Deprecated: Matchers should implement RequestMatcherWithError
1890 // which can return an error directly, instead of smuggling it
1891 // through the vars map.
1892 const MatcherErrorVarKey = "matchers.error"
1893
1894 // Interface guards
1895 var (
1896 _ RequestMatcherWithError = (*MatchHost)(nil)
1897 _ caddy.Provisioner = (*MatchHost)(nil)
1898 _ RequestMatcherWithError = (*MatchPath)(nil)
1899 _ caddy.Provisioner = (*MatchPath)(nil)
1900 _ RequestMatcherWithError = (*MatchPathRE)(nil)
1901 _ caddy.Provisioner = (*MatchPathRE)(nil)
1902 _ RequestMatcherWithError = (*MatchMethod)(nil)
1903 _ caddy.Provisioner = (*MatchMethod)(nil)
1904 _ RequestMatcherWithError = (*MatchQuery)(nil)
1905 _ RequestMatcherWithError = (*MatchHeader)(nil)
1906 _ RequestMatcherWithError = (*MatchHeaderRE)(nil)
1907 _ caddy.Provisioner = (*MatchHeaderRE)(nil)
1908 _ RequestMatcherWithError = (*MatchProtocol)(nil)
1909 _ RequestMatcherWithError = (*MatchNot)(nil)
1910 _ caddy.Provisioner = (*MatchNot)(nil)
1911 _ caddy.Provisioner = (*MatchRegexp)(nil)
1912
1913 _ caddyfile.Unmarshaler = (*MatchHost)(nil)
1914 _ caddyfile.Unmarshaler = (*MatchPath)(nil)
1915 _ caddyfile.Unmarshaler = (*MatchPathRE)(nil)
1916 _ caddyfile.Unmarshaler = (*MatchMethod)(nil)
1917 _ caddyfile.Unmarshaler = (*MatchQuery)(nil)
1918 _ caddyfile.Unmarshaler = (*MatchHeader)(nil)
1919 _ caddyfile.Unmarshaler = (*MatchHeaderRE)(nil)
1920 _ caddyfile.Unmarshaler = (*MatchProtocol)(nil)
1921 _ caddyfile.Unmarshaler = (*VarsMatcher)(nil)
1922 _ caddyfile.Unmarshaler = (*MatchVarsRE)(nil)
1923
1924 _ CELLibraryProducer = (*MatchHost)(nil)
1925 _ CELLibraryProducer = (*MatchPath)(nil)
1926 _ CELLibraryProducer = (*MatchPathRE)(nil)
1927 _ CELLibraryProducer = (*MatchMethod)(nil)
1928 _ CELLibraryProducer = (*MatchQuery)(nil)
1929 _ CELLibraryProducer = (*MatchHeader)(nil)
1930 _ CELLibraryProducer = (*MatchHeaderRE)(nil)
1931 _ CELLibraryProducer = (*MatchProtocol)(nil)
1932 _ CELLibraryProducer = (*MatchTLS)(nil)
1933 _ CELLibraryProducer = (*VarsMatcher)(nil)
1934 _ CELLibraryProducer = (*MatchVarsRE)(nil)
1935
1936 _ json.Marshaler = (*MatchNot)(nil)
1937 _ json.Unmarshaler = (*MatchNot)(nil)
1938 )