mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-16 21:56:40 -05:00
caddyhttp: Add MatchWithError
to replace SetVar hack (#6596)
* caddyhttp: Add `MatchWithError` to replace SetVar hack * Error in IP matchers on TLS handshake not complete * Use MatchWithError everywhere possible * Move implementations to MatchWithError versions * Looser interface checking to allow fallback * CEL factories can return RequestMatcherWithError * Clarifying comment since it's subtle that an err is returned * Return 425 Too Early status in IP matchers * Keep AnyMatch signature the same for now * Apparently Deprecated can't be all-uppercase to get IDE linting * Linter
This commit is contained in:
parent
a3481f871b
commit
09b2cbcf4d
16 changed files with 537 additions and 203 deletions
|
@ -1466,9 +1466,9 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod
|
||||||
|
|
||||||
// iterate each pairing of host and path matchers and
|
// iterate each pairing of host and path matchers and
|
||||||
// put them into a map for JSON encoding
|
// put them into a map for JSON encoding
|
||||||
var matcherSets []map[string]caddyhttp.RequestMatcher
|
var matcherSets []map[string]caddyhttp.RequestMatcherWithError
|
||||||
for _, mp := range matcherPairs {
|
for _, mp := range matcherPairs {
|
||||||
matcherSet := make(map[string]caddyhttp.RequestMatcher)
|
matcherSet := make(map[string]caddyhttp.RequestMatcherWithError)
|
||||||
if len(mp.hostm) > 0 {
|
if len(mp.hostm) > 0 {
|
||||||
matcherSet["host"] = mp.hostm
|
matcherSet["host"] = mp.hostm
|
||||||
}
|
}
|
||||||
|
@ -1527,12 +1527,17 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rm, ok := unm.(caddyhttp.RequestMatcher)
|
|
||||||
if !ok {
|
if rm, ok := unm.(caddyhttp.RequestMatcherWithError); ok {
|
||||||
return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
|
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
|
// nolint:staticcheck
|
||||||
return nil
|
if rm, ok := unm.(caddyhttp.RequestMatcher); ok {
|
||||||
|
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the next token is quoted, we can assume it's not a matcher name
|
// if the next token is quoted, we can assume it's not a matcher name
|
||||||
|
@ -1576,7 +1581,7 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (caddy.ModuleMap, error) {
|
func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcherWithError) (caddy.ModuleMap, error) {
|
||||||
msEncoded := make(caddy.ModuleMap)
|
msEncoded := make(caddy.ModuleMap)
|
||||||
for matcherName, val := range matchers {
|
for matcherName, val := range matchers {
|
||||||
jsonBytes, err := json.Marshal(val)
|
jsonBytes, err := json.Marshal(val)
|
||||||
|
|
|
@ -36,10 +36,26 @@ func init() {
|
||||||
// RequestMatcher is a type that can match to a request.
|
// RequestMatcher is a type that can match to a request.
|
||||||
// A route matcher MUST NOT modify the request, with the
|
// A route matcher MUST NOT modify the request, with the
|
||||||
// only exception being its context.
|
// only exception being its context.
|
||||||
|
//
|
||||||
|
// Deprecated: Matchers should now implement RequestMatcherWithError.
|
||||||
|
// You may remove any interface guards for RequestMatcher
|
||||||
|
// but keep your Match() methods for backwards compatibility.
|
||||||
type RequestMatcher interface {
|
type RequestMatcher interface {
|
||||||
Match(*http.Request) bool
|
Match(*http.Request) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestMatcherWithError is like RequestMatcher but can return an error.
|
||||||
|
// An error during matching will abort the request middleware chain and
|
||||||
|
// invoke the error middleware chain.
|
||||||
|
//
|
||||||
|
// This will eventually replace RequestMatcher. Matcher modules
|
||||||
|
// should implement both interfaces, and once all modules have
|
||||||
|
// been updated to use RequestMatcherWithError, the RequestMatcher
|
||||||
|
// interface may eventually be dropped.
|
||||||
|
type RequestMatcherWithError interface {
|
||||||
|
MatchWithError(*http.Request) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
// Handler is like http.Handler except ServeHTTP may return an error.
|
// Handler is like http.Handler except ServeHTTP may return an error.
|
||||||
//
|
//
|
||||||
// If any handler encounters an error, it should be returned for proper
|
// If any handler encounters an error, it should be returned for proper
|
||||||
|
|
|
@ -202,17 +202,25 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error {
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchExpression) Match(r *http.Request) bool {
|
func (m MatchExpression) Match(r *http.Request) bool {
|
||||||
|
match, err := m.MatchWithError(r)
|
||||||
|
if err != nil {
|
||||||
|
SetVar(r.Context(), MatcherErrorVarKey, err)
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (m MatchExpression) MatchWithError(r *http.Request) (bool, error) {
|
||||||
celReq := celHTTPRequest{r}
|
celReq := celHTTPRequest{r}
|
||||||
out, _, err := m.prg.Eval(celReq)
|
out, _, err := m.prg.Eval(celReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.log.Error("evaluating expression", zap.Error(err))
|
m.log.Error("evaluating expression", zap.Error(err))
|
||||||
SetVar(r.Context(), MatcherErrorVarKey, err)
|
return false, err
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
if outBool, ok := out.Value().(bool); ok {
|
if outBool, ok := out.Value().(bool); ok {
|
||||||
return outBool
|
return outBool, nil
|
||||||
}
|
}
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||||
|
@ -380,7 +388,7 @@ type CELLibraryProducer interface {
|
||||||
// limited set of function signatures. For strong type validation you may need
|
// limited set of function signatures. For strong type validation you may need
|
||||||
// to provide a custom macro which does a more detailed analysis of the CEL
|
// to provide a custom macro which does a more detailed analysis of the CEL
|
||||||
// literal provided to the macro as an argument.
|
// literal provided to the macro as an argument.
|
||||||
func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fac CELMatcherFactory) (cel.Library, error) {
|
func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fac any) (cel.Library, error) {
|
||||||
requestType := cel.ObjectType("http.Request")
|
requestType := cel.ObjectType("http.Request")
|
||||||
var macro parser.Macro
|
var macro parser.Macro
|
||||||
switch len(matcherDataTypes) {
|
switch len(matcherDataTypes) {
|
||||||
|
@ -424,7 +432,11 @@ func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fa
|
||||||
}
|
}
|
||||||
|
|
||||||
// CELMatcherFactory converts a constant CEL value into a RequestMatcher.
|
// CELMatcherFactory converts a constant CEL value into a RequestMatcher.
|
||||||
type CELMatcherFactory func(data ref.Val) (RequestMatcher, error)
|
// Deprecated: Use CELMatcherWithErrorFactory instead.
|
||||||
|
type CELMatcherFactory = func(data ref.Val) (RequestMatcher, error)
|
||||||
|
|
||||||
|
// CELMatcherWithErrorFactory converts a constant CEL value into a RequestMatcherWithError.
|
||||||
|
type CELMatcherWithErrorFactory = func(data ref.Val) (RequestMatcherWithError, error)
|
||||||
|
|
||||||
// matcherCELLibrary is a simplistic configurable cel.Library implementation.
|
// matcherCELLibrary is a simplistic configurable cel.Library implementation.
|
||||||
type matcherCELLibrary struct {
|
type matcherCELLibrary struct {
|
||||||
|
@ -452,7 +464,7 @@ func (lib *matcherCELLibrary) ProgramOptions() []cel.ProgramOption {
|
||||||
// that takes a single argument, and optimizes the implementation to precompile
|
// that takes a single argument, and optimizes the implementation to precompile
|
||||||
// the matcher and return a function that references the precompiled and
|
// the matcher and return a function that references the precompiled and
|
||||||
// provisioned matcher.
|
// provisioned matcher.
|
||||||
func CELMatcherDecorator(funcName string, fac CELMatcherFactory) interpreter.InterpretableDecorator {
|
func CELMatcherDecorator(funcName string, fac any) interpreter.InterpretableDecorator {
|
||||||
return func(i interpreter.Interpretable) (interpreter.Interpretable, error) {
|
return func(i interpreter.Interpretable) (interpreter.Interpretable, error) {
|
||||||
call, ok := i.(interpreter.InterpretableCall)
|
call, ok := i.(interpreter.InterpretableCall)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -481,35 +493,92 @@ func CELMatcherDecorator(funcName string, fac CELMatcherFactory) interpreter.Int
|
||||||
// and matcher provisioning should be handled at dynamically.
|
// and matcher provisioning should be handled at dynamically.
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
matcher, err := fac(matcherData.Value())
|
|
||||||
if err != nil {
|
if factory, ok := fac.(CELMatcherWithErrorFactory); ok {
|
||||||
return nil, err
|
matcher, err := factory(matcherData.Value())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return interpreter.NewCall(
|
||||||
|
i.ID(), funcName, funcName+"_opt",
|
||||||
|
[]interpreter.Interpretable{reqAttr},
|
||||||
|
func(args ...ref.Val) ref.Val {
|
||||||
|
// The request value, guaranteed to be of type celHTTPRequest
|
||||||
|
celReq := args[0]
|
||||||
|
// If needed this call could be changed to convert the value
|
||||||
|
// to a *http.Request using CEL's ConvertToNative method.
|
||||||
|
httpReq := celReq.Value().(celHTTPRequest)
|
||||||
|
match, err := matcher.MatchWithError(httpReq.Request)
|
||||||
|
if err != nil {
|
||||||
|
return types.WrapErr(err)
|
||||||
|
}
|
||||||
|
return types.Bool(match)
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
return interpreter.NewCall(
|
|
||||||
i.ID(), funcName, funcName+"_opt",
|
if factory, ok := fac.(CELMatcherFactory); ok {
|
||||||
[]interpreter.Interpretable{reqAttr},
|
matcher, err := factory(matcherData.Value())
|
||||||
func(args ...ref.Val) ref.Val {
|
if err != nil {
|
||||||
// The request value, guaranteed to be of type celHTTPRequest
|
return nil, err
|
||||||
celReq := args[0]
|
}
|
||||||
// If needed this call could be changed to convert the value
|
return interpreter.NewCall(
|
||||||
// to a *http.Request using CEL's ConvertToNative method.
|
i.ID(), funcName, funcName+"_opt",
|
||||||
httpReq := celReq.Value().(celHTTPRequest)
|
[]interpreter.Interpretable{reqAttr},
|
||||||
return types.Bool(matcher.Match(httpReq.Request))
|
func(args ...ref.Val) ref.Val {
|
||||||
},
|
// The request value, guaranteed to be of type celHTTPRequest
|
||||||
), nil
|
celReq := args[0]
|
||||||
|
// If needed this call could be changed to convert the value
|
||||||
|
// to a *http.Request using CEL's ConvertToNative method.
|
||||||
|
httpReq := celReq.Value().(celHTTPRequest)
|
||||||
|
if m, ok := matcher.(RequestMatcherWithError); ok {
|
||||||
|
match, err := m.MatchWithError(httpReq.Request)
|
||||||
|
if err != nil {
|
||||||
|
return types.WrapErr(err)
|
||||||
|
}
|
||||||
|
return types.Bool(match)
|
||||||
|
}
|
||||||
|
return types.Bool(matcher.Match(httpReq.Request))
|
||||||
|
},
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("invalid matcher factory, must be CELMatcherFactory or CELMatcherWithErrorFactory: %T", fac)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CELMatcherRuntimeFunction creates a function binding for when the input to the matcher
|
// CELMatcherRuntimeFunction creates a function binding for when the input to the matcher
|
||||||
// is dynamically resolved rather than a set of static constant values.
|
// is dynamically resolved rather than a set of static constant values.
|
||||||
func CELMatcherRuntimeFunction(funcName string, fac CELMatcherFactory) functions.BinaryOp {
|
func CELMatcherRuntimeFunction(funcName string, fac any) functions.BinaryOp {
|
||||||
return func(celReq, matcherData ref.Val) ref.Val {
|
return func(celReq, matcherData ref.Val) ref.Val {
|
||||||
matcher, err := fac(matcherData)
|
if factory, ok := fac.(CELMatcherWithErrorFactory); ok {
|
||||||
if err != nil {
|
matcher, err := factory(matcherData)
|
||||||
return types.WrapErr(err)
|
if err != nil {
|
||||||
|
return types.WrapErr(err)
|
||||||
|
}
|
||||||
|
httpReq := celReq.Value().(celHTTPRequest)
|
||||||
|
match, err := matcher.MatchWithError(httpReq.Request)
|
||||||
|
if err != nil {
|
||||||
|
return types.WrapErr(err)
|
||||||
|
}
|
||||||
|
return types.Bool(match)
|
||||||
}
|
}
|
||||||
httpReq := celReq.Value().(celHTTPRequest)
|
if factory, ok := fac.(CELMatcherFactory); ok {
|
||||||
return types.Bool(matcher.Match(httpReq.Request))
|
matcher, err := factory(matcherData)
|
||||||
|
if err != nil {
|
||||||
|
return types.WrapErr(err)
|
||||||
|
}
|
||||||
|
httpReq := celReq.Value().(celHTTPRequest)
|
||||||
|
if m, ok := matcher.(RequestMatcherWithError); ok {
|
||||||
|
match, err := m.MatchWithError(httpReq.Request)
|
||||||
|
if err != nil {
|
||||||
|
return types.WrapErr(err)
|
||||||
|
}
|
||||||
|
return types.Bool(match)
|
||||||
|
}
|
||||||
|
return types.Bool(matcher.Match(httpReq.Request))
|
||||||
|
}
|
||||||
|
return types.NewErr("CELMatcherRuntimeFunction invalid matcher factory: %T", fac)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -733,9 +802,9 @@ const MatcherNameCtxKey = "matcher_name"
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ caddy.Provisioner = (*MatchExpression)(nil)
|
_ caddy.Provisioner = (*MatchExpression)(nil)
|
||||||
_ RequestMatcher = (*MatchExpression)(nil)
|
_ RequestMatcherWithError = (*MatchExpression)(nil)
|
||||||
_ caddyfile.Unmarshaler = (*MatchExpression)(nil)
|
_ caddyfile.Unmarshaler = (*MatchExpression)(nil)
|
||||||
_ json.Marshaler = (*MatchExpression)(nil)
|
_ json.Marshaler = (*MatchExpression)(nil)
|
||||||
_ json.Unmarshaler = (*MatchExpression)(nil)
|
_ json.Unmarshaler = (*MatchExpression)(nil)
|
||||||
)
|
)
|
||||||
|
|
|
@ -489,7 +489,11 @@ func TestMatchExpressionMatch(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tc.expression.Match(req) != tc.wantResult {
|
matches, err := tc.expression.MatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MatchExpression.Match() error = %v", err)
|
||||||
|
}
|
||||||
|
if matches != tc.wantResult {
|
||||||
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr)
|
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -532,7 +536,7 @@ func BenchmarkMatchExpressionMatch(b *testing.B) {
|
||||||
}
|
}
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
tc.expression.Match(req)
|
tc.expression.MatchWithError(req)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,7 +173,7 @@ func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
func (MatchFile) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
func (MatchFile) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||||
requestType := cel.ObjectType("http.Request")
|
requestType := cel.ObjectType("http.Request")
|
||||||
|
|
||||||
matcherFactory := func(data ref.Val) (caddyhttp.RequestMatcher, error) {
|
matcherFactory := func(data ref.Val) (caddyhttp.RequestMatcherWithError, error) {
|
||||||
values, err := caddyhttp.CELValueToMapStrList(data)
|
values, err := caddyhttp.CELValueToMapStrList(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -313,12 +313,22 @@ func (m MatchFile) Validate() error {
|
||||||
// - http.matchers.file.type: file or directory
|
// - http.matchers.file.type: file or directory
|
||||||
// - http.matchers.file.remainder: Portion remaining after splitting file path (if configured)
|
// - http.matchers.file.remainder: Portion remaining after splitting file path (if configured)
|
||||||
func (m MatchFile) Match(r *http.Request) bool {
|
func (m MatchFile) Match(r *http.Request) bool {
|
||||||
|
match, err := m.selectFile(r)
|
||||||
|
if err != nil {
|
||||||
|
// nolint:staticcheck
|
||||||
|
caddyhttp.SetVar(r.Context(), caddyhttp.MatcherErrorVarKey, err)
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (m MatchFile) MatchWithError(r *http.Request) (bool, error) {
|
||||||
return m.selectFile(r)
|
return m.selectFile(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectFile chooses a file according to m.TryPolicy by appending
|
// selectFile chooses a file according to m.TryPolicy by appending
|
||||||
// the paths in m.TryFiles to m.Root, with placeholder replacements.
|
// the paths in m.TryFiles to m.Root, with placeholder replacements.
|
||||||
func (m MatchFile) selectFile(r *http.Request) (matched bool) {
|
func (m MatchFile) selectFile(r *http.Request) (bool, error) {
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
|
|
||||||
root := filepath.Clean(repl.ReplaceAll(m.Root, "."))
|
root := filepath.Clean(repl.ReplaceAll(m.Root, "."))
|
||||||
|
@ -330,7 +340,7 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
|
||||||
if c := m.logger.Check(zapcore.ErrorLevel, "use of unregistered filesystem"); c != nil {
|
if c := m.logger.Check(zapcore.ErrorLevel, "use of unregistered filesystem"); c != nil {
|
||||||
c.Write(zap.String("fs", fsName))
|
c.Write(zap.String("fs", fsName))
|
||||||
}
|
}
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
type matchCandidate struct {
|
type matchCandidate struct {
|
||||||
fullpath, relative, splitRemainder string
|
fullpath, relative, splitRemainder string
|
||||||
|
@ -421,15 +431,18 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
|
||||||
switch m.TryPolicy {
|
switch m.TryPolicy {
|
||||||
case "", tryPolicyFirstExist:
|
case "", tryPolicyFirstExist:
|
||||||
for _, pattern := range m.TryFiles {
|
for _, pattern := range m.TryFiles {
|
||||||
|
// If the pattern is a status code, emit an error,
|
||||||
|
// which short-circuits the middleware pipeline and
|
||||||
|
// writes an HTTP error response.
|
||||||
if err := parseErrorCode(pattern); err != nil {
|
if err := parseErrorCode(pattern); err != nil {
|
||||||
caddyhttp.SetVar(r.Context(), caddyhttp.MatcherErrorVarKey, err)
|
return false, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
candidates := makeCandidates(pattern)
|
candidates := makeCandidates(pattern)
|
||||||
for _, c := range candidates {
|
for _, c := range candidates {
|
||||||
if info, exists := m.strictFileExists(fileSystem, c.fullpath); exists {
|
if info, exists := m.strictFileExists(fileSystem, c.fullpath); exists {
|
||||||
setPlaceholders(c, info)
|
setPlaceholders(c, info)
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -450,10 +463,10 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if largestInfo == nil {
|
if largestInfo == nil {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
setPlaceholders(largest, largestInfo)
|
setPlaceholders(largest, largestInfo)
|
||||||
return true
|
return true, nil
|
||||||
|
|
||||||
case tryPolicySmallestSize:
|
case tryPolicySmallestSize:
|
||||||
var smallestSize int64
|
var smallestSize int64
|
||||||
|
@ -471,10 +484,10 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if smallestInfo == nil {
|
if smallestInfo == nil {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
setPlaceholders(smallest, smallestInfo)
|
setPlaceholders(smallest, smallestInfo)
|
||||||
return true
|
return true, nil
|
||||||
|
|
||||||
case tryPolicyMostRecentlyMod:
|
case tryPolicyMostRecentlyMod:
|
||||||
var recent matchCandidate
|
var recent matchCandidate
|
||||||
|
@ -491,13 +504,13 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if recentInfo == nil {
|
if recentInfo == nil {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
setPlaceholders(recent, recentInfo)
|
setPlaceholders(recent, recentInfo)
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseErrorCode checks if the input is a status
|
// parseErrorCode checks if the input is a status
|
||||||
|
@ -703,7 +716,7 @@ const (
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ caddy.Validator = (*MatchFile)(nil)
|
_ caddy.Validator = (*MatchFile)(nil)
|
||||||
_ caddyhttp.RequestMatcher = (*MatchFile)(nil)
|
_ caddyhttp.RequestMatcherWithError = (*MatchFile)(nil)
|
||||||
_ caddyhttp.CELLibraryProducer = (*MatchFile)(nil)
|
_ caddyhttp.CELLibraryProducer = (*MatchFile)(nil)
|
||||||
)
|
)
|
||||||
|
|
|
@ -130,7 +130,10 @@ func TestFileMatcher(t *testing.T) {
|
||||||
req := &http.Request{URL: u}
|
req := &http.Request{URL: u}
|
||||||
repl := caddyhttp.NewTestReplacer(req)
|
repl := caddyhttp.NewTestReplacer(req)
|
||||||
|
|
||||||
result := m.Match(req)
|
result, err := m.MatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test %d: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
if result != tc.matched {
|
if result != tc.matched {
|
||||||
t.Errorf("Test %d: expected match=%t, got %t", i, tc.matched, result)
|
t.Errorf("Test %d: expected match=%t, got %t", i, tc.matched, result)
|
||||||
}
|
}
|
||||||
|
@ -240,7 +243,10 @@ func TestPHPFileMatcher(t *testing.T) {
|
||||||
req := &http.Request{URL: u}
|
req := &http.Request{URL: u}
|
||||||
repl := caddyhttp.NewTestReplacer(req)
|
repl := caddyhttp.NewTestReplacer(req)
|
||||||
|
|
||||||
result := m.Match(req)
|
result, err := m.MatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test %d: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
if result != tc.matched {
|
if result != tc.matched {
|
||||||
t.Errorf("Test %d: expected match=%t, got %t", i, tc.matched, result)
|
t.Errorf("Test %d: expected match=%t, got %t", i, tc.matched, result)
|
||||||
}
|
}
|
||||||
|
@ -389,7 +395,12 @@ func TestMatchExpressionMatch(t *testing.T) {
|
||||||
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
if tc.expression.Match(req) != tc.wantResult {
|
matches, err := tc.expression.MatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MatchExpression.Match() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if matches != tc.wantResult {
|
||||||
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr)
|
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -108,7 +108,7 @@ func (MatchRemoteIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||||
// internal data type of the MatchPath value.
|
// internal data type of the MatchPath value.
|
||||||
[]*cel.Type{cel.ListType(cel.StringType)},
|
[]*cel.Type{cel.ListType(cel.StringType)},
|
||||||
// function to convert a constant list of strings to a MatchPath instance.
|
// function to convert a constant list of strings to a MatchPath instance.
|
||||||
func(data ref.Val) (RequestMatcher, error) {
|
func(data ref.Val) (RequestMatcherWithError, error) {
|
||||||
refStringList := reflect.TypeOf([]string{})
|
refStringList := reflect.TypeOf([]string{})
|
||||||
strList, err := data.ConvertToNative(refStringList)
|
strList, err := data.ConvertToNative(refStringList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -145,9 +145,23 @@ func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchRemoteIP) Match(r *http.Request) bool {
|
func (m MatchRemoteIP) Match(r *http.Request) bool {
|
||||||
if r.TLS != nil && !r.TLS.HandshakeComplete {
|
match, err := m.MatchWithError(r)
|
||||||
return false // if handshake is not finished, we infer 0-RTT that has not verified remote IP; could be spoofed
|
if err != nil {
|
||||||
|
SetVar(r.Context(), MatcherErrorVarKey, err)
|
||||||
}
|
}
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (m MatchRemoteIP) MatchWithError(r *http.Request) (bool, error) {
|
||||||
|
// if handshake is not finished, we infer 0-RTT that has
|
||||||
|
// not verified remote IP; could be spoofed, so we throw
|
||||||
|
// HTTP 425 status to tell the client to try again after
|
||||||
|
// the handshake is complete
|
||||||
|
if r.TLS != nil && !r.TLS.HandshakeComplete {
|
||||||
|
return false, Error(http.StatusTooEarly, fmt.Errorf("TLS handshake not complete, remote IP cannot be verified"))
|
||||||
|
}
|
||||||
|
|
||||||
address := r.RemoteAddr
|
address := r.RemoteAddr
|
||||||
clientIP, zoneID, err := parseIPZoneFromString(address)
|
clientIP, zoneID, err := parseIPZoneFromString(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -155,7 +169,7 @@ func (m MatchRemoteIP) Match(r *http.Request) bool {
|
||||||
c.Write(zap.Error(err))
|
c.Write(zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones)
|
matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones)
|
||||||
if !matches && !zoneFilter {
|
if !matches && !zoneFilter {
|
||||||
|
@ -163,7 +177,7 @@ func (m MatchRemoteIP) Match(r *http.Request) bool {
|
||||||
c.Write(zap.String("zone", zoneID))
|
c.Write(zap.String("zone", zoneID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return matches
|
return matches, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaddyModule returns the Caddy module information.
|
// CaddyModule returns the Caddy module information.
|
||||||
|
@ -207,7 +221,7 @@ func (MatchClientIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||||
// internal data type of the MatchPath value.
|
// internal data type of the MatchPath value.
|
||||||
[]*cel.Type{cel.ListType(cel.StringType)},
|
[]*cel.Type{cel.ListType(cel.StringType)},
|
||||||
// function to convert a constant list of strings to a MatchPath instance.
|
// function to convert a constant list of strings to a MatchPath instance.
|
||||||
func(data ref.Val) (RequestMatcher, error) {
|
func(data ref.Val) (RequestMatcherWithError, error) {
|
||||||
refStringList := reflect.TypeOf([]string{})
|
refStringList := reflect.TypeOf([]string{})
|
||||||
strList, err := data.ConvertToNative(refStringList)
|
strList, err := data.ConvertToNative(refStringList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -238,20 +252,34 @@ func (m *MatchClientIP) Provision(ctx caddy.Context) error {
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchClientIP) Match(r *http.Request) bool {
|
func (m MatchClientIP) Match(r *http.Request) bool {
|
||||||
if r.TLS != nil && !r.TLS.HandshakeComplete {
|
match, err := m.MatchWithError(r)
|
||||||
return false // if handshake is not finished, we infer 0-RTT that has not verified remote IP; could be spoofed
|
if err != nil {
|
||||||
|
SetVar(r.Context(), MatcherErrorVarKey, err)
|
||||||
}
|
}
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (m MatchClientIP) MatchWithError(r *http.Request) (bool, error) {
|
||||||
|
// if handshake is not finished, we infer 0-RTT that has
|
||||||
|
// not verified remote IP; could be spoofed, so we throw
|
||||||
|
// HTTP 425 status to tell the client to try again after
|
||||||
|
// the handshake is complete
|
||||||
|
if r.TLS != nil && !r.TLS.HandshakeComplete {
|
||||||
|
return false, Error(http.StatusTooEarly, fmt.Errorf("TLS handshake not complete, remote IP cannot be verified"))
|
||||||
|
}
|
||||||
|
|
||||||
address := GetVar(r.Context(), ClientIPVarKey).(string)
|
address := GetVar(r.Context(), ClientIPVarKey).(string)
|
||||||
clientIP, zoneID, err := parseIPZoneFromString(address)
|
clientIP, zoneID, err := parseIPZoneFromString(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.logger.Error("getting client IP", zap.Error(err))
|
m.logger.Error("getting client IP", zap.Error(err))
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones)
|
matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones)
|
||||||
if !matches && !zoneFilter {
|
if !matches && !zoneFilter {
|
||||||
m.logger.Debug("zone ID from client IP did not match", zap.String("zone", zoneID))
|
m.logger.Debug("zone ID from client IP did not match", zap.String("zone", zoneID))
|
||||||
}
|
}
|
||||||
return matches
|
return matches, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func provisionCidrsZonesFromRanges(ranges []string) ([]*netip.Prefix, []string, error) {
|
func provisionCidrsZonesFromRanges(ranges []string) ([]*netip.Prefix, []string, error) {
|
||||||
|
@ -326,13 +354,13 @@ func matchIPByCidrZones(clientIP netip.Addr, zoneID string, cidrs []*netip.Prefi
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ RequestMatcher = (*MatchRemoteIP)(nil)
|
_ RequestMatcherWithError = (*MatchRemoteIP)(nil)
|
||||||
_ caddy.Provisioner = (*MatchRemoteIP)(nil)
|
_ caddy.Provisioner = (*MatchRemoteIP)(nil)
|
||||||
_ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil)
|
_ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil)
|
||||||
_ CELLibraryProducer = (*MatchRemoteIP)(nil)
|
_ CELLibraryProducer = (*MatchRemoteIP)(nil)
|
||||||
|
|
||||||
_ RequestMatcher = (*MatchClientIP)(nil)
|
_ RequestMatcherWithError = (*MatchClientIP)(nil)
|
||||||
_ caddy.Provisioner = (*MatchClientIP)(nil)
|
_ caddy.Provisioner = (*MatchClientIP)(nil)
|
||||||
_ caddyfile.Unmarshaler = (*MatchClientIP)(nil)
|
_ caddyfile.Unmarshaler = (*MatchClientIP)(nil)
|
||||||
_ CELLibraryProducer = (*MatchClientIP)(nil)
|
_ CELLibraryProducer = (*MatchClientIP)(nil)
|
||||||
)
|
)
|
||||||
|
|
|
@ -296,6 +296,12 @@ func (m MatchHost) Provision(_ caddy.Context) error {
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchHost) Match(r *http.Request) bool {
|
func (m MatchHost) Match(r *http.Request) bool {
|
||||||
|
match, _ := m.MatchWithError(r)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (m MatchHost) MatchWithError(r *http.Request) (bool, error) {
|
||||||
reqHost, _, err := net.SplitHostPort(r.Host)
|
reqHost, _, err := net.SplitHostPort(r.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// OK; probably didn't have a port
|
// OK; probably didn't have a port
|
||||||
|
@ -315,7 +321,7 @@ func (m MatchHost) Match(r *http.Request) bool {
|
||||||
return m[i] >= reqHost
|
return m[i] >= reqHost
|
||||||
})
|
})
|
||||||
if pos < len(m) && m[pos] == reqHost {
|
if pos < len(m) && m[pos] == reqHost {
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,13 +352,13 @@ outer:
|
||||||
continue outer
|
continue outer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true, nil
|
||||||
} else if strings.EqualFold(reqHost, host) {
|
} else if strings.EqualFold(reqHost, host) {
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CELLibrary produces options that expose this matcher for use in CEL
|
// CELLibrary produces options that expose this matcher for use in CEL
|
||||||
|
@ -366,7 +372,7 @@ func (MatchHost) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||||
"host",
|
"host",
|
||||||
"host_match_request_list",
|
"host_match_request_list",
|
||||||
[]*cel.Type{cel.ListType(cel.StringType)},
|
[]*cel.Type{cel.ListType(cel.StringType)},
|
||||||
func(data ref.Val) (RequestMatcher, error) {
|
func(data ref.Val) (RequestMatcherWithError, error) {
|
||||||
refStringList := reflect.TypeOf([]string{})
|
refStringList := reflect.TypeOf([]string{})
|
||||||
strList, err := data.ConvertToNative(refStringList)
|
strList, err := data.ConvertToNative(refStringList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -411,6 +417,12 @@ func (m MatchPath) Provision(_ caddy.Context) error {
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchPath) Match(r *http.Request) bool {
|
func (m MatchPath) Match(r *http.Request) bool {
|
||||||
|
match, _ := m.MatchWithError(r)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (m MatchPath) MatchWithError(r *http.Request) (bool, error) {
|
||||||
// Even though RFC 9110 says that path matching is case-sensitive
|
// Even though RFC 9110 says that path matching is case-sensitive
|
||||||
// (https://www.rfc-editor.org/rfc/rfc9110.html#section-4.2.3),
|
// (https://www.rfc-editor.org/rfc/rfc9110.html#section-4.2.3),
|
||||||
// we do case-insensitive matching to mitigate security issues
|
// we do case-insensitive matching to mitigate security issues
|
||||||
|
@ -436,7 +448,7 @@ func (m MatchPath) Match(r *http.Request) bool {
|
||||||
// special case: whole path is wildcard; this is unnecessary
|
// special case: whole path is wildcard; this is unnecessary
|
||||||
// as it matches all requests, which is the same as no matcher
|
// as it matches all requests, which is the same as no matcher
|
||||||
if matchPattern == "*" {
|
if matchPattern == "*" {
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean the path, merge doubled slashes, etc.
|
// Clean the path, merge doubled slashes, etc.
|
||||||
|
@ -464,7 +476,7 @@ func (m MatchPath) Match(r *http.Request) bool {
|
||||||
if strings.Contains(matchPattern, "%") {
|
if strings.Contains(matchPattern, "%") {
|
||||||
reqPathForPattern := CleanPath(r.URL.EscapedPath(), mergeSlashes)
|
reqPathForPattern := CleanPath(r.URL.EscapedPath(), mergeSlashes)
|
||||||
if m.matchPatternWithEscapeSequence(reqPathForPattern, matchPattern) {
|
if m.matchPatternWithEscapeSequence(reqPathForPattern, matchPattern) {
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// doing prefix/suffix/substring matches doesn't make sense
|
// doing prefix/suffix/substring matches doesn't make sense
|
||||||
|
@ -483,7 +495,7 @@ func (m MatchPath) Match(r *http.Request) bool {
|
||||||
strings.HasPrefix(matchPattern, "*") &&
|
strings.HasPrefix(matchPattern, "*") &&
|
||||||
strings.HasSuffix(matchPattern, "*") {
|
strings.HasSuffix(matchPattern, "*") {
|
||||||
if strings.Contains(reqPathForPattern, matchPattern[1:len(matchPattern)-1]) {
|
if strings.Contains(reqPathForPattern, matchPattern[1:len(matchPattern)-1]) {
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -495,7 +507,7 @@ func (m MatchPath) Match(r *http.Request) bool {
|
||||||
// treat it as a fast suffix match
|
// treat it as a fast suffix match
|
||||||
if strings.HasPrefix(matchPattern, "*") {
|
if strings.HasPrefix(matchPattern, "*") {
|
||||||
if strings.HasSuffix(reqPathForPattern, matchPattern[1:]) {
|
if strings.HasSuffix(reqPathForPattern, matchPattern[1:]) {
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -504,7 +516,7 @@ func (m MatchPath) Match(r *http.Request) bool {
|
||||||
// treat it as a fast prefix match
|
// treat it as a fast prefix match
|
||||||
if strings.HasSuffix(matchPattern, "*") {
|
if strings.HasSuffix(matchPattern, "*") {
|
||||||
if strings.HasPrefix(reqPathForPattern, matchPattern[:len(matchPattern)-1]) {
|
if strings.HasPrefix(reqPathForPattern, matchPattern[:len(matchPattern)-1]) {
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -515,10 +527,10 @@ func (m MatchPath) Match(r *http.Request) bool {
|
||||||
// because we can't handle it anyway
|
// because we can't handle it anyway
|
||||||
matches, _ := path.Match(matchPattern, reqPathForPattern)
|
matches, _ := path.Match(matchPattern, reqPathForPattern)
|
||||||
if matches {
|
if matches {
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath string) bool {
|
func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath string) bool {
|
||||||
|
@ -642,7 +654,7 @@ func (MatchPath) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||||
// internal data type of the MatchPath value.
|
// internal data type of the MatchPath value.
|
||||||
[]*cel.Type{cel.ListType(cel.StringType)},
|
[]*cel.Type{cel.ListType(cel.StringType)},
|
||||||
// function to convert a constant list of strings to a MatchPath instance.
|
// function to convert a constant list of strings to a MatchPath instance.
|
||||||
func(data ref.Val) (RequestMatcher, error) {
|
func(data ref.Val) (RequestMatcherWithError, error) {
|
||||||
refStringList := reflect.TypeOf([]string{})
|
refStringList := reflect.TypeOf([]string{})
|
||||||
strList, err := data.ConvertToNative(refStringList)
|
strList, err := data.ConvertToNative(refStringList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -677,6 +689,12 @@ func (MatchPathRE) CaddyModule() caddy.ModuleInfo {
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchPathRE) Match(r *http.Request) bool {
|
func (m MatchPathRE) Match(r *http.Request) bool {
|
||||||
|
match, _ := m.MatchWithError(r)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (m MatchPathRE) MatchWithError(r *http.Request) (bool, error) {
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
|
|
||||||
// Clean the path, merges doubled slashes, etc.
|
// Clean the path, merges doubled slashes, etc.
|
||||||
|
@ -684,7 +702,7 @@ func (m MatchPathRE) Match(r *http.Request) bool {
|
||||||
// the path matcher. See #4407
|
// the path matcher. See #4407
|
||||||
cleanedPath := cleanPath(r.URL.Path)
|
cleanedPath := cleanPath(r.URL.Path)
|
||||||
|
|
||||||
return m.MatchRegexp.Match(cleanedPath, repl)
|
return m.MatchRegexp.Match(cleanedPath, repl), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CELLibrary produces options that expose this matcher for use in CEL
|
// CELLibrary produces options that expose this matcher for use in CEL
|
||||||
|
@ -698,7 +716,7 @@ func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||||
"path_regexp",
|
"path_regexp",
|
||||||
"path_regexp_request_string",
|
"path_regexp_request_string",
|
||||||
[]*cel.Type{cel.StringType},
|
[]*cel.Type{cel.StringType},
|
||||||
func(data ref.Val) (RequestMatcher, error) {
|
func(data ref.Val) (RequestMatcherWithError, error) {
|
||||||
pattern := data.(types.String)
|
pattern := data.(types.String)
|
||||||
matcher := MatchPathRE{MatchRegexp{
|
matcher := MatchPathRE{MatchRegexp{
|
||||||
Name: ctx.Value(MatcherNameCtxKey).(string),
|
Name: ctx.Value(MatcherNameCtxKey).(string),
|
||||||
|
@ -715,7 +733,7 @@ func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||||
"path_regexp",
|
"path_regexp",
|
||||||
"path_regexp_request_string_string",
|
"path_regexp_request_string_string",
|
||||||
[]*cel.Type{cel.StringType, cel.StringType},
|
[]*cel.Type{cel.StringType, cel.StringType},
|
||||||
func(data ref.Val) (RequestMatcher, error) {
|
func(data ref.Val) (RequestMatcherWithError, error) {
|
||||||
refStringList := reflect.TypeOf([]string{})
|
refStringList := reflect.TypeOf([]string{})
|
||||||
params, err := data.ConvertToNative(refStringList)
|
params, err := data.ConvertToNative(refStringList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -764,7 +782,13 @@ func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchMethod) Match(r *http.Request) bool {
|
func (m MatchMethod) Match(r *http.Request) bool {
|
||||||
return slices.Contains(m, r.Method)
|
match, _ := m.MatchWithError(r)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (m MatchMethod) MatchWithError(r *http.Request) (bool, error) {
|
||||||
|
return slices.Contains(m, r.Method), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CELLibrary produces options that expose this matcher for use in CEL
|
// CELLibrary produces options that expose this matcher for use in CEL
|
||||||
|
@ -778,7 +802,7 @@ func (MatchMethod) CELLibrary(_ caddy.Context) (cel.Library, error) {
|
||||||
"method",
|
"method",
|
||||||
"method_request_list",
|
"method_request_list",
|
||||||
[]*cel.Type{cel.ListType(cel.StringType)},
|
[]*cel.Type{cel.ListType(cel.StringType)},
|
||||||
func(data ref.Val) (RequestMatcher, error) {
|
func(data ref.Val) (RequestMatcherWithError, error) {
|
||||||
refStringList := reflect.TypeOf([]string{})
|
refStringList := reflect.TypeOf([]string{})
|
||||||
strList, err := data.ConvertToNative(refStringList)
|
strList, err := data.ConvertToNative(refStringList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -823,10 +847,17 @@ func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
|
||||||
// Match returns true if r matches m. An empty m matches an empty query string.
|
// Match returns true if r matches m. An empty m matches an empty query string.
|
||||||
func (m MatchQuery) Match(r *http.Request) bool {
|
func (m MatchQuery) Match(r *http.Request) bool {
|
||||||
|
match, _ := m.MatchWithError(r)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
// An empty m matches an empty query string.
|
||||||
|
func (m MatchQuery) MatchWithError(r *http.Request) (bool, error) {
|
||||||
// If no query keys are configured, this only
|
// If no query keys are configured, this only
|
||||||
// matches an empty query string.
|
// matches an empty query string.
|
||||||
if len(m) == 0 {
|
if len(m) == 0 {
|
||||||
return len(r.URL.Query()) == 0
|
return len(r.URL.Query()) == 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
|
@ -843,7 +874,7 @@ func (m MatchQuery) Match(r *http.Request) bool {
|
||||||
// "Relying on parser alignment for security is doomed." Overall conclusion is that
|
// "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 ;.
|
// 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.
|
// We regard the Go team's decision as sound and thus reject malformed query strings.
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count the amount of matched keys, to ensure we AND
|
// Count the amount of matched keys, to ensure we AND
|
||||||
|
@ -854,7 +885,7 @@ func (m MatchQuery) Match(r *http.Request) bool {
|
||||||
param = repl.ReplaceAll(param, "")
|
param = repl.ReplaceAll(param, "")
|
||||||
paramVal, found := parsed[param]
|
paramVal, found := parsed[param]
|
||||||
if !found {
|
if !found {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
for _, v := range vals {
|
for _, v := range vals {
|
||||||
v = repl.ReplaceAll(v, "")
|
v = repl.ReplaceAll(v, "")
|
||||||
|
@ -864,7 +895,7 @@ func (m MatchQuery) Match(r *http.Request) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return matchedKeys == len(m)
|
return matchedKeys == len(m), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CELLibrary produces options that expose this matcher for use in CEL
|
// CELLibrary produces options that expose this matcher for use in CEL
|
||||||
|
@ -878,7 +909,7 @@ func (MatchQuery) CELLibrary(_ caddy.Context) (cel.Library, error) {
|
||||||
"query",
|
"query",
|
||||||
"query_matcher_request_map",
|
"query_matcher_request_map",
|
||||||
[]*cel.Type{CELTypeJSON},
|
[]*cel.Type{CELTypeJSON},
|
||||||
func(data ref.Val) (RequestMatcher, error) {
|
func(data ref.Val) (RequestMatcherWithError, error) {
|
||||||
mapStrListStr, err := CELValueToMapStrList(data)
|
mapStrListStr, err := CELValueToMapStrList(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -940,8 +971,14 @@ func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchHeader) Match(r *http.Request) bool {
|
func (m MatchHeader) Match(r *http.Request) bool {
|
||||||
|
match, _ := m.MatchWithError(r)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (m MatchHeader) MatchWithError(r *http.Request) (bool, error) {
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
return matchHeaders(r.Header, http.Header(m), r.Host, repl)
|
return matchHeaders(r.Header, http.Header(m), r.Host, repl), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CELLibrary produces options that expose this matcher for use in CEL
|
// CELLibrary produces options that expose this matcher for use in CEL
|
||||||
|
@ -956,7 +993,7 @@ func (MatchHeader) CELLibrary(_ caddy.Context) (cel.Library, error) {
|
||||||
"header",
|
"header",
|
||||||
"header_matcher_request_map",
|
"header_matcher_request_map",
|
||||||
[]*cel.Type{CELTypeJSON},
|
[]*cel.Type{CELTypeJSON},
|
||||||
func(data ref.Val) (RequestMatcher, error) {
|
func(data ref.Val) (RequestMatcherWithError, error) {
|
||||||
mapStrListStr, err := CELValueToMapStrList(data)
|
mapStrListStr, err := CELValueToMapStrList(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -1075,6 +1112,12 @@ func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchHeaderRE) Match(r *http.Request) bool {
|
func (m MatchHeaderRE) Match(r *http.Request) bool {
|
||||||
|
match, _ := m.MatchWithError(r)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (m MatchHeaderRE) MatchWithError(r *http.Request) (bool, error) {
|
||||||
for field, rm := range m {
|
for field, rm := range m {
|
||||||
actualFieldVals := getHeaderFieldVals(r.Header, field, r.Host)
|
actualFieldVals := getHeaderFieldVals(r.Header, field, r.Host)
|
||||||
match := false
|
match := false
|
||||||
|
@ -1087,10 +1130,10 @@ func (m MatchHeaderRE) Match(r *http.Request) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !match {
|
if !match {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provision compiles m's regular expressions.
|
// Provision compiles m's regular expressions.
|
||||||
|
@ -1126,7 +1169,7 @@ func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||||
"header_regexp",
|
"header_regexp",
|
||||||
"header_regexp_request_string_string",
|
"header_regexp_request_string_string",
|
||||||
[]*cel.Type{cel.StringType, cel.StringType},
|
[]*cel.Type{cel.StringType, cel.StringType},
|
||||||
func(data ref.Val) (RequestMatcher, error) {
|
func(data ref.Val) (RequestMatcherWithError, error) {
|
||||||
refStringList := reflect.TypeOf([]string{})
|
refStringList := reflect.TypeOf([]string{})
|
||||||
params, err := data.ConvertToNative(refStringList)
|
params, err := data.ConvertToNative(refStringList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1149,7 +1192,7 @@ func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||||
"header_regexp",
|
"header_regexp",
|
||||||
"header_regexp_request_string_string_string",
|
"header_regexp_request_string_string_string",
|
||||||
[]*cel.Type{cel.StringType, cel.StringType, cel.StringType},
|
[]*cel.Type{cel.StringType, cel.StringType, cel.StringType},
|
||||||
func(data ref.Val) (RequestMatcher, error) {
|
func(data ref.Val) (RequestMatcherWithError, error) {
|
||||||
refStringList := reflect.TypeOf([]string{})
|
refStringList := reflect.TypeOf([]string{})
|
||||||
params, err := data.ConvertToNative(refStringList)
|
params, err := data.ConvertToNative(refStringList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1187,31 +1230,37 @@ func (MatchProtocol) CaddyModule() caddy.ModuleInfo {
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchProtocol) Match(r *http.Request) bool {
|
func (m MatchProtocol) Match(r *http.Request) bool {
|
||||||
|
match, _ := m.MatchWithError(r)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (m MatchProtocol) MatchWithError(r *http.Request) (bool, error) {
|
||||||
switch string(m) {
|
switch string(m) {
|
||||||
case "grpc":
|
case "grpc":
|
||||||
return strings.HasPrefix(r.Header.Get("content-type"), "application/grpc")
|
return strings.HasPrefix(r.Header.Get("content-type"), "application/grpc"), nil
|
||||||
case "https":
|
case "https":
|
||||||
return r.TLS != nil
|
return r.TLS != nil, nil
|
||||||
case "http":
|
case "http":
|
||||||
return r.TLS == nil
|
return r.TLS == nil, nil
|
||||||
case "http/1.0":
|
case "http/1.0":
|
||||||
return r.ProtoMajor == 1 && r.ProtoMinor == 0
|
return r.ProtoMajor == 1 && r.ProtoMinor == 0, nil
|
||||||
case "http/1.0+":
|
case "http/1.0+":
|
||||||
return r.ProtoAtLeast(1, 0)
|
return r.ProtoAtLeast(1, 0), nil
|
||||||
case "http/1.1":
|
case "http/1.1":
|
||||||
return r.ProtoMajor == 1 && r.ProtoMinor == 1
|
return r.ProtoMajor == 1 && r.ProtoMinor == 1, nil
|
||||||
case "http/1.1+":
|
case "http/1.1+":
|
||||||
return r.ProtoAtLeast(1, 1)
|
return r.ProtoAtLeast(1, 1), nil
|
||||||
case "http/2":
|
case "http/2":
|
||||||
return r.ProtoMajor == 2
|
return r.ProtoMajor == 2, nil
|
||||||
case "http/2+":
|
case "http/2+":
|
||||||
return r.ProtoAtLeast(2, 0)
|
return r.ProtoAtLeast(2, 0), nil
|
||||||
case "http/3":
|
case "http/3":
|
||||||
return r.ProtoMajor == 3
|
return r.ProtoMajor == 3, nil
|
||||||
case "http/3+":
|
case "http/3+":
|
||||||
return r.ProtoAtLeast(3, 0)
|
return r.ProtoAtLeast(3, 0), nil
|
||||||
}
|
}
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||||
|
@ -1238,7 +1287,7 @@ func (MatchProtocol) CELLibrary(_ caddy.Context) (cel.Library, error) {
|
||||||
"protocol",
|
"protocol",
|
||||||
"protocol_request_string",
|
"protocol_request_string",
|
||||||
[]*cel.Type{cel.StringType},
|
[]*cel.Type{cel.StringType},
|
||||||
func(data ref.Val) (RequestMatcher, error) {
|
func(data ref.Val) (RequestMatcherWithError, error) {
|
||||||
protocolStr, ok := data.(types.String)
|
protocolStr, ok := data.(types.String)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("protocol argument was not a string")
|
return nil, errors.New("protocol argument was not a string")
|
||||||
|
@ -1258,16 +1307,22 @@ func (MatchTLS) CaddyModule() caddy.ModuleInfo {
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchTLS) Match(r *http.Request) bool {
|
func (m MatchTLS) Match(r *http.Request) bool {
|
||||||
|
match, _ := m.MatchWithError(r)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (m MatchTLS) MatchWithError(r *http.Request) (bool, error) {
|
||||||
if r.TLS == nil {
|
if r.TLS == nil {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
if m.HandshakeComplete != nil {
|
if m.HandshakeComplete != nil {
|
||||||
if (!*m.HandshakeComplete && r.TLS.HandshakeComplete) ||
|
if (!*m.HandshakeComplete && r.TLS.HandshakeComplete) ||
|
||||||
(*m.HandshakeComplete && !r.TLS.HandshakeComplete) {
|
(*m.HandshakeComplete && !r.TLS.HandshakeComplete) {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile parses Caddyfile tokens for this matcher. Syntax:
|
// UnmarshalCaddyfile parses Caddyfile tokens for this matcher. Syntax:
|
||||||
|
@ -1337,7 +1392,15 @@ func (m *MatchNot) Provision(ctx caddy.Context) error {
|
||||||
for _, modMap := range matcherSets.([]map[string]any) {
|
for _, modMap := range matcherSets.([]map[string]any) {
|
||||||
var ms MatcherSet
|
var ms MatcherSet
|
||||||
for _, modIface := range modMap {
|
for _, modIface := range modMap {
|
||||||
ms = append(ms, modIface.(RequestMatcher))
|
if mod, ok := modIface.(RequestMatcherWithError); ok {
|
||||||
|
ms = append(ms, mod)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if mod, ok := modIface.(RequestMatcher); ok {
|
||||||
|
ms = append(ms, mod)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("module is not a request matcher: %T", modIface)
|
||||||
}
|
}
|
||||||
m.MatcherSets = append(m.MatcherSets, ms)
|
m.MatcherSets = append(m.MatcherSets, ms)
|
||||||
}
|
}
|
||||||
|
@ -1348,12 +1411,24 @@ func (m *MatchNot) Provision(ctx caddy.Context) error {
|
||||||
// the embedded matchers, false is returned if any of its matcher
|
// the embedded matchers, false is returned if any of its matcher
|
||||||
// sets return true.
|
// sets return true.
|
||||||
func (m MatchNot) Match(r *http.Request) bool {
|
func (m MatchNot) Match(r *http.Request) bool {
|
||||||
|
match, _ := m.MatchWithError(r)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError 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) MatchWithError(r *http.Request) (bool, error) {
|
||||||
for _, ms := range m.MatcherSets {
|
for _, ms := range m.MatcherSets {
|
||||||
if ms.Match(r) {
|
matches, err := ms.MatchWithError(r)
|
||||||
return false
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if matches {
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchRegexp is an embedable type for matching
|
// MatchRegexp is an embedable type for matching
|
||||||
|
@ -1469,7 +1544,7 @@ func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
// ParseCaddyfileNestedMatcher parses the Caddyfile tokens for a nested
|
// ParseCaddyfileNestedMatcher parses the Caddyfile tokens for a nested
|
||||||
// matcher set, and returns its raw module map value.
|
// matcher set, and returns its raw module map value.
|
||||||
func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
|
func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
|
||||||
matcherMap := make(map[string]RequestMatcher)
|
matcherMap := make(map[string]any)
|
||||||
|
|
||||||
// in case there are multiple instances of the same matcher, concatenate
|
// in case there are multiple instances of the same matcher, concatenate
|
||||||
// their tokens (we expect that UnmarshalCaddyfile should be able to
|
// their tokens (we expect that UnmarshalCaddyfile should be able to
|
||||||
|
@ -1494,11 +1569,15 @@ func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rm, ok := unm.(RequestMatcher)
|
if rm, ok := unm.(RequestMatcherWithError); ok {
|
||||||
if !ok {
|
matcherMap[matcherName] = rm
|
||||||
return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
|
continue
|
||||||
}
|
}
|
||||||
matcherMap[matcherName] = rm
|
if rm, ok := unm.(RequestMatcher); ok {
|
||||||
|
matcherMap[matcherName] = rm
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we should now have a functional matcher, but we also
|
// we should now have a functional matcher, but we also
|
||||||
|
@ -1524,24 +1603,28 @@ const regexpPlaceholderPrefix = "http.regexp"
|
||||||
// holds an optional error emitted from a request matcher,
|
// holds an optional error emitted from a request matcher,
|
||||||
// to short-circuit the handler chain, since matchers cannot
|
// to short-circuit the handler chain, since matchers cannot
|
||||||
// return errors via the RequestMatcher interface.
|
// return errors via the RequestMatcher interface.
|
||||||
|
//
|
||||||
|
// Deprecated: Matchers should implement RequestMatcherWithError
|
||||||
|
// which can return an error directly, instead of smuggling it
|
||||||
|
// through the vars map.
|
||||||
const MatcherErrorVarKey = "matchers.error"
|
const MatcherErrorVarKey = "matchers.error"
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ RequestMatcher = (*MatchHost)(nil)
|
_ RequestMatcherWithError = (*MatchHost)(nil)
|
||||||
_ caddy.Provisioner = (*MatchHost)(nil)
|
_ caddy.Provisioner = (*MatchHost)(nil)
|
||||||
_ RequestMatcher = (*MatchPath)(nil)
|
_ RequestMatcherWithError = (*MatchPath)(nil)
|
||||||
_ RequestMatcher = (*MatchPathRE)(nil)
|
_ RequestMatcherWithError = (*MatchPathRE)(nil)
|
||||||
_ caddy.Provisioner = (*MatchPathRE)(nil)
|
_ caddy.Provisioner = (*MatchPathRE)(nil)
|
||||||
_ RequestMatcher = (*MatchMethod)(nil)
|
_ RequestMatcherWithError = (*MatchMethod)(nil)
|
||||||
_ RequestMatcher = (*MatchQuery)(nil)
|
_ RequestMatcherWithError = (*MatchQuery)(nil)
|
||||||
_ RequestMatcher = (*MatchHeader)(nil)
|
_ RequestMatcherWithError = (*MatchHeader)(nil)
|
||||||
_ RequestMatcher = (*MatchHeaderRE)(nil)
|
_ RequestMatcherWithError = (*MatchHeaderRE)(nil)
|
||||||
_ caddy.Provisioner = (*MatchHeaderRE)(nil)
|
_ caddy.Provisioner = (*MatchHeaderRE)(nil)
|
||||||
_ RequestMatcher = (*MatchProtocol)(nil)
|
_ RequestMatcherWithError = (*MatchProtocol)(nil)
|
||||||
_ RequestMatcher = (*MatchNot)(nil)
|
_ RequestMatcherWithError = (*MatchNot)(nil)
|
||||||
_ caddy.Provisioner = (*MatchNot)(nil)
|
_ caddy.Provisioner = (*MatchNot)(nil)
|
||||||
_ caddy.Provisioner = (*MatchRegexp)(nil)
|
_ caddy.Provisioner = (*MatchRegexp)(nil)
|
||||||
|
|
||||||
_ caddyfile.Unmarshaler = (*MatchHost)(nil)
|
_ caddyfile.Unmarshaler = (*MatchHost)(nil)
|
||||||
_ caddyfile.Unmarshaler = (*MatchPath)(nil)
|
_ caddyfile.Unmarshaler = (*MatchPath)(nil)
|
||||||
|
|
|
@ -158,7 +158,10 @@ func TestHostMatcher(t *testing.T) {
|
||||||
t.Errorf("Test %d %v: provisioning failed: %v", i, tc.match, err)
|
t.Errorf("Test %d %v: provisioning failed: %v", i, tc.match, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := tc.match.Match(req)
|
actual, err := tc.match.MatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
|
||||||
|
}
|
||||||
if actual != tc.expect {
|
if actual != tc.expect {
|
||||||
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
|
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
|
||||||
continue
|
continue
|
||||||
|
@ -430,7 +433,10 @@ func TestPathMatcher(t *testing.T) {
|
||||||
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
actual := tc.match.Match(req)
|
actual, err := tc.match.MatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
|
||||||
|
}
|
||||||
if actual != tc.expect {
|
if actual != tc.expect {
|
||||||
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
|
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
|
||||||
continue
|
continue
|
||||||
|
@ -451,7 +457,10 @@ func TestPathMatcherWindows(t *testing.T) {
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
match := MatchPath{"*.php"}
|
match := MatchPath{"*.php"}
|
||||||
matched := match.Match(req)
|
matched, err := match.MatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error, but got: %v", err)
|
||||||
|
}
|
||||||
if !matched {
|
if !matched {
|
||||||
t.Errorf("Expected to match; should ignore trailing dots and spaces")
|
t.Errorf("Expected to match; should ignore trailing dots and spaces")
|
||||||
}
|
}
|
||||||
|
@ -555,7 +564,10 @@ func TestPathREMatcher(t *testing.T) {
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
||||||
|
|
||||||
actual := tc.match.Match(req)
|
actual, err := tc.match.MatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
|
||||||
|
}
|
||||||
if actual != tc.expect {
|
if actual != tc.expect {
|
||||||
t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
|
t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
|
||||||
i, tc.match.Pattern, tc.expect, actual, tc.input)
|
i, tc.match.Pattern, tc.expect, actual, tc.input)
|
||||||
|
@ -691,7 +703,10 @@ func TestHeaderMatcher(t *testing.T) {
|
||||||
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
actual := tc.match.Match(req)
|
actual, err := tc.match.MatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
|
||||||
|
}
|
||||||
if actual != tc.expect {
|
if actual != tc.expect {
|
||||||
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
|
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
|
||||||
continue
|
continue
|
||||||
|
@ -818,7 +833,10 @@ func TestQueryMatcher(t *testing.T) {
|
||||||
repl.Set("http.vars.debug", "1")
|
repl.Set("http.vars.debug", "1")
|
||||||
repl.Set("http.vars.key", "somekey")
|
repl.Set("http.vars.key", "somekey")
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
actual := tc.match.Match(req)
|
actual, err := tc.match.MatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
|
||||||
|
}
|
||||||
if actual != tc.expect {
|
if actual != tc.expect {
|
||||||
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
|
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
|
||||||
continue
|
continue
|
||||||
|
@ -887,7 +905,10 @@ func TestHeaderREMatcher(t *testing.T) {
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
||||||
|
|
||||||
actual := tc.match.Match(req)
|
actual, err := tc.match.MatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
|
||||||
|
}
|
||||||
if actual != tc.expect {
|
if actual != tc.expect {
|
||||||
t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
|
t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
|
||||||
i, tc.match, tc.expect, actual, tc.input)
|
i, tc.match, tc.expect, actual, tc.input)
|
||||||
|
@ -927,7 +948,7 @@ func BenchmarkHeaderREMatcher(b *testing.B) {
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
||||||
for run := 0; run < b.N; run++ {
|
for run := 0; run < b.N; run++ {
|
||||||
match.Match(req)
|
match.MatchWithError(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -998,7 +1019,10 @@ func TestVarREMatcher(t *testing.T) {
|
||||||
|
|
||||||
tc.input.ServeHTTP(httptest.NewRecorder(), req, emptyHandler)
|
tc.input.ServeHTTP(httptest.NewRecorder(), req, emptyHandler)
|
||||||
|
|
||||||
actual := tc.match.Match(req)
|
actual, err := tc.match.MatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
|
||||||
|
}
|
||||||
if actual != tc.expect {
|
if actual != tc.expect {
|
||||||
t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
|
t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
|
||||||
i, tc.match, tc.expect, actual, tc.input)
|
i, tc.match, tc.expect, actual, tc.input)
|
||||||
|
@ -1123,7 +1147,10 @@ func TestNotMatcher(t *testing.T) {
|
||||||
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
actual := tc.match.Match(req)
|
actual, err := tc.match.MatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
|
||||||
|
}
|
||||||
if actual != tc.expect {
|
if actual != tc.expect {
|
||||||
t.Errorf("Test %d %+v: Expected %t, got %t for: host=%s path=%s'", i, tc.match, tc.expect, actual, tc.host, tc.path)
|
t.Errorf("Test %d %+v: Expected %t, got %t for: host=%s path=%s'", i, tc.match, tc.expect, actual, tc.host, tc.path)
|
||||||
continue
|
continue
|
||||||
|
@ -1155,7 +1182,7 @@ func BenchmarkLargeHostMatcher(b *testing.B) {
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
matcher.Match(req)
|
matcher.MatchWithError(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1169,7 +1196,7 @@ func BenchmarkHostMatcherWithoutPlaceholder(b *testing.B) {
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
match.Match(req)
|
match.MatchWithError(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1187,6 +1214,6 @@ func BenchmarkHostMatcherWithPlaceholder(b *testing.B) {
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
match.Match(req)
|
match.MatchWithError(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ type HealthChecks struct {
|
||||||
// health checks (that is, health checks which occur in a
|
// health checks (that is, health checks which occur in a
|
||||||
// background goroutine independently).
|
// background goroutine independently).
|
||||||
type ActiveHealthChecks struct {
|
type ActiveHealthChecks struct {
|
||||||
// DEPRECATED: Use 'uri' instead. This field will be removed. TODO: remove this field
|
// Deprecated: Use 'uri' instead. This field will be removed. TODO: remove this field
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
|
|
||||||
// The URI (path and query) to use for health checks
|
// The URI (path and query) to use for health checks
|
||||||
|
|
|
@ -545,11 +545,11 @@ type TLSConfig struct {
|
||||||
// Certificate authority module which provides the certificate pool of trusted certificates
|
// Certificate authority module which provides the certificate pool of trusted certificates
|
||||||
CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"`
|
CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"`
|
||||||
|
|
||||||
// DEPRECATED: Use the `ca` field with the `tls.ca_pool.source.inline` module instead.
|
// Deprecated: Use the `ca` field with the `tls.ca_pool.source.inline` module instead.
|
||||||
// Optional list of base64-encoded DER-encoded CA certificates to trust.
|
// Optional list of base64-encoded DER-encoded CA certificates to trust.
|
||||||
RootCAPool []string `json:"root_ca_pool,omitempty"`
|
RootCAPool []string `json:"root_ca_pool,omitempty"`
|
||||||
|
|
||||||
// DEPRECATED: Use the `ca` field with the `tls.ca_pool.source.file` module instead.
|
// Deprecated: Use the `ca` field with the `tls.ca_pool.source.file` module instead.
|
||||||
// List of PEM-encoded CA certificate files to add to the same trust
|
// List of PEM-encoded CA certificate files to add to the same trust
|
||||||
// store as RootCAPool (or root_ca_pool in the JSON).
|
// store as RootCAPool (or root_ca_pool in the JSON).
|
||||||
RootCAPEMFiles []string `json:"root_ca_pem_files,omitempty"`
|
RootCAPEMFiles []string `json:"root_ca_pem_files,omitempty"`
|
||||||
|
|
|
@ -496,7 +496,7 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
|
||||||
if proxyErr == nil {
|
if proxyErr == nil {
|
||||||
proxyErr = caddyhttp.Error(http.StatusServiceUnavailable, errNoUpstream)
|
proxyErr = caddyhttp.Error(http.StatusServiceUnavailable, errNoUpstream)
|
||||||
}
|
}
|
||||||
if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r) {
|
if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r, h.logger) {
|
||||||
return true, proxyErr
|
return true, proxyErr
|
||||||
}
|
}
|
||||||
return false, proxyErr
|
return false, proxyErr
|
||||||
|
@ -562,7 +562,7 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
|
||||||
h.countFailure(upstream)
|
h.countFailure(upstream)
|
||||||
|
|
||||||
// if we've tried long enough, break
|
// if we've tried long enough, break
|
||||||
if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r) {
|
if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r, h.logger) {
|
||||||
return true, proxyErr
|
return true, proxyErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1089,7 +1089,7 @@ func (h *Handler) finalizeResponse(
|
||||||
// If true is returned, it has already blocked long enough before
|
// If true is returned, it has already blocked long enough before
|
||||||
// the next retry (i.e. no more sleeping is needed). If false is
|
// the next retry (i.e. no more sleeping is needed). If false is
|
||||||
// returned, the handler should stop trying to proxy the request.
|
// returned, the handler should stop trying to proxy the request.
|
||||||
func (lb LoadBalancing) tryAgain(ctx caddy.Context, start time.Time, retries int, proxyErr error, req *http.Request) bool {
|
func (lb LoadBalancing) tryAgain(ctx caddy.Context, start time.Time, retries int, proxyErr error, req *http.Request, logger *zap.Logger) bool {
|
||||||
// no retries are configured
|
// no retries are configured
|
||||||
if lb.TryDuration == 0 && lb.Retries == 0 {
|
if lb.TryDuration == 0 && lb.Retries == 0 {
|
||||||
return false
|
return false
|
||||||
|
@ -1124,7 +1124,12 @@ func (lb LoadBalancing) tryAgain(ctx caddy.Context, start time.Time, retries int
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !lb.RetryMatch.AnyMatch(req) {
|
match, err := lb.RetryMatch.AnyMatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error matching request for retry", zap.Error(err))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,18 +254,13 @@ func wrapRoute(route Route) Middleware {
|
||||||
nextCopy := next
|
nextCopy := next
|
||||||
|
|
||||||
// route must match at least one of the matcher sets
|
// route must match at least one of the matcher sets
|
||||||
if !route.MatcherSets.AnyMatch(req) {
|
matches, err := route.MatcherSets.AnyMatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
// allow matchers the opportunity to short circuit
|
// allow matchers the opportunity to short circuit
|
||||||
// the request and trigger the error handling chain
|
// the request and trigger the error handling chain
|
||||||
err, ok := GetVar(req.Context(), MatcherErrorVarKey).(error)
|
return err
|
||||||
if ok {
|
}
|
||||||
// clear out the error from context, otherwise
|
if !matches {
|
||||||
// it will cascade to the error routes (#4916)
|
|
||||||
SetVar(req.Context(), MatcherErrorVarKey, nil)
|
|
||||||
// return the matcher's error
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// call the next handler, and skip this one,
|
// call the next handler, and skip this one,
|
||||||
// since the matcher didn't match
|
// since the matcher didn't match
|
||||||
return nextCopy.ServeHTTP(rw, req)
|
return nextCopy.ServeHTTP(rw, req)
|
||||||
|
@ -341,19 +336,58 @@ func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler, metrics *Metrics) M
|
||||||
// MatcherSet is a set of matchers which
|
// MatcherSet is a set of matchers which
|
||||||
// must all match in order for the request
|
// must all match in order for the request
|
||||||
// to be matched successfully.
|
// to be matched successfully.
|
||||||
type MatcherSet []RequestMatcher
|
type MatcherSet []any
|
||||||
|
|
||||||
// Match returns true if the request matches all
|
// Match returns true if the request matches all
|
||||||
// matchers in mset or if there are no matchers.
|
// matchers in mset or if there are no matchers.
|
||||||
func (mset MatcherSet) Match(r *http.Request) bool {
|
func (mset MatcherSet) Match(r *http.Request) bool {
|
||||||
for _, m := range mset {
|
for _, m := range mset {
|
||||||
if !m.Match(r) {
|
if me, ok := m.(RequestMatcherWithError); ok {
|
||||||
return false
|
match, _ := me.MatchWithError(r)
|
||||||
|
if !match {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
if me, ok := m.(RequestMatcher); ok {
|
||||||
|
if !me.Match(r) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (mset MatcherSet) MatchWithError(r *http.Request) (bool, error) {
|
||||||
|
for _, m := range mset {
|
||||||
|
if me, ok := m.(RequestMatcherWithError); ok {
|
||||||
|
match, err := me.MatchWithError(r)
|
||||||
|
if err != nil || !match {
|
||||||
|
return match, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if me, ok := m.(RequestMatcher); ok {
|
||||||
|
if !me.Match(r) {
|
||||||
|
// for backwards compatibility
|
||||||
|
err, ok := GetVar(r.Context(), MatcherErrorVarKey).(error)
|
||||||
|
if ok {
|
||||||
|
// clear out the error from context since we've consumed it
|
||||||
|
SetVar(r.Context(), MatcherErrorVarKey, nil)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("matcher is not a RequestMatcher or RequestMatcherWithError: %#v", m)
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
// RawMatcherSets is a group of matcher sets
|
// RawMatcherSets is a group of matcher sets
|
||||||
// in their raw, JSON form.
|
// in their raw, JSON form.
|
||||||
type RawMatcherSets []caddy.ModuleMap
|
type RawMatcherSets []caddy.ModuleMap
|
||||||
|
@ -366,25 +400,50 @@ type MatcherSets []MatcherSet
|
||||||
// AnyMatch returns true if req matches any of the
|
// AnyMatch returns true if req matches any of the
|
||||||
// matcher sets in ms or if there are no matchers,
|
// matcher sets in ms or if there are no matchers,
|
||||||
// in which case the request always matches.
|
// in which case the request always matches.
|
||||||
|
//
|
||||||
|
// Deprecated: Use AnyMatchWithError instead.
|
||||||
func (ms MatcherSets) AnyMatch(req *http.Request) bool {
|
func (ms MatcherSets) AnyMatch(req *http.Request) bool {
|
||||||
for _, m := range ms {
|
for _, m := range ms {
|
||||||
if m.Match(req) {
|
match, err := m.MatchWithError(req)
|
||||||
return true
|
if err != nil {
|
||||||
|
SetVar(req.Context(), MatcherErrorVarKey, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if match {
|
||||||
|
return match
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return len(ms) == 0
|
return len(ms) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnyMatchWithError returns true if req matches any of the
|
||||||
|
// matcher sets in ms or if there are no matchers, in which
|
||||||
|
// case the request always matches. If any matcher returns
|
||||||
|
// an error, we cut short and return the error.
|
||||||
|
func (ms MatcherSets) AnyMatchWithError(req *http.Request) (bool, error) {
|
||||||
|
for _, m := range ms {
|
||||||
|
match, err := m.MatchWithError(req)
|
||||||
|
if err != nil || match {
|
||||||
|
return match, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(ms) == 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
// FromInterface fills ms from an 'any' value obtained from LoadModule.
|
// FromInterface fills ms from an 'any' value obtained from LoadModule.
|
||||||
func (ms *MatcherSets) FromInterface(matcherSets any) error {
|
func (ms *MatcherSets) FromInterface(matcherSets any) error {
|
||||||
for _, matcherSetIfaces := range matcherSets.([]map[string]any) {
|
for _, matcherSetIfaces := range matcherSets.([]map[string]any) {
|
||||||
var matcherSet MatcherSet
|
var matcherSet MatcherSet
|
||||||
for _, matcher := range matcherSetIfaces {
|
for _, matcher := range matcherSetIfaces {
|
||||||
reqMatcher, ok := matcher.(RequestMatcher)
|
if m, ok := matcher.(RequestMatcherWithError); ok {
|
||||||
if !ok {
|
matcherSet = append(matcherSet, m)
|
||||||
return fmt.Errorf("decoded module is not a RequestMatcher: %#v", matcher)
|
continue
|
||||||
}
|
}
|
||||||
matcherSet = append(matcherSet, reqMatcher)
|
if m, ok := matcher.(RequestMatcher); ok {
|
||||||
|
matcherSet = append(matcherSet, m)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("decoded module is not a RequestMatcher or RequestMatcherWithError: %#v", matcher)
|
||||||
}
|
}
|
||||||
*ms = append(*ms, matcherSet)
|
*ms = append(*ms, matcherSet)
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,8 +166,14 @@ func (m *VarsMatcher) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
// Match matches a request based on variables in the context,
|
// Match matches a request based on variables in the context,
|
||||||
// or placeholders if the key is not a variable.
|
// or placeholders if the key is not a variable.
|
||||||
func (m VarsMatcher) Match(r *http.Request) bool {
|
func (m VarsMatcher) Match(r *http.Request) bool {
|
||||||
|
match, _ := m.MatchWithError(r)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (m VarsMatcher) MatchWithError(r *http.Request) (bool, error) {
|
||||||
if len(m) == 0 {
|
if len(m) == 0 {
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := r.Context().Value(VarsCtxKey).(map[string]any)
|
vars := r.Context().Value(VarsCtxKey).(map[string]any)
|
||||||
|
@ -200,11 +206,11 @@ func (m VarsMatcher) Match(r *http.Request) bool {
|
||||||
varStr = fmt.Sprintf("%v", vv)
|
varStr = fmt.Sprintf("%v", vv)
|
||||||
}
|
}
|
||||||
if varStr == matcherValExpanded {
|
if varStr == matcherValExpanded {
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CELLibrary produces options that expose this matcher for use in CEL
|
// CELLibrary produces options that expose this matcher for use in CEL
|
||||||
|
@ -219,7 +225,7 @@ func (VarsMatcher) CELLibrary(_ caddy.Context) (cel.Library, error) {
|
||||||
"vars",
|
"vars",
|
||||||
"vars_matcher_request_map",
|
"vars_matcher_request_map",
|
||||||
[]*cel.Type{CELTypeJSON},
|
[]*cel.Type{CELTypeJSON},
|
||||||
func(data ref.Val) (RequestMatcher, error) {
|
func(data ref.Val) (RequestMatcherWithError, error) {
|
||||||
mapStrListStr, err := CELValueToMapStrList(data)
|
mapStrListStr, err := CELValueToMapStrList(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -294,6 +300,12 @@ func (m MatchVarsRE) Provision(ctx caddy.Context) error {
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchVarsRE) Match(r *http.Request) bool {
|
func (m MatchVarsRE) Match(r *http.Request) bool {
|
||||||
|
match, _ := m.MatchWithError(r)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (m MatchVarsRE) MatchWithError(r *http.Request) (bool, error) {
|
||||||
vars := r.Context().Value(VarsCtxKey).(map[string]any)
|
vars := r.Context().Value(VarsCtxKey).(map[string]any)
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
for key, val := range m {
|
for key, val := range m {
|
||||||
|
@ -322,10 +334,10 @@ func (m MatchVarsRE) Match(r *http.Request) bool {
|
||||||
|
|
||||||
valExpanded := repl.ReplaceAll(varStr, "")
|
valExpanded := repl.ReplaceAll(varStr, "")
|
||||||
if match := val.Match(valExpanded, repl); match {
|
if match := val.Match(valExpanded, repl); match {
|
||||||
return match
|
return match, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CELLibrary produces options that expose this matcher for use in CEL
|
// CELLibrary produces options that expose this matcher for use in CEL
|
||||||
|
@ -340,7 +352,7 @@ func (MatchVarsRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||||
"vars_regexp",
|
"vars_regexp",
|
||||||
"vars_regexp_request_string_string",
|
"vars_regexp_request_string_string",
|
||||||
[]*cel.Type{cel.StringType, cel.StringType},
|
[]*cel.Type{cel.StringType, cel.StringType},
|
||||||
func(data ref.Val) (RequestMatcher, error) {
|
func(data ref.Val) (RequestMatcherWithError, error) {
|
||||||
refStringList := reflect.TypeOf([]string{})
|
refStringList := reflect.TypeOf([]string{})
|
||||||
params, err := data.ConvertToNative(refStringList)
|
params, err := data.ConvertToNative(refStringList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -363,7 +375,7 @@ func (MatchVarsRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||||
"vars_regexp",
|
"vars_regexp",
|
||||||
"vars_regexp_request_string_string_string",
|
"vars_regexp_request_string_string_string",
|
||||||
[]*cel.Type{cel.StringType, cel.StringType, cel.StringType},
|
[]*cel.Type{cel.StringType, cel.StringType, cel.StringType},
|
||||||
func(data ref.Val) (RequestMatcher, error) {
|
func(data ref.Val) (RequestMatcherWithError, error) {
|
||||||
refStringList := reflect.TypeOf([]string{})
|
refStringList := reflect.TypeOf([]string{})
|
||||||
params, err := data.ConvertToNative(refStringList)
|
params, err := data.ConvertToNative(refStringList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -435,8 +447,10 @@ func SetVar(ctx context.Context, key string, value any) {
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ MiddlewareHandler = (*VarsMiddleware)(nil)
|
_ MiddlewareHandler = (*VarsMiddleware)(nil)
|
||||||
_ caddyfile.Unmarshaler = (*VarsMiddleware)(nil)
|
_ caddyfile.Unmarshaler = (*VarsMiddleware)(nil)
|
||||||
_ RequestMatcher = (*VarsMatcher)(nil)
|
_ RequestMatcherWithError = (*VarsMatcher)(nil)
|
||||||
_ caddyfile.Unmarshaler = (*VarsMatcher)(nil)
|
_ caddyfile.Unmarshaler = (*VarsMatcher)(nil)
|
||||||
|
_ RequestMatcherWithError = (*MatchVarsRE)(nil)
|
||||||
|
_ caddyfile.Unmarshaler = (*MatchVarsRE)(nil)
|
||||||
)
|
)
|
||||||
|
|
|
@ -535,21 +535,21 @@ type ClientAuthentication struct {
|
||||||
CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"`
|
CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"`
|
||||||
ca CA
|
ca CA
|
||||||
|
|
||||||
// DEPRECATED: Use the `ca` field with the `tls.ca_pool.source.inline` module instead.
|
// Deprecated: Use the `ca` field with the `tls.ca_pool.source.inline` module instead.
|
||||||
// A list of base64 DER-encoded CA certificates
|
// A list of base64 DER-encoded CA certificates
|
||||||
// against which to validate client certificates.
|
// against which to validate client certificates.
|
||||||
// Client certs which are not signed by any of
|
// Client certs which are not signed by any of
|
||||||
// these CAs will be rejected.
|
// these CAs will be rejected.
|
||||||
TrustedCACerts []string `json:"trusted_ca_certs,omitempty"`
|
TrustedCACerts []string `json:"trusted_ca_certs,omitempty"`
|
||||||
|
|
||||||
// DEPRECATED: Use the `ca` field with the `tls.ca_pool.source.file` module instead.
|
// Deprecated: Use the `ca` field with the `tls.ca_pool.source.file` module instead.
|
||||||
// TrustedCACertPEMFiles is a list of PEM file names
|
// TrustedCACertPEMFiles is a list of PEM file names
|
||||||
// from which to load certificates of trusted CAs.
|
// from which to load certificates of trusted CAs.
|
||||||
// Client certificates which are not signed by any of
|
// Client certificates which are not signed by any of
|
||||||
// these CA certificates will be rejected.
|
// these CA certificates will be rejected.
|
||||||
TrustedCACertPEMFiles []string `json:"trusted_ca_certs_pem_files,omitempty"`
|
TrustedCACertPEMFiles []string `json:"trusted_ca_certs_pem_files,omitempty"`
|
||||||
|
|
||||||
// DEPRECATED: This field is deprecated and will be removed in
|
// Deprecated: This field is deprecated and will be removed in
|
||||||
// a future version. Please use the `validators` field instead
|
// a future version. Please use the `validators` field instead
|
||||||
// with the tls.client_auth.verifier.leaf module instead.
|
// with the tls.client_auth.verifier.leaf module instead.
|
||||||
//
|
//
|
||||||
|
|
|
@ -42,7 +42,7 @@ func init() {
|
||||||
// to your application whether a particular domain is allowed
|
// to your application whether a particular domain is allowed
|
||||||
// to have a certificate issued for it.
|
// to have a certificate issued for it.
|
||||||
type OnDemandConfig struct {
|
type OnDemandConfig struct {
|
||||||
// DEPRECATED. WILL BE REMOVED SOON. Use 'permission' instead with the `http` module.
|
// Deprecated. WILL BE REMOVED SOON. Use 'permission' instead with the `http` module.
|
||||||
Ask string `json:"ask,omitempty"`
|
Ask string `json:"ask,omitempty"`
|
||||||
|
|
||||||
// REQUIRED. A module that will determine whether a
|
// REQUIRED. A module that will determine whether a
|
||||||
|
|
Loading…
Reference in a new issue