mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-28 09:12:52 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			1624 lines
		
	
	
		
			51 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1624 lines
		
	
	
		
			51 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 Matthew Holt and The Caddy Authors
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //     http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package caddyhttp
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"net/netip"
 | |
| 	"net/textproto"
 | |
| 	"net/url"
 | |
| 	"path"
 | |
| 	"reflect"
 | |
| 	"regexp"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/caddyserver/caddy/v2"
 | |
| 	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
 | |
| 	"github.com/google/cel-go/cel"
 | |
| 	"github.com/google/cel-go/common/types"
 | |
| 	"github.com/google/cel-go/common/types/ref"
 | |
| 	"go.uber.org/zap"
 | |
| )
 | |
| 
 | |
| type (
 | |
| 	// MatchHost matches requests by the Host value (case-insensitive).
 | |
| 	//
 | |
| 	// When used in a top-level HTTP route,
 | |
| 	// [qualifying domain names](/docs/automatic-https#hostname-requirements)
 | |
| 	// may trigger [automatic HTTPS](/docs/automatic-https), which automatically
 | |
| 	// provisions and renews certificates for you. Before doing this, you
 | |
| 	// should ensure that DNS records for these domains are properly configured,
 | |
| 	// especially A/AAAA pointed at your server.
 | |
| 	//
 | |
| 	// Automatic HTTPS can be
 | |
| 	// [customized or disabled](/docs/modules/http#servers/automatic_https).
 | |
| 	//
 | |
| 	// Wildcards (`*`) may be used to represent exactly one label of the
 | |
| 	// hostname, in accordance with RFC 1034 (because host matchers are also
 | |
| 	// used for automatic HTTPS which influences TLS certificates). Thus,
 | |
| 	// a host of `*` matches hosts like `localhost` or `internal` but not
 | |
| 	// `example.com`. To catch all hosts, omit the host matcher entirely.
 | |
| 	//
 | |
| 	// The wildcard can be useful for matching all subdomains, for example:
 | |
| 	// `*.example.com` matches `foo.example.com` but not `foo.bar.example.com`.
 | |
| 	//
 | |
| 	// Duplicate entries will return an error.
 | |
| 	MatchHost []string
 | |
| 
 | |
| 	// MatchPath case-insensitively matches requests by the URI's path. Path
 | |
| 	// matching is exact, not prefix-based, giving you more control and clarity
 | |
| 	// over matching. Wildcards (`*`) may be used:
 | |
| 	//
 | |
| 	// - At the end only, for a prefix match (`/prefix/*`)
 | |
| 	// - At the beginning only, for a suffix match (`*.suffix`)
 | |
| 	// - On both sides only, for a substring match (`*/contains/*`)
 | |
| 	// - In the middle, for a globular match (`/accounts/*/info`)
 | |
| 	//
 | |
| 	// Slashes are significant; i.e. `/foo*` matches `/foo`, `/foo/`, `/foo/bar`,
 | |
| 	// and `/foobar`; but `/foo/*` does not match `/foo` or `/foobar`. Valid
 | |
| 	// paths start with a slash `/`.
 | |
| 	//
 | |
| 	// Because there are, in general, multiple possible escaped forms of any
 | |
| 	// path, path matchers operate in unescaped space; that is, path matchers
 | |
| 	// should be written in their unescaped form to prevent ambiguities and
 | |
| 	// possible security issues, as all request paths will be normalized to
 | |
| 	// their unescaped forms before matcher evaluation.
 | |
| 	//
 | |
| 	// However, escape sequences in a match pattern are supported; they are
 | |
| 	// compared with the request's raw/escaped path for those bytes only.
 | |
| 	// In other words, a matcher of `/foo%2Fbar` will match a request path
 | |
| 	// of precisely `/foo%2Fbar`, but not `/foo/bar`. It follows that matching
 | |
| 	// the literal percent sign (%) in normalized space can be done using the
 | |
| 	// escaped form, `%25`.
 | |
| 	//
 | |
| 	// Even though wildcards (`*`) operate in the normalized space, the special
 | |
| 	// escaped wildcard (`%*`), which is not a valid escape sequence, may be
 | |
| 	// used in place of a span that should NOT be decoded; that is, `/bands/%*`
 | |
| 	// will match `/bands/AC%2fDC` whereas `/bands/*` will not.
 | |
| 	//
 | |
| 	// Even though path matching is done in normalized space, the special
 | |
| 	// wildcard `%*` may be used in place of a span that should NOT be decoded;
 | |
| 	// that is, `/bands/%*/` will match `/bands/AC%2fDC/` whereas `/bands/*/`
 | |
| 	// will not.
 | |
| 	//
 | |
| 	// This matcher is fast, so it does not support regular expressions or
 | |
| 	// capture groups. For slower but more powerful matching, use the
 | |
| 	// path_regexp matcher. (Note that due to the special treatment of
 | |
| 	// escape sequences in matcher patterns, they may perform slightly slower
 | |
| 	// in high-traffic environments.)
 | |
| 	MatchPath []string
 | |
| 
 | |
| 	// MatchPathRE matches requests by a regular expression on the URI's path.
 | |
| 	// Path matching is performed in the unescaped (decoded) form of the path.
 | |
| 	//
 | |
| 	// Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}`
 | |
| 	// where `name` is the regular expression's name, and `capture_group` is either
 | |
| 	// the named or positional capture group from the expression itself. If no name
 | |
| 	// is given, then the placeholder omits the name: `{http.regexp.capture_group}`
 | |
| 	// (potentially leading to collisions).
 | |
| 	MatchPathRE struct{ MatchRegexp }
 | |
| 
 | |
| 	// MatchMethod matches requests by the method.
 | |
| 	MatchMethod []string
 | |
| 
 | |
| 	// MatchQuery matches requests by the URI's query string. It takes a JSON object
 | |
| 	// keyed by the query keys, with an array of string values to match for that key.
 | |
| 	// Query key matches are exact, but wildcards may be used for value matches. Both
 | |
| 	// keys and values may be placeholders.
 | |
| 	//
 | |
| 	// An example of the structure to match `?key=value&topic=api&query=something` is:
 | |
| 	//
 | |
| 	// ```json
 | |
| 	// {
 | |
| 	// 	"key": ["value"],
 | |
| 	//	"topic": ["api"],
 | |
| 	//	"query": ["*"]
 | |
| 	// }
 | |
| 	// ```
 | |
| 	//
 | |
| 	// Invalid query strings, including those with bad escapings or illegal characters
 | |
| 	// like semicolons, will fail to parse and thus fail to match.
 | |
| 	//
 | |
| 	// **NOTE:** Notice that query string values are arrays, not singular values. This is
 | |
| 	// because repeated keys are valid in query strings, and each one may have a
 | |
| 	// different value. This matcher will match for a key if any one of its configured
 | |
| 	// values is assigned in the query string. Backend applications relying on query
 | |
| 	// strings MUST take into consideration that query string values are arrays and can
 | |
| 	// have multiple values.
 | |
| 	MatchQuery url.Values
 | |
| 
 | |
| 	// MatchHeader matches requests by header fields. The key is the field
 | |
| 	// name and the array is the list of field values. It performs fast,
 | |
| 	// exact string comparisons of the field values. Fast prefix, suffix,
 | |
| 	// and substring matches can also be done by suffixing, prefixing, or
 | |
| 	// surrounding the value with the wildcard `*` character, respectively.
 | |
| 	// If a list is null, the header must not exist. If the list is empty,
 | |
| 	// the field must simply exist, regardless of its value.
 | |
| 	//
 | |
| 	// **NOTE:** Notice that header values are arrays, not singular values. This is
 | |
| 	// because repeated fields are valid in headers, and each one may have a
 | |
| 	// different value. This matcher will match for a field if any one of its configured
 | |
| 	// values matches in the header. Backend applications relying on headers MUST take
 | |
| 	// into consideration that header field values are arrays and can have multiple
 | |
| 	// values.
 | |
| 	MatchHeader http.Header
 | |
| 
 | |
| 	// MatchHeaderRE matches requests by a regular expression on header fields.
 | |
| 	//
 | |
| 	// Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}`
 | |
| 	// where `name` is the regular expression's name, and `capture_group` is either
 | |
| 	// the named or positional capture group from the expression itself. If no name
 | |
| 	// is given, then the placeholder omits the name: `{http.regexp.capture_group}`
 | |
| 	// (potentially leading to collisions).
 | |
| 	MatchHeaderRE map[string]*MatchRegexp
 | |
| 
 | |
| 	// MatchProtocol matches requests by protocol. Recognized values are
 | |
| 	// "http", "https", and "grpc" for broad protocol matches, or specific
 | |
| 	// HTTP versions can be specified like so: "http/1", "http/1.1",
 | |
| 	// "http/2", "http/3", or minimum versions: "http/2+", etc.
 | |
| 	MatchProtocol string
 | |
| 
 | |
| 	// MatchRemoteIP matches requests by client IP (or CIDR range).
 | |
| 	MatchRemoteIP struct {
 | |
| 		// The IPs or CIDR ranges to match.
 | |
| 		Ranges []string `json:"ranges,omitempty"`
 | |
| 
 | |
| 		// If true, prefer the first IP in the request's X-Forwarded-For
 | |
| 		// header, if present, rather than the immediate peer's IP, as
 | |
| 		// the reference IP against which to match. Note that it is easy
 | |
| 		// to spoof request headers. Default: false
 | |
| 		Forwarded bool `json:"forwarded,omitempty"`
 | |
| 
 | |
| 		// cidrs and zones vars should aligned always in the same
 | |
| 		// length and indexes for matching later
 | |
| 		cidrs  []*netip.Prefix
 | |
| 		zones  []string
 | |
| 		logger *zap.Logger
 | |
| 	}
 | |
| 
 | |
| 	// MatchNot matches requests by negating the results of its matcher
 | |
| 	// sets. A single "not" matcher takes one or more matcher sets. Each
 | |
| 	// matcher set is OR'ed; in other words, if any matcher set returns
 | |
| 	// true, the final result of the "not" matcher is false. Individual
 | |
| 	// matchers within a set work the same (i.e. different matchers in
 | |
| 	// the same set are AND'ed).
 | |
| 	//
 | |
| 	// NOTE: The generated docs which describe the structure of this
 | |
| 	// module are wrong because of how this type unmarshals JSON in a
 | |
| 	// custom way. The correct structure is:
 | |
| 	//
 | |
| 	// ```json
 | |
| 	// [
 | |
| 	// 	{},
 | |
| 	// 	{}
 | |
| 	// ]
 | |
| 	// ```
 | |
| 	//
 | |
| 	// where each of the array elements is a matcher set, i.e. an
 | |
| 	// object keyed by matcher name.
 | |
| 	MatchNot struct {
 | |
| 		MatcherSetsRaw []caddy.ModuleMap `json:"-" caddy:"namespace=http.matchers"`
 | |
| 		MatcherSets    []MatcherSet      `json:"-"`
 | |
| 	}
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	caddy.RegisterModule(MatchHost{})
 | |
| 	caddy.RegisterModule(MatchPath{})
 | |
| 	caddy.RegisterModule(MatchPathRE{})
 | |
| 	caddy.RegisterModule(MatchMethod{})
 | |
| 	caddy.RegisterModule(MatchQuery{})
 | |
| 	caddy.RegisterModule(MatchHeader{})
 | |
| 	caddy.RegisterModule(MatchHeaderRE{})
 | |
| 	caddy.RegisterModule(new(MatchProtocol))
 | |
| 	caddy.RegisterModule(MatchRemoteIP{})
 | |
| 	caddy.RegisterModule(MatchNot{})
 | |
| }
 | |
| 
 | |
| // CaddyModule returns the Caddy module information.
 | |
| func (MatchHost) CaddyModule() caddy.ModuleInfo {
 | |
| 	return caddy.ModuleInfo{
 | |
| 		ID:  "http.matchers.host",
 | |
| 		New: func() caddy.Module { return new(MatchHost) },
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
 | |
| func (m *MatchHost) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | |
| 	for d.Next() {
 | |
| 		*m = append(*m, d.RemainingArgs()...)
 | |
| 		if d.NextBlock(0) {
 | |
| 			return d.Err("malformed host matcher: blocks are not supported")
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Provision sets up and validates m, including making it more efficient for large lists.
 | |
| func (m MatchHost) Provision(_ caddy.Context) error {
 | |
| 	// check for duplicates; they are nonsensical and reduce efficiency
 | |
| 	// (we could just remove them, but the user should know their config is erroneous)
 | |
| 	seen := make(map[string]int)
 | |
| 	for i, h := range m {
 | |
| 		h = strings.ToLower(h)
 | |
| 		if firstI, ok := seen[h]; ok {
 | |
| 			return fmt.Errorf("host at index %d is repeated at index %d: %s", firstI, i, h)
 | |
| 		}
 | |
| 		seen[h] = i
 | |
| 	}
 | |
| 
 | |
| 	if m.large() {
 | |
| 		// sort the slice lexicographically, grouping "fuzzy" entries (wildcards and placeholders)
 | |
| 		// at the front of the list; this allows us to use binary search for exact matches, which
 | |
| 		// we have seen from experience is the most common kind of value in large lists; and any
 | |
| 		// other kinds of values (wildcards and placeholders) are grouped in front so the linear
 | |
| 		// search should find a match fairly quickly
 | |
| 		sort.Slice(m, func(i, j int) bool {
 | |
| 			iInexact, jInexact := m.fuzzy(m[i]), m.fuzzy(m[j])
 | |
| 			if iInexact && !jInexact {
 | |
| 				return true
 | |
| 			}
 | |
| 			if !iInexact && jInexact {
 | |
| 				return false
 | |
| 			}
 | |
| 			return m[i] < m[j]
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Match returns true if r matches m.
 | |
| func (m MatchHost) Match(r *http.Request) bool {
 | |
| 	reqHost, _, err := net.SplitHostPort(r.Host)
 | |
| 	if err != nil {
 | |
| 		// OK; probably didn't have a port
 | |
| 		reqHost = r.Host
 | |
| 
 | |
| 		// make sure we strip the brackets from IPv6 addresses
 | |
| 		reqHost = strings.TrimPrefix(reqHost, "[")
 | |
| 		reqHost = strings.TrimSuffix(reqHost, "]")
 | |
| 	}
 | |
| 
 | |
| 	if m.large() {
 | |
| 		// fast path: locate exact match using binary search (about 100-1000x faster for large lists)
 | |
| 		pos := sort.Search(len(m), func(i int) bool {
 | |
| 			if m.fuzzy(m[i]) {
 | |
| 				return false
 | |
| 			}
 | |
| 			return m[i] >= reqHost
 | |
| 		})
 | |
| 		if pos < len(m) && m[pos] == reqHost {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
 | |
| 
 | |
| outer:
 | |
| 	for _, host := range m {
 | |
| 		// fast path: if matcher is large, we already know we don't have an exact
 | |
| 		// match, so we're only looking for fuzzy match now, which should be at the
 | |
| 		// front of the list; if we have reached a value that is not fuzzy, there
 | |
| 		// will be no match and we can short-circuit for efficiency
 | |
| 		if m.large() && !m.fuzzy(host) {
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		host = repl.ReplaceAll(host, "")
 | |
| 		if strings.Contains(host, "*") {
 | |
| 			patternParts := strings.Split(host, ".")
 | |
| 			incomingParts := strings.Split(reqHost, ".")
 | |
| 			if len(patternParts) != len(incomingParts) {
 | |
| 				continue
 | |
| 			}
 | |
| 			for i := range patternParts {
 | |
| 				if patternParts[i] == "*" {
 | |
| 					continue
 | |
| 				}
 | |
| 				if !strings.EqualFold(patternParts[i], incomingParts[i]) {
 | |
| 					continue outer
 | |
| 				}
 | |
| 			}
 | |
| 			return true
 | |
| 		} else if strings.EqualFold(reqHost, host) {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // CELLibrary produces options that expose this matcher for use in CEL
 | |
| // expression matchers.
 | |
| //
 | |
| // Example:
 | |
| //
 | |
| //	expression host('localhost')
 | |
| func (MatchHost) CELLibrary(ctx caddy.Context) (cel.Library, error) {
 | |
| 	return CELMatcherImpl(
 | |
| 		"host",
 | |
| 		"host_match_request_list",
 | |
| 		[]*cel.Type{cel.ListType(cel.StringType)},
 | |
| 		func(data ref.Val) (RequestMatcher, error) {
 | |
| 			refStringList := reflect.TypeOf([]string{})
 | |
| 			strList, err := data.ConvertToNative(refStringList)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			matcher := MatchHost(strList.([]string))
 | |
| 			err = matcher.Provision(ctx)
 | |
| 			return matcher, err
 | |
| 		},
 | |
| 	)
 | |
| }
 | |
| 
 | |
| // fuzzy returns true if the given hostname h is not a specific
 | |
| // hostname, e.g. has placeholders or wildcards.
 | |
| func (MatchHost) fuzzy(h string) bool { return strings.ContainsAny(h, "{*") }
 | |
| 
 | |
| // large returns true if m is considered to be large. Optimizing
 | |
| // the matcher for smaller lists has diminishing returns.
 | |
| // See related benchmark function in test file to conduct experiments.
 | |
| func (m MatchHost) large() bool { return len(m) > 100 }
 | |
| 
 | |
| // CaddyModule returns the Caddy module information.
 | |
| func (MatchPath) CaddyModule() caddy.ModuleInfo {
 | |
| 	return caddy.ModuleInfo{
 | |
| 		ID:  "http.matchers.path",
 | |
| 		New: func() caddy.Module { return new(MatchPath) },
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Provision lower-cases the paths in m to ensure case-insensitive matching.
 | |
| func (m MatchPath) Provision(_ caddy.Context) error {
 | |
| 	for i := range m {
 | |
| 		if m[i] == "*" && i > 0 {
 | |
| 			// will always match, so just put it first
 | |
| 			m[0] = m[i]
 | |
| 			break
 | |
| 		}
 | |
| 		m[i] = strings.ToLower(m[i])
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Match returns true if r matches m.
 | |
| func (m MatchPath) Match(r *http.Request) bool {
 | |
| 	// Even though RFC 9110 says that path matching is case-sensitive
 | |
| 	// (https://www.rfc-editor.org/rfc/rfc9110.html#section-4.2.3),
 | |
| 	// we do case-insensitive matching to mitigate security issues
 | |
| 	// related to differences between operating systems, applications,
 | |
| 	// etc; if case-sensitive matching is needed, the regex matcher
 | |
| 	// can be used instead.
 | |
| 	reqPath := strings.ToLower(r.URL.Path)
 | |
| 
 | |
| 	// See #2917; Windows ignores trailing dots and spaces
 | |
| 	// when accessing files (sigh), potentially causing a
 | |
| 	// security risk (cry) if PHP files end up being served
 | |
| 	// as static files, exposing the source code, instead of
 | |
| 	// being matched by *.php to be treated as PHP scripts.
 | |
| 	reqPath = strings.TrimRight(reqPath, ". ")
 | |
| 
 | |
| 	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
 | |
| 
 | |
| 	for _, matchPattern := range m {
 | |
| 		matchPattern = repl.ReplaceAll(matchPattern, "")
 | |
| 
 | |
| 		// special case: whole path is wildcard; this is unnecessary
 | |
| 		// as it matches all requests, which is the same as no matcher
 | |
| 		if matchPattern == "*" {
 | |
| 			return true
 | |
| 		}
 | |
| 
 | |
| 		// Clean the path, merge doubled slashes, etc.
 | |
| 		// This ensures maliciously crafted requests can't bypass
 | |
| 		// the path matcher. See #4407. Good security posture
 | |
| 		// requires that we should do all we can to reduce any
 | |
| 		// funny-looking paths into "normalized" forms such that
 | |
| 		// weird variants can't sneak by.
 | |
| 		//
 | |
| 		// How we clean the path depends on the kind of pattern:
 | |
| 		// we either merge slashes or we don't. If the pattern
 | |
| 		// has double slashes, we preserve them in the path.
 | |
| 		//
 | |
| 		// TODO: Despite the fact that the *vast* majority of path
 | |
| 		// matchers have only 1 pattern, a possible optimization is
 | |
| 		// to remember the cleaned form of the path for future
 | |
| 		// iterations; it's just that the way we clean depends on
 | |
| 		// the kind of pattern.
 | |
| 
 | |
| 		mergeSlashes := !strings.Contains(matchPattern, "//")
 | |
| 
 | |
| 		// if '%' appears in the match pattern, we interpret that to mean
 | |
| 		// the intent is to compare that part of the path in raw/escaped
 | |
| 		// space; i.e. "%40"=="%40", not "@", and "%2F"=="%2F", not "/"
 | |
| 		if strings.Contains(matchPattern, "%") {
 | |
| 			reqPathForPattern := CleanPath(r.URL.EscapedPath(), mergeSlashes)
 | |
| 			if m.matchPatternWithEscapeSequence(reqPathForPattern, matchPattern) {
 | |
| 				return true
 | |
| 			}
 | |
| 
 | |
| 			// doing prefix/suffix/substring matches doesn't make sense
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		reqPathForPattern := CleanPath(reqPath, mergeSlashes)
 | |
| 
 | |
| 		// for substring, prefix, and suffix matching, only perform those
 | |
| 		// special, fast matches if they are the only wildcards in the pattern;
 | |
| 		// otherwise we assume a globular match if any * appears in the middle
 | |
| 
 | |
| 		// special case: first and last characters are wildcard,
 | |
| 		// treat it as a fast substring match
 | |
| 		if strings.Count(matchPattern, "*") == 2 &&
 | |
| 			strings.HasPrefix(matchPattern, "*") &&
 | |
| 			strings.HasSuffix(matchPattern, "*") &&
 | |
| 			strings.Count(matchPattern, "*") == 2 {
 | |
| 			if strings.Contains(reqPathForPattern, matchPattern[1:len(matchPattern)-1]) {
 | |
| 				return true
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// only perform prefix/suffix match if it is the only wildcard...
 | |
| 		// I think that is more correct most of the time
 | |
| 		if strings.Count(matchPattern, "*") == 1 {
 | |
| 			// special case: first character is a wildcard,
 | |
| 			// treat it as a fast suffix match
 | |
| 			if strings.HasPrefix(matchPattern, "*") {
 | |
| 				if strings.HasSuffix(reqPathForPattern, matchPattern[1:]) {
 | |
| 					return true
 | |
| 				}
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			// special case: last character is a wildcard,
 | |
| 			// treat it as a fast prefix match
 | |
| 			if strings.HasSuffix(matchPattern, "*") {
 | |
| 				if strings.HasPrefix(reqPathForPattern, matchPattern[:len(matchPattern)-1]) {
 | |
| 					return true
 | |
| 				}
 | |
| 				continue
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// at last, use globular matching, which also is exact matching
 | |
| 		// if there are no glob/wildcard chars; we ignore the error here
 | |
| 		// because we can't handle it anyway
 | |
| 		matches, _ := path.Match(matchPattern, reqPathForPattern)
 | |
| 		if matches {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath string) bool {
 | |
| 	// We would just compare the pattern against r.URL.Path,
 | |
| 	// but the pattern contains %, indicating that we should
 | |
| 	// compare at least some part of the path in raw/escaped
 | |
| 	// space, not normalized space; so we build the string we
 | |
| 	// will compare against by adding the normalized parts
 | |
| 	// of the path, then switching to the escaped parts where
 | |
| 	// the pattern hints to us wherever % is present.
 | |
| 	var sb strings.Builder
 | |
| 
 | |
| 	// iterate the pattern and escaped path in lock-step;
 | |
| 	// increment iPattern every time we consume a char from the pattern,
 | |
| 	// increment iPath every time we consume a char from the path;
 | |
| 	// iPattern and iPath are our cursors/iterator positions for each string
 | |
| 	var iPattern, iPath int
 | |
| 	for {
 | |
| 		if iPattern >= len(matchPath) || iPath >= len(escapedPath) {
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		// get the next character from the request path
 | |
| 
 | |
| 		pathCh := string(escapedPath[iPath])
 | |
| 		var escapedPathCh string
 | |
| 
 | |
| 		// normalize (decode) escape sequences
 | |
| 		if pathCh == "%" && len(escapedPath) >= iPath+3 {
 | |
| 			// hold onto this in case we find out the intent is to match in escaped space here;
 | |
| 			// we lowercase it even though technically the spec says: "For consistency, URI
 | |
| 			// producers and normalizers should use uppercase hexadecimal digits for all percent-
 | |
| 			// encodings" (RFC 3986 section 2.1) - we lowercased the matcher pattern earlier in
 | |
| 			// provisioning so we do the same here to gain case-insensitivity in equivalence;
 | |
| 			// besides, this string is never shown visibly
 | |
| 			escapedPathCh = strings.ToLower(escapedPath[iPath : iPath+3])
 | |
| 
 | |
| 			var err error
 | |
| 			pathCh, err = url.PathUnescape(escapedPathCh)
 | |
| 			if err != nil {
 | |
| 				// should be impossible unless EscapedPath() is giving us an invalid sequence!
 | |
| 				return false
 | |
| 			}
 | |
| 			iPath += 2 // escape sequence is 2 bytes longer than normal char
 | |
| 		}
 | |
| 
 | |
| 		// now get the next character from the pattern
 | |
| 
 | |
| 		normalize := true
 | |
| 		switch matchPath[iPattern] {
 | |
| 		case '%':
 | |
| 			// escape sequence
 | |
| 
 | |
| 			// if not a wildcard ("%*"), compare literally; consume next two bytes of pattern
 | |
| 			if len(matchPath) >= iPattern+3 && matchPath[iPattern+1] != '*' {
 | |
| 				sb.WriteString(escapedPathCh)
 | |
| 				iPath++
 | |
| 				iPattern += 2
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			// escaped wildcard sequence; consume next byte only ('*')
 | |
| 			iPattern++
 | |
| 			normalize = false
 | |
| 
 | |
| 			fallthrough
 | |
| 		case '*':
 | |
| 			// wildcard, so consume until next matching character
 | |
| 			remaining := escapedPath[iPath:]
 | |
| 			until := len(escapedPath) - iPath // go until end of string...
 | |
| 			if iPattern < len(matchPath)-1 {  // ...unless the * is not at the end
 | |
| 				nextCh := matchPath[iPattern+1]
 | |
| 				until = strings.IndexByte(remaining, nextCh)
 | |
| 				if until == -1 {
 | |
| 					// terminating char of wildcard span not found, so definitely no match
 | |
| 					return false
 | |
| 				}
 | |
| 			}
 | |
| 			if until == 0 {
 | |
| 				// empty span; nothing to add on this iteration
 | |
| 				break
 | |
| 			}
 | |
| 			next := remaining[:until]
 | |
| 			if normalize {
 | |
| 				var err error
 | |
| 				next, err = url.PathUnescape(next)
 | |
| 				if err != nil {
 | |
| 					return false // should be impossible anyway
 | |
| 				}
 | |
| 			}
 | |
| 			sb.WriteString(next)
 | |
| 			iPath += until
 | |
| 		default:
 | |
| 			sb.WriteString(pathCh)
 | |
| 			iPath++
 | |
| 		}
 | |
| 
 | |
| 		iPattern++
 | |
| 	}
 | |
| 
 | |
| 	// we can now treat rawpath globs (%*) as regular globs (*)
 | |
| 	matchPath = strings.ReplaceAll(matchPath, "%*", "*")
 | |
| 
 | |
| 	// ignore error here because we can't handle it anyway=
 | |
| 	matches, _ := path.Match(matchPath, sb.String())
 | |
| 	return matches
 | |
| }
 | |
| 
 | |
| // CELLibrary produces options that expose this matcher for use in CEL
 | |
| // expression matchers.
 | |
| //
 | |
| // Example:
 | |
| //
 | |
| //	expression path('*substring*', '*suffix')
 | |
| func (MatchPath) CELLibrary(ctx caddy.Context) (cel.Library, error) {
 | |
| 	return CELMatcherImpl(
 | |
| 		// name of the macro, this is the function name that users see when writing expressions.
 | |
| 		"path",
 | |
| 		// name of the function that the macro will be rewritten to call.
 | |
| 		"path_match_request_list",
 | |
| 		// internal data type of the MatchPath value.
 | |
| 		[]*cel.Type{cel.ListType(cel.StringType)},
 | |
| 		// function to convert a constant list of strings to a MatchPath instance.
 | |
| 		func(data ref.Val) (RequestMatcher, error) {
 | |
| 			refStringList := reflect.TypeOf([]string{})
 | |
| 			strList, err := data.ConvertToNative(refStringList)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			matcher := MatchPath(strList.([]string))
 | |
| 			err = matcher.Provision(ctx)
 | |
| 			return matcher, err
 | |
| 		},
 | |
| 	)
 | |
| }
 | |
| 
 | |
| // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
 | |
| func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | |
| 	for d.Next() {
 | |
| 		*m = append(*m, d.RemainingArgs()...)
 | |
| 		if d.NextBlock(0) {
 | |
| 			return d.Err("malformed path matcher: blocks are not supported")
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // CaddyModule returns the Caddy module information.
 | |
| func (MatchPathRE) CaddyModule() caddy.ModuleInfo {
 | |
| 	return caddy.ModuleInfo{
 | |
| 		ID:  "http.matchers.path_regexp",
 | |
| 		New: func() caddy.Module { return new(MatchPathRE) },
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Match returns true if r matches m.
 | |
| func (m MatchPathRE) Match(r *http.Request) bool {
 | |
| 	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
 | |
| 
 | |
| 	// Clean the path, merges doubled slashes, etc.
 | |
| 	// This ensures maliciously crafted requests can't bypass
 | |
| 	// the path matcher. See #4407
 | |
| 	cleanedPath := cleanPath(r.URL.Path)
 | |
| 
 | |
| 	return m.MatchRegexp.Match(cleanedPath, repl)
 | |
| }
 | |
| 
 | |
| // CELLibrary produces options that expose this matcher for use in CEL
 | |
| // expression matchers.
 | |
| //
 | |
| // Example:
 | |
| //
 | |
| //	expression path_regexp('^/bar')
 | |
| func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
 | |
| 	unnamedPattern, err := CELMatcherImpl(
 | |
| 		"path_regexp",
 | |
| 		"path_regexp_request_string",
 | |
| 		[]*cel.Type{cel.StringType},
 | |
| 		func(data ref.Val) (RequestMatcher, error) {
 | |
| 			pattern := data.(types.String)
 | |
| 			matcher := MatchPathRE{MatchRegexp{Pattern: string(pattern)}}
 | |
| 			err := matcher.Provision(ctx)
 | |
| 			return matcher, err
 | |
| 		},
 | |
| 	)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	namedPattern, err := CELMatcherImpl(
 | |
| 		"path_regexp",
 | |
| 		"path_regexp_request_string_string",
 | |
| 		[]*cel.Type{cel.StringType, cel.StringType},
 | |
| 		func(data ref.Val) (RequestMatcher, error) {
 | |
| 			refStringList := reflect.TypeOf([]string{})
 | |
| 			params, err := data.ConvertToNative(refStringList)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			strParams := params.([]string)
 | |
| 			matcher := MatchPathRE{MatchRegexp{Name: strParams[0], Pattern: strParams[1]}}
 | |
| 			err = matcher.Provision(ctx)
 | |
| 			return matcher, err
 | |
| 		},
 | |
| 	)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	envOpts := append(unnamedPattern.CompileOptions(), namedPattern.CompileOptions()...)
 | |
| 	prgOpts := append(unnamedPattern.ProgramOptions(), namedPattern.ProgramOptions()...)
 | |
| 	return NewMatcherCELLibrary(envOpts, prgOpts), nil
 | |
| }
 | |
| 
 | |
| // CaddyModule returns the Caddy module information.
 | |
| func (MatchMethod) CaddyModule() caddy.ModuleInfo {
 | |
| 	return caddy.ModuleInfo{
 | |
| 		ID:  "http.matchers.method",
 | |
| 		New: func() caddy.Module { return new(MatchMethod) },
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
 | |
| func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | |
| 	for d.Next() {
 | |
| 		*m = append(*m, d.RemainingArgs()...)
 | |
| 		if d.NextBlock(0) {
 | |
| 			return d.Err("malformed method matcher: blocks are not supported")
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Match returns true if r matches m.
 | |
| func (m MatchMethod) Match(r *http.Request) bool {
 | |
| 	for _, method := range m {
 | |
| 		if r.Method == method {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // CELLibrary produces options that expose this matcher for use in CEL
 | |
| // expression matchers.
 | |
| //
 | |
| // Example:
 | |
| //
 | |
| //	expression method('PUT', 'POST')
 | |
| func (MatchMethod) CELLibrary(_ caddy.Context) (cel.Library, error) {
 | |
| 	return CELMatcherImpl(
 | |
| 		"method",
 | |
| 		"method_request_list",
 | |
| 		[]*cel.Type{cel.ListType(cel.StringType)},
 | |
| 		func(data ref.Val) (RequestMatcher, error) {
 | |
| 			refStringList := reflect.TypeOf([]string{})
 | |
| 			strList, err := data.ConvertToNative(refStringList)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			return MatchMethod(strList.([]string)), nil
 | |
| 		},
 | |
| 	)
 | |
| }
 | |
| 
 | |
| // CaddyModule returns the Caddy module information.
 | |
| func (MatchQuery) CaddyModule() caddy.ModuleInfo {
 | |
| 	return caddy.ModuleInfo{
 | |
| 		ID:  "http.matchers.query",
 | |
| 		New: func() caddy.Module { return new(MatchQuery) },
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
 | |
| func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | |
| 	if *m == nil {
 | |
| 		*m = make(map[string][]string)
 | |
| 	}
 | |
| 	for d.Next() {
 | |
| 		for _, query := range d.RemainingArgs() {
 | |
| 			if query == "" {
 | |
| 				continue
 | |
| 			}
 | |
| 			before, after, found := strings.Cut(query, "=")
 | |
| 			if !found {
 | |
| 				return d.Errf("malformed query matcher token: %s; must be in param=val format", d.Val())
 | |
| 			}
 | |
| 			url.Values(*m).Add(before, after)
 | |
| 		}
 | |
| 		if d.NextBlock(0) {
 | |
| 			return d.Err("malformed query matcher: blocks are not supported")
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Match returns true if r matches m. An empty m matches an empty query string.
 | |
| func (m MatchQuery) Match(r *http.Request) bool {
 | |
| 	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
 | |
| 
 | |
| 	// parse query string just once, for efficiency
 | |
| 	parsed, err := url.ParseQuery(r.URL.RawQuery)
 | |
| 	if err != nil {
 | |
| 		// Illegal query string. Likely bad escape sequence or unescaped literals.
 | |
| 		// Note that semicolons in query string have a controversial history. Summaries:
 | |
| 		// - https://github.com/golang/go/issues/50034
 | |
| 		// - https://github.com/golang/go/issues/25192
 | |
| 		// Despite the URL WHATWG spec mandating the use of & separators for query strings,
 | |
| 		// every URL parser implementation is different, and Filippo Valsorda rightly wrote:
 | |
| 		// "Relying on parser alignment for security is doomed." Overall conclusion is that
 | |
| 		// splitting on & and rejecting ; in key=value pairs is safer than accepting raw ;.
 | |
| 		// We regard the Go team's decision as sound and thus reject malformed query strings.
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	for param, vals := range m {
 | |
| 		param = repl.ReplaceAll(param, "")
 | |
| 		paramVal, found := parsed[param]
 | |
| 		if found {
 | |
| 			for _, v := range vals {
 | |
| 				v = repl.ReplaceAll(v, "")
 | |
| 				if paramVal[0] == v || v == "*" {
 | |
| 					return true
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return len(m) == 0 && len(r.URL.Query()) == 0
 | |
| }
 | |
| 
 | |
| // CELLibrary produces options that expose this matcher for use in CEL
 | |
| // expression matchers.
 | |
| //
 | |
| // Example:
 | |
| //
 | |
| //	expression query({'sort': 'asc'}) || query({'foo': ['*bar*', 'baz']})
 | |
| func (MatchQuery) CELLibrary(_ caddy.Context) (cel.Library, error) {
 | |
| 	return CELMatcherImpl(
 | |
| 		"query",
 | |
| 		"query_matcher_request_map",
 | |
| 		[]*cel.Type{CELTypeJSON},
 | |
| 		func(data ref.Val) (RequestMatcher, error) {
 | |
| 			mapStrListStr, err := CELValueToMapStrList(data)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			return MatchQuery(url.Values(mapStrListStr)), nil
 | |
| 		},
 | |
| 	)
 | |
| }
 | |
| 
 | |
| // CaddyModule returns the Caddy module information.
 | |
| func (MatchHeader) CaddyModule() caddy.ModuleInfo {
 | |
| 	return caddy.ModuleInfo{
 | |
| 		ID:  "http.matchers.header",
 | |
| 		New: func() caddy.Module { return new(MatchHeader) },
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
 | |
| func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | |
| 	if *m == nil {
 | |
| 		*m = make(map[string][]string)
 | |
| 	}
 | |
| 	for d.Next() {
 | |
| 		var field, val string
 | |
| 		if !d.Args(&field) {
 | |
| 			return d.Errf("malformed header matcher: expected field")
 | |
| 		}
 | |
| 
 | |
| 		if strings.HasPrefix(field, "!") {
 | |
| 			if len(field) == 1 {
 | |
| 				return d.Errf("malformed header matcher: must have field name following ! character")
 | |
| 			}
 | |
| 
 | |
| 			field = field[1:]
 | |
| 			headers := *m
 | |
| 			headers[field] = nil
 | |
| 			m = &headers
 | |
| 			if d.NextArg() {
 | |
| 				return d.Errf("malformed header matcher: null matching headers cannot have a field value")
 | |
| 			}
 | |
| 		} else {
 | |
| 			if !d.NextArg() {
 | |
| 				return d.Errf("malformed header matcher: expected both field and value")
 | |
| 			}
 | |
| 
 | |
| 			// If multiple header matchers with the same header field are defined,
 | |
| 			// we want to add the existing to the list of headers (will be OR'ed)
 | |
| 			val = d.Val()
 | |
| 			http.Header(*m).Add(field, val)
 | |
| 		}
 | |
| 
 | |
| 		if d.NextBlock(0) {
 | |
| 			return d.Err("malformed header matcher: blocks are not supported")
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Match returns true if r matches m.
 | |
| func (m MatchHeader) Match(r *http.Request) bool {
 | |
| 	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
 | |
| 	return matchHeaders(r.Header, http.Header(m), r.Host, repl)
 | |
| }
 | |
| 
 | |
| // CELLibrary produces options that expose this matcher for use in CEL
 | |
| // expression matchers.
 | |
| //
 | |
| // Example:
 | |
| //
 | |
| //	expression header({'content-type': 'image/png'})
 | |
| //	expression header({'foo': ['bar', 'baz']}) // match bar or baz
 | |
| func (MatchHeader) CELLibrary(_ caddy.Context) (cel.Library, error) {
 | |
| 	return CELMatcherImpl(
 | |
| 		"header",
 | |
| 		"header_matcher_request_map",
 | |
| 		[]*cel.Type{CELTypeJSON},
 | |
| 		func(data ref.Val) (RequestMatcher, error) {
 | |
| 			mapStrListStr, err := CELValueToMapStrList(data)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			return MatchHeader(http.Header(mapStrListStr)), nil
 | |
| 		},
 | |
| 	)
 | |
| }
 | |
| 
 | |
| // getHeaderFieldVals returns the field values for the given fieldName from input.
 | |
| // The host parameter should be obtained from the http.Request.Host field since
 | |
| // net/http removes it from the header map.
 | |
| func getHeaderFieldVals(input http.Header, fieldName, host string) []string {
 | |
| 	fieldName = textproto.CanonicalMIMEHeaderKey(fieldName)
 | |
| 	if fieldName == "Host" && host != "" {
 | |
| 		return []string{host}
 | |
| 	}
 | |
| 	return input[fieldName]
 | |
| }
 | |
| 
 | |
| // matchHeaders returns true if input matches the criteria in against without regex.
 | |
| // The host parameter should be obtained from the http.Request.Host field since
 | |
| // net/http removes it from the header map.
 | |
| func matchHeaders(input, against http.Header, host string, repl *caddy.Replacer) bool {
 | |
| 	for field, allowedFieldVals := range against {
 | |
| 		actualFieldVals := getHeaderFieldVals(input, field, host)
 | |
| 		if allowedFieldVals != nil && len(allowedFieldVals) == 0 && actualFieldVals != nil {
 | |
| 			// a non-nil but empty list of allowed values means
 | |
| 			// match if the header field exists at all
 | |
| 			continue
 | |
| 		}
 | |
| 		if allowedFieldVals == nil && actualFieldVals == nil {
 | |
| 			// a nil list means match if the header does not exist at all
 | |
| 			continue
 | |
| 		}
 | |
| 		var match bool
 | |
| 	fieldVals:
 | |
| 		for _, actualFieldVal := range actualFieldVals {
 | |
| 			for _, allowedFieldVal := range allowedFieldVals {
 | |
| 				if repl != nil {
 | |
| 					allowedFieldVal = repl.ReplaceAll(allowedFieldVal, "")
 | |
| 				}
 | |
| 				switch {
 | |
| 				case allowedFieldVal == "*":
 | |
| 					match = true
 | |
| 				case strings.HasPrefix(allowedFieldVal, "*") && strings.HasSuffix(allowedFieldVal, "*"):
 | |
| 					match = strings.Contains(actualFieldVal, allowedFieldVal[1:len(allowedFieldVal)-1])
 | |
| 				case strings.HasPrefix(allowedFieldVal, "*"):
 | |
| 					match = strings.HasSuffix(actualFieldVal, allowedFieldVal[1:])
 | |
| 				case strings.HasSuffix(allowedFieldVal, "*"):
 | |
| 					match = strings.HasPrefix(actualFieldVal, allowedFieldVal[:len(allowedFieldVal)-1])
 | |
| 				default:
 | |
| 					match = actualFieldVal == allowedFieldVal
 | |
| 				}
 | |
| 				if match {
 | |
| 					break fieldVals
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		if !match {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // CaddyModule returns the Caddy module information.
 | |
| func (MatchHeaderRE) CaddyModule() caddy.ModuleInfo {
 | |
| 	return caddy.ModuleInfo{
 | |
| 		ID:  "http.matchers.header_regexp",
 | |
| 		New: func() caddy.Module { return new(MatchHeaderRE) },
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
 | |
| func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | |
| 	if *m == nil {
 | |
| 		*m = make(map[string]*MatchRegexp)
 | |
| 	}
 | |
| 	for d.Next() {
 | |
| 		var first, second, third string
 | |
| 		if !d.Args(&first, &second) {
 | |
| 			return d.ArgErr()
 | |
| 		}
 | |
| 
 | |
| 		var name, field, val string
 | |
| 		if d.Args(&third) {
 | |
| 			name = first
 | |
| 			field = second
 | |
| 			val = third
 | |
| 		} else {
 | |
| 			field = first
 | |
| 			val = second
 | |
| 		}
 | |
| 
 | |
| 		// If there's already a pattern for this field
 | |
| 		// then we would end up overwriting the old one
 | |
| 		if (*m)[field] != nil {
 | |
| 			return d.Errf("header_regexp matcher can only be used once per named matcher, per header field: %s", field)
 | |
| 		}
 | |
| 
 | |
| 		(*m)[field] = &MatchRegexp{Pattern: val, Name: name}
 | |
| 
 | |
| 		if d.NextBlock(0) {
 | |
| 			return d.Err("malformed header_regexp matcher: blocks are not supported")
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Match returns true if r matches m.
 | |
| func (m MatchHeaderRE) Match(r *http.Request) bool {
 | |
| 	for field, rm := range m {
 | |
| 		actualFieldVals := getHeaderFieldVals(r.Header, field, r.Host)
 | |
| 		match := false
 | |
| 	fieldVal:
 | |
| 		for _, actualFieldVal := range actualFieldVals {
 | |
| 			repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
 | |
| 			if rm.Match(actualFieldVal, repl) {
 | |
| 				match = true
 | |
| 				break fieldVal
 | |
| 			}
 | |
| 		}
 | |
| 		if !match {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // Provision compiles m's regular expressions.
 | |
| func (m MatchHeaderRE) Provision(ctx caddy.Context) error {
 | |
| 	for _, rm := range m {
 | |
| 		err := rm.Provision(ctx)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Validate validates m's regular expressions.
 | |
| func (m MatchHeaderRE) Validate() error {
 | |
| 	for _, rm := range m {
 | |
| 		err := rm.Validate()
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // CELLibrary produces options that expose this matcher for use in CEL
 | |
| // expression matchers.
 | |
| //
 | |
| // Example:
 | |
| //
 | |
| //	expression header_regexp('foo', 'Field', 'fo+')
 | |
| func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
 | |
| 	unnamedPattern, err := CELMatcherImpl(
 | |
| 		"header_regexp",
 | |
| 		"header_regexp_request_string_string",
 | |
| 		[]*cel.Type{cel.StringType, cel.StringType},
 | |
| 		func(data ref.Val) (RequestMatcher, error) {
 | |
| 			refStringList := reflect.TypeOf([]string{})
 | |
| 			params, err := data.ConvertToNative(refStringList)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			strParams := params.([]string)
 | |
| 			matcher := MatchHeaderRE{}
 | |
| 			matcher[strParams[0]] = &MatchRegexp{Pattern: strParams[1], Name: ""}
 | |
| 			err = matcher.Provision(ctx)
 | |
| 			return matcher, err
 | |
| 		},
 | |
| 	)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	namedPattern, err := CELMatcherImpl(
 | |
| 		"header_regexp",
 | |
| 		"header_regexp_request_string_string_string",
 | |
| 		[]*cel.Type{cel.StringType, cel.StringType, cel.StringType},
 | |
| 		func(data ref.Val) (RequestMatcher, error) {
 | |
| 			refStringList := reflect.TypeOf([]string{})
 | |
| 			params, err := data.ConvertToNative(refStringList)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			strParams := params.([]string)
 | |
| 			matcher := MatchHeaderRE{}
 | |
| 			matcher[strParams[1]] = &MatchRegexp{Pattern: strParams[2], Name: strParams[0]}
 | |
| 			err = matcher.Provision(ctx)
 | |
| 			return matcher, err
 | |
| 		},
 | |
| 	)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	envOpts := append(unnamedPattern.CompileOptions(), namedPattern.CompileOptions()...)
 | |
| 	prgOpts := append(unnamedPattern.ProgramOptions(), namedPattern.ProgramOptions()...)
 | |
| 	return NewMatcherCELLibrary(envOpts, prgOpts), nil
 | |
| }
 | |
| 
 | |
| // CaddyModule returns the Caddy module information.
 | |
| func (MatchProtocol) CaddyModule() caddy.ModuleInfo {
 | |
| 	return caddy.ModuleInfo{
 | |
| 		ID:  "http.matchers.protocol",
 | |
| 		New: func() caddy.Module { return new(MatchProtocol) },
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Match returns true if r matches m.
 | |
| func (m MatchProtocol) Match(r *http.Request) bool {
 | |
| 	switch string(m) {
 | |
| 	case "grpc":
 | |
| 		return strings.HasPrefix(r.Header.Get("content-type"), "application/grpc")
 | |
| 	case "https":
 | |
| 		return r.TLS != nil
 | |
| 	case "http":
 | |
| 		return r.TLS == nil
 | |
| 	case "http/1.0":
 | |
| 		return r.ProtoMajor == 1 && r.ProtoMinor == 0
 | |
| 	case "http/1.0+":
 | |
| 		return r.ProtoAtLeast(1, 0)
 | |
| 	case "http/1.1":
 | |
| 		return r.ProtoMajor == 1 && r.ProtoMinor == 1
 | |
| 	case "http/1.1+":
 | |
| 		return r.ProtoAtLeast(1, 1)
 | |
| 	case "http/2":
 | |
| 		return r.ProtoMajor == 2
 | |
| 	case "http/2+":
 | |
| 		return r.ProtoAtLeast(2, 0)
 | |
| 	case "http/3":
 | |
| 		return r.ProtoMajor == 3
 | |
| 	case "http/3+":
 | |
| 		return r.ProtoAtLeast(3, 0)
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
 | |
| func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | |
| 	for d.Next() {
 | |
| 		var proto string
 | |
| 		if !d.Args(&proto) {
 | |
| 			return d.Err("expected exactly one protocol")
 | |
| 		}
 | |
| 		*m = MatchProtocol(proto)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // CELLibrary produces options that expose this matcher for use in CEL
 | |
| // expression matchers.
 | |
| //
 | |
| // Example:
 | |
| //
 | |
| //	expression protocol('https')
 | |
| func (MatchProtocol) CELLibrary(_ caddy.Context) (cel.Library, error) {
 | |
| 	return CELMatcherImpl(
 | |
| 		"protocol",
 | |
| 		"protocol_request_string",
 | |
| 		[]*cel.Type{cel.StringType},
 | |
| 		func(data ref.Val) (RequestMatcher, error) {
 | |
| 			protocolStr, ok := data.(types.String)
 | |
| 			if !ok {
 | |
| 				return nil, errors.New("protocol argument was not a string")
 | |
| 			}
 | |
| 			return MatchProtocol(strings.ToLower(string(protocolStr))), nil
 | |
| 		},
 | |
| 	)
 | |
| }
 | |
| 
 | |
| // CaddyModule returns the Caddy module information.
 | |
| func (MatchNot) CaddyModule() caddy.ModuleInfo {
 | |
| 	return caddy.ModuleInfo{
 | |
| 		ID:  "http.matchers.not",
 | |
| 		New: func() caddy.Module { return new(MatchNot) },
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
 | |
| func (m *MatchNot) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | |
| 	for d.Next() {
 | |
| 		matcherSet, err := ParseCaddyfileNestedMatcherSet(d)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		m.MatcherSetsRaw = append(m.MatcherSetsRaw, matcherSet)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // UnmarshalJSON satisfies json.Unmarshaler. It puts the JSON
 | |
| // bytes directly into m's MatcherSetsRaw field.
 | |
| func (m *MatchNot) UnmarshalJSON(data []byte) error {
 | |
| 	return json.Unmarshal(data, &m.MatcherSetsRaw)
 | |
| }
 | |
| 
 | |
| // MarshalJSON satisfies json.Marshaler by marshaling
 | |
| // m's raw matcher sets.
 | |
| func (m MatchNot) MarshalJSON() ([]byte, error) {
 | |
| 	return json.Marshal(m.MatcherSetsRaw)
 | |
| }
 | |
| 
 | |
| // Provision loads the matcher modules to be negated.
 | |
| func (m *MatchNot) Provision(ctx caddy.Context) error {
 | |
| 	matcherSets, err := ctx.LoadModule(m, "MatcherSetsRaw")
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("loading matcher sets: %v", err)
 | |
| 	}
 | |
| 	for _, modMap := range matcherSets.([]map[string]any) {
 | |
| 		var ms MatcherSet
 | |
| 		for _, modIface := range modMap {
 | |
| 			ms = append(ms, modIface.(RequestMatcher))
 | |
| 		}
 | |
| 		m.MatcherSets = append(m.MatcherSets, ms)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Match returns true if r matches m. Since this matcher negates
 | |
| // the embedded matchers, false is returned if any of its matcher
 | |
| // sets return true.
 | |
| func (m MatchNot) Match(r *http.Request) bool {
 | |
| 	for _, ms := range m.MatcherSets {
 | |
| 		if ms.Match(r) {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // CaddyModule returns the Caddy module information.
 | |
| func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
 | |
| 	return caddy.ModuleInfo{
 | |
| 		ID:  "http.matchers.remote_ip",
 | |
| 		New: func() caddy.Module { return new(MatchRemoteIP) },
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
 | |
| func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | |
| 	for d.Next() {
 | |
| 		for d.NextArg() {
 | |
| 			if d.Val() == "forwarded" {
 | |
| 				if len(m.Ranges) > 0 {
 | |
| 					return d.Err("if used, 'forwarded' must be first argument")
 | |
| 				}
 | |
| 				m.Forwarded = true
 | |
| 				continue
 | |
| 			}
 | |
| 			if d.Val() == "private_ranges" {
 | |
| 				m.Ranges = append(m.Ranges, PrivateRangesCIDR()...)
 | |
| 				continue
 | |
| 			}
 | |
| 			m.Ranges = append(m.Ranges, d.Val())
 | |
| 		}
 | |
| 		if d.NextBlock(0) {
 | |
| 			return d.Err("malformed remote_ip matcher: blocks are not supported")
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // CELLibrary produces options that expose this matcher for use in CEL
 | |
| // expression matchers.
 | |
| //
 | |
| // Example:
 | |
| //
 | |
| //	expression remote_ip('forwarded', '192.168.0.0/16', '172.16.0.0/12', '10.0.0.0/8')
 | |
| func (MatchRemoteIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
 | |
| 	return CELMatcherImpl(
 | |
| 		// name of the macro, this is the function name that users see when writing expressions.
 | |
| 		"remote_ip",
 | |
| 		// name of the function that the macro will be rewritten to call.
 | |
| 		"remote_ip_match_request_list",
 | |
| 		// internal data type of the MatchPath value.
 | |
| 		[]*cel.Type{cel.ListType(cel.StringType)},
 | |
| 		// function to convert a constant list of strings to a MatchPath instance.
 | |
| 		func(data ref.Val) (RequestMatcher, error) {
 | |
| 			refStringList := reflect.TypeOf([]string{})
 | |
| 			strList, err := data.ConvertToNative(refStringList)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 
 | |
| 			m := MatchRemoteIP{}
 | |
| 
 | |
| 			for _, input := range strList.([]string) {
 | |
| 				if input == "forwarded" {
 | |
| 					if len(m.Ranges) > 0 {
 | |
| 						return nil, errors.New("if used, 'forwarded' must be first argument")
 | |
| 					}
 | |
| 					m.Forwarded = true
 | |
| 					continue
 | |
| 				}
 | |
| 				m.Ranges = append(m.Ranges, input)
 | |
| 			}
 | |
| 
 | |
| 			err = m.Provision(ctx)
 | |
| 			return m, err
 | |
| 		},
 | |
| 	)
 | |
| }
 | |
| 
 | |
| // Provision parses m's IP ranges, either from IP or CIDR expressions.
 | |
| func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
 | |
| 	m.logger = ctx.Logger()
 | |
| 	for _, str := range m.Ranges {
 | |
| 		// Exclude the zone_id from the IP
 | |
| 		if strings.Contains(str, "%") {
 | |
| 			split := strings.Split(str, "%")
 | |
| 			str = split[0]
 | |
| 			// write zone identifiers in m.zones for matching later
 | |
| 			m.zones = append(m.zones, split[1])
 | |
| 		} else {
 | |
| 			m.zones = append(m.zones, "")
 | |
| 		}
 | |
| 		if strings.Contains(str, "/") {
 | |
| 			ipNet, err := netip.ParsePrefix(str)
 | |
| 			if err != nil {
 | |
| 				return fmt.Errorf("parsing CIDR expression '%s': %v", str, err)
 | |
| 			}
 | |
| 			m.cidrs = append(m.cidrs, &ipNet)
 | |
| 		} else {
 | |
| 			ipAddr, err := netip.ParseAddr(str)
 | |
| 			if err != nil {
 | |
| 				return fmt.Errorf("invalid IP address: '%s': %v", str, err)
 | |
| 			}
 | |
| 			ipNew := netip.PrefixFrom(ipAddr, ipAddr.BitLen())
 | |
| 			m.cidrs = append(m.cidrs, &ipNew)
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (m MatchRemoteIP) getClientIP(r *http.Request) (netip.Addr, string, error) {
 | |
| 	remote := r.RemoteAddr
 | |
| 	zoneID := ""
 | |
| 	if m.Forwarded {
 | |
| 		if fwdFor := r.Header.Get("X-Forwarded-For"); fwdFor != "" {
 | |
| 			remote = strings.TrimSpace(strings.Split(fwdFor, ",")[0])
 | |
| 		}
 | |
| 	}
 | |
| 	ipStr, _, err := net.SplitHostPort(remote)
 | |
| 	if err != nil {
 | |
| 		ipStr = remote // OK; probably didn't have a port
 | |
| 	}
 | |
| 	// Some IPv6-Adresses can contain zone identifiers at the end,
 | |
| 	// which are separated with "%"
 | |
| 	if strings.Contains(ipStr, "%") {
 | |
| 		split := strings.Split(ipStr, "%")
 | |
| 		ipStr = split[0]
 | |
| 		zoneID = split[1]
 | |
| 	}
 | |
| 	ipAddr, err := netip.ParseAddr(ipStr)
 | |
| 	if err != nil {
 | |
| 		return netip.IPv4Unspecified(), "", err
 | |
| 	}
 | |
| 	return ipAddr, zoneID, nil
 | |
| }
 | |
| 
 | |
| // Match returns true if r matches m.
 | |
| func (m MatchRemoteIP) Match(r *http.Request) bool {
 | |
| 	clientIP, zoneID, err := m.getClientIP(r)
 | |
| 	if err != nil {
 | |
| 		m.logger.Error("getting client IP", zap.Error(err))
 | |
| 		return false
 | |
| 	}
 | |
| 	zoneFilter := true
 | |
| 	for i, ipRange := range m.cidrs {
 | |
| 		if ipRange.Contains(clientIP) {
 | |
| 			// Check if there are zone filters assigned and if they match.
 | |
| 			if m.zones[i] == "" || zoneID == m.zones[i] {
 | |
| 				return true
 | |
| 			}
 | |
| 			zoneFilter = false
 | |
| 		}
 | |
| 	}
 | |
| 	if !zoneFilter {
 | |
| 		m.logger.Debug("zone ID from remote did not match", zap.String("zone", zoneID))
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // MatchRegexp is an embedable type for matching
 | |
| // using regular expressions. It adds placeholders
 | |
| // to the request's replacer.
 | |
| type MatchRegexp struct {
 | |
| 	// A unique name for this regular expression. Optional,
 | |
| 	// but useful to prevent overwriting captures from other
 | |
| 	// regexp matchers.
 | |
| 	Name string `json:"name,omitempty"`
 | |
| 
 | |
| 	// The regular expression to evaluate, in RE2 syntax,
 | |
| 	// which is the same general syntax used by Go, Perl,
 | |
| 	// and Python. For details, see
 | |
| 	// [Go's regexp package](https://golang.org/pkg/regexp/).
 | |
| 	// Captures are accessible via placeholders. Unnamed
 | |
| 	// capture groups are exposed as their numeric, 1-based
 | |
| 	// index, while named capture groups are available by
 | |
| 	// the capture group name.
 | |
| 	Pattern string `json:"pattern"`
 | |
| 
 | |
| 	compiled *regexp.Regexp
 | |
| 	phPrefix string
 | |
| }
 | |
| 
 | |
| // Provision compiles the regular expression.
 | |
| func (mre *MatchRegexp) Provision(caddy.Context) error {
 | |
| 	re, err := regexp.Compile(mre.Pattern)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("compiling matcher regexp %s: %v", mre.Pattern, err)
 | |
| 	}
 | |
| 	mre.compiled = re
 | |
| 	mre.phPrefix = regexpPlaceholderPrefix
 | |
| 	if mre.Name != "" {
 | |
| 		mre.phPrefix += "." + mre.Name
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Validate ensures mre is set up correctly.
 | |
| func (mre *MatchRegexp) Validate() error {
 | |
| 	if mre.Name != "" && !wordRE.MatchString(mre.Name) {
 | |
| 		return fmt.Errorf("invalid regexp name (must contain only word characters): %s", mre.Name)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Match returns true if input matches the compiled regular
 | |
| // expression in mre. It sets values on the replacer repl
 | |
| // associated with capture groups, using the given scope
 | |
| // (namespace).
 | |
| func (mre *MatchRegexp) Match(input string, repl *caddy.Replacer) bool {
 | |
| 	matches := mre.compiled.FindStringSubmatch(input)
 | |
| 	if matches == nil {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	// save all capture groups, first by index
 | |
| 	for i, match := range matches {
 | |
| 		key := mre.phPrefix + "." + strconv.Itoa(i)
 | |
| 		repl.Set(key, match)
 | |
| 	}
 | |
| 
 | |
| 	// then by name
 | |
| 	for i, name := range mre.compiled.SubexpNames() {
 | |
| 		if i != 0 && name != "" {
 | |
| 			key := mre.phPrefix + "." + name
 | |
| 			repl.Set(key, matches[i])
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
 | |
| func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | |
| 	for d.Next() {
 | |
| 		// If this is the second iteration of the loop
 | |
| 		// then there's more than one path_regexp matcher
 | |
| 		// and we would end up overwriting the old one
 | |
| 		if mre.Pattern != "" {
 | |
| 			return d.Err("regular expression can only be used once per named matcher")
 | |
| 		}
 | |
| 
 | |
| 		args := d.RemainingArgs()
 | |
| 		switch len(args) {
 | |
| 		case 1:
 | |
| 			mre.Pattern = args[0]
 | |
| 		case 2:
 | |
| 			mre.Name = args[0]
 | |
| 			mre.Pattern = args[1]
 | |
| 		default:
 | |
| 			return d.ArgErr()
 | |
| 		}
 | |
| 		if d.NextBlock(0) {
 | |
| 			return d.Err("malformed path_regexp matcher: blocks are not supported")
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // ParseCaddyfileNestedMatcher parses the Caddyfile tokens for a nested
 | |
| // matcher set, and returns its raw module map value.
 | |
| func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
 | |
| 	matcherMap := make(map[string]RequestMatcher)
 | |
| 
 | |
| 	// in case there are multiple instances of the same matcher, concatenate
 | |
| 	// their tokens (we expect that UnmarshalCaddyfile should be able to
 | |
| 	// handle more than one segment); otherwise, we'd overwrite other
 | |
| 	// instances of the matcher in this set
 | |
| 	tokensByMatcherName := make(map[string][]caddyfile.Token)
 | |
| 	for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
 | |
| 		matcherName := d.Val()
 | |
| 		tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
 | |
| 	}
 | |
| 
 | |
| 	for matcherName, tokens := range tokensByMatcherName {
 | |
| 		mod, err := caddy.GetModule("http.matchers." + matcherName)
 | |
| 		if err != nil {
 | |
| 			return nil, d.Errf("getting matcher module '%s': %v", matcherName, err)
 | |
| 		}
 | |
| 		unm, ok := mod.New().(caddyfile.Unmarshaler)
 | |
| 		if !ok {
 | |
| 			return nil, d.Errf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName)
 | |
| 		}
 | |
| 		err = unm.UnmarshalCaddyfile(caddyfile.NewDispenser(tokens))
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		rm, ok := unm.(RequestMatcher)
 | |
| 		if !ok {
 | |
| 			return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
 | |
| 		}
 | |
| 		matcherMap[matcherName] = rm
 | |
| 	}
 | |
| 
 | |
| 	// we should now have a functional matcher, but we also
 | |
| 	// need to be able to marshal as JSON, otherwise config
 | |
| 	// adaptation will be missing the matchers!
 | |
| 	matcherSet := make(caddy.ModuleMap)
 | |
| 	for name, matcher := range matcherMap {
 | |
| 		jsonBytes, err := json.Marshal(matcher)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("marshaling %T matcher: %v", matcher, err)
 | |
| 		}
 | |
| 		matcherSet[name] = jsonBytes
 | |
| 	}
 | |
| 
 | |
| 	return matcherSet, nil
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	wordRE = regexp.MustCompile(`\w+`)
 | |
| )
 | |
| 
 | |
| const regexpPlaceholderPrefix = "http.regexp"
 | |
| 
 | |
| // MatcherErrorVarKey is the key used for the variable that
 | |
| // holds an optional error emitted from a request matcher,
 | |
| // to short-circuit the handler chain, since matchers cannot
 | |
| // return errors via the RequestMatcher interface.
 | |
| const MatcherErrorVarKey = "matchers.error"
 | |
| 
 | |
| // Interface guards
 | |
| var (
 | |
| 	_ RequestMatcher    = (*MatchHost)(nil)
 | |
| 	_ caddy.Provisioner = (*MatchHost)(nil)
 | |
| 	_ RequestMatcher    = (*MatchPath)(nil)
 | |
| 	_ RequestMatcher    = (*MatchPathRE)(nil)
 | |
| 	_ caddy.Provisioner = (*MatchPathRE)(nil)
 | |
| 	_ RequestMatcher    = (*MatchMethod)(nil)
 | |
| 	_ RequestMatcher    = (*MatchQuery)(nil)
 | |
| 	_ RequestMatcher    = (*MatchHeader)(nil)
 | |
| 	_ RequestMatcher    = (*MatchHeaderRE)(nil)
 | |
| 	_ caddy.Provisioner = (*MatchHeaderRE)(nil)
 | |
| 	_ RequestMatcher    = (*MatchProtocol)(nil)
 | |
| 	_ RequestMatcher    = (*MatchRemoteIP)(nil)
 | |
| 	_ caddy.Provisioner = (*MatchRemoteIP)(nil)
 | |
| 	_ RequestMatcher    = (*MatchNot)(nil)
 | |
| 	_ caddy.Provisioner = (*MatchNot)(nil)
 | |
| 	_ caddy.Provisioner = (*MatchRegexp)(nil)
 | |
| 
 | |
| 	_ caddyfile.Unmarshaler = (*MatchHost)(nil)
 | |
| 	_ caddyfile.Unmarshaler = (*MatchPath)(nil)
 | |
| 	_ caddyfile.Unmarshaler = (*MatchPathRE)(nil)
 | |
| 	_ caddyfile.Unmarshaler = (*MatchMethod)(nil)
 | |
| 	_ caddyfile.Unmarshaler = (*MatchQuery)(nil)
 | |
| 	_ caddyfile.Unmarshaler = (*MatchHeader)(nil)
 | |
| 	_ caddyfile.Unmarshaler = (*MatchHeaderRE)(nil)
 | |
| 	_ caddyfile.Unmarshaler = (*MatchProtocol)(nil)
 | |
| 	_ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil)
 | |
| 	_ caddyfile.Unmarshaler = (*VarsMatcher)(nil)
 | |
| 	_ caddyfile.Unmarshaler = (*MatchVarsRE)(nil)
 | |
| 
 | |
| 	_ CELLibraryProducer = (*MatchHost)(nil)
 | |
| 	_ CELLibraryProducer = (*MatchPath)(nil)
 | |
| 	_ CELLibraryProducer = (*MatchPathRE)(nil)
 | |
| 	_ CELLibraryProducer = (*MatchMethod)(nil)
 | |
| 	_ CELLibraryProducer = (*MatchQuery)(nil)
 | |
| 	_ CELLibraryProducer = (*MatchHeader)(nil)
 | |
| 	_ CELLibraryProducer = (*MatchHeaderRE)(nil)
 | |
| 	_ CELLibraryProducer = (*MatchProtocol)(nil)
 | |
| 	_ CELLibraryProducer = (*MatchRemoteIP)(nil)
 | |
| 	// _ CELLibraryProducer = (*VarsMatcher)(nil)
 | |
| 	// _ CELLibraryProducer = (*MatchVarsRE)(nil)
 | |
| 
 | |
| 	_ json.Marshaler   = (*MatchNot)(nil)
 | |
| 	_ json.Unmarshaler = (*MatchNot)(nil)
 | |
| )
 |