Skip to content

Commit

Permalink
caddyhttp: Add MatchWithError to replace SetVar hack
Browse files Browse the repository at this point in the history
  • Loading branch information
francislavoie committed Oct 11, 2024
1 parent 48ce47f commit 54a305c
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 58 deletions.
12 changes: 12 additions & 0 deletions modules/caddyhttp/caddyhttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ type RequestMatcher interface {
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.
//
// If any handler encounters an error, it should be returned for proper
Expand Down
41 changes: 32 additions & 9 deletions modules/caddyhttp/celmatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,17 +202,25 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error {

// Match returns true if r matches m.
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}
out, _, err := m.prg.Eval(celReq)
if err != nil {
m.log.Error("evaluating expression", zap.Error(err))
SetVar(r.Context(), MatcherErrorVarKey, err)
return false
return false, err
}
if outBool, ok := out.Value().(bool); ok {
return outBool
return outBool, nil
}
return false
return false, nil
}

// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
Expand Down Expand Up @@ -494,6 +502,13 @@ func CELMatcherDecorator(funcName string, fac CELMatcherFactory) interpreter.Int
// 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
Expand All @@ -509,6 +524,13 @@ func CELMatcherRuntimeFunction(funcName string, fac CELMatcherFactory) functions
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))
}
}
Expand Down Expand Up @@ -733,9 +755,10 @@ const MatcherNameCtxKey = "matcher_name"

// Interface guards
var (
_ caddy.Provisioner = (*MatchExpression)(nil)
_ RequestMatcher = (*MatchExpression)(nil)
_ caddyfile.Unmarshaler = (*MatchExpression)(nil)
_ json.Marshaler = (*MatchExpression)(nil)
_ json.Unmarshaler = (*MatchExpression)(nil)
_ caddy.Provisioner = (*MatchExpression)(nil)
_ RequestMatcher = (*MatchExpression)(nil)
_ RequestMatcherWithError = (*MatchExpression)(nil)
_ caddyfile.Unmarshaler = (*MatchExpression)(nil)
_ json.Marshaler = (*MatchExpression)(nil)
_ json.Unmarshaler = (*MatchExpression)(nil)
)
32 changes: 20 additions & 12 deletions modules/caddyhttp/fileserver/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,12 +313,21 @@ func (m MatchFile) Validate() error {
// - http.matchers.file.type: file or directory
// - http.matchers.file.remainder: Portion remaining after splitting file path (if configured)
func (m MatchFile) Match(r *http.Request) bool {
match, err := m.selectFile(r)
if err != nil {
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)
}

// selectFile chooses a file according to m.TryPolicy by appending
// 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)

root := filepath.Clean(repl.ReplaceAll(m.Root, "."))
Expand All @@ -330,7 +339,7 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
if c := m.logger.Check(zapcore.ErrorLevel, "use of unregistered filesystem"); c != nil {
c.Write(zap.String("fs", fsName))
}
return false
return false, nil
}
type matchCandidate struct {
fullpath, relative, splitRemainder string
Expand Down Expand Up @@ -422,14 +431,13 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
case "", tryPolicyFirstExist:
for _, pattern := range m.TryFiles {
if err := parseErrorCode(pattern); err != nil {
caddyhttp.SetVar(r.Context(), caddyhttp.MatcherErrorVarKey, err)
return
return false, err
}
candidates := makeCandidates(pattern)
for _, c := range candidates {
if info, exists := m.strictFileExists(fileSystem, c.fullpath); exists {
setPlaceholders(c, info)
return true
return true, nil
}
}
}
Expand All @@ -450,10 +458,10 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
}
}
if largestInfo == nil {
return false
return false, nil
}
setPlaceholders(largest, largestInfo)
return true
return true, nil

case tryPolicySmallestSize:
var smallestSize int64
Expand All @@ -471,10 +479,10 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
}
}
if smallestInfo == nil {
return false
return false, nil
}
setPlaceholders(smallest, smallestInfo)
return true
return true, nil

case tryPolicyMostRecentlyMod:
var recent matchCandidate
Expand All @@ -491,13 +499,13 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
}
}
if recentInfo == nil {
return false
return false, nil
}
setPlaceholders(recent, recentInfo)
return true
return true, nil
}

return
return false, nil
}

// parseErrorCode checks if the input is a status
Expand Down
30 changes: 21 additions & 9 deletions modules/caddyhttp/ip_matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ func (m MatchRemoteIP) Match(r *http.Request) bool {
return matches
}

// MatchWithError returns true if r matches m.
func (m MatchRemoteIP) MatchWithError(r *http.Request) (bool, error) {
return m.Match(r), nil
}

// CaddyModule returns the Caddy module information.
func (MatchClientIP) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Expand Down Expand Up @@ -254,6 +259,11 @@ func (m MatchClientIP) Match(r *http.Request) bool {
return matches
}

// MatchWithError returns true if r matches m.
func (m MatchClientIP) MatchWithError(r *http.Request) (bool, error) {
return m.Match(r), nil
}

func provisionCidrsZonesFromRanges(ranges []string) ([]*netip.Prefix, []string, error) {
cidrs := []*netip.Prefix{}
zones := []string{}
Expand Down Expand Up @@ -326,13 +336,15 @@ func matchIPByCidrZones(clientIP netip.Addr, zoneID string, cidrs []*netip.Prefi

// Interface guards
var (
_ RequestMatcher = (*MatchRemoteIP)(nil)
_ caddy.Provisioner = (*MatchRemoteIP)(nil)
_ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil)
_ CELLibraryProducer = (*MatchRemoteIP)(nil)

_ RequestMatcher = (*MatchClientIP)(nil)
_ caddy.Provisioner = (*MatchClientIP)(nil)
_ caddyfile.Unmarshaler = (*MatchClientIP)(nil)
_ CELLibraryProducer = (*MatchClientIP)(nil)
_ RequestMatcher = (*MatchRemoteIP)(nil)
_ RequestMatcherWithError = (*MatchRemoteIP)(nil)
_ caddy.Provisioner = (*MatchRemoteIP)(nil)
_ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil)
_ CELLibraryProducer = (*MatchRemoteIP)(nil)

_ RequestMatcher = (*MatchClientIP)(nil)
_ RequestMatcherWithError = (*MatchClientIP)(nil)
_ caddy.Provisioner = (*MatchClientIP)(nil)
_ caddyfile.Unmarshaler = (*MatchClientIP)(nil)
_ CELLibraryProducer = (*MatchClientIP)(nil)
)
87 changes: 73 additions & 14 deletions modules/caddyhttp/matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,11 @@ outer:
return false
}

// MatchWithError returns true if r matches m.
func (m MatchHost) MatchWithError(r *http.Request) (bool, error) {
return m.Match(r), nil
}

// CELLibrary produces options that expose this matcher for use in CEL
// expression matchers.
//
Expand Down Expand Up @@ -627,6 +632,11 @@ func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath string) b
return matches
}

// MatchWithError returns true if r matches m.
func (m MatchPath) MatchWithError(r *http.Request) (bool, error) {
return m.Match(r), nil
}

// CELLibrary produces options that expose this matcher for use in CEL
// expression matchers.
//
Expand Down Expand Up @@ -687,6 +697,11 @@ func (m MatchPathRE) Match(r *http.Request) bool {
return m.MatchRegexp.Match(cleanedPath, repl)
}

// MatchWithError returns true if r matches m.
func (m MatchPathRE) MatchWithError(r *http.Request) (bool, error) {
return m.Match(r), nil
}

// CELLibrary produces options that expose this matcher for use in CEL
// expression matchers.
//
Expand Down Expand Up @@ -767,6 +782,11 @@ func (m MatchMethod) Match(r *http.Request) bool {
return slices.Contains(m, r.Method)
}

// MatchWithError returns true if r matches m.
func (m MatchMethod) MatchWithError(r *http.Request) (bool, error) {
return m.Match(r), nil
}

// CELLibrary produces options that expose this matcher for use in CEL
// expression matchers.
//
Expand Down Expand Up @@ -867,6 +887,11 @@ func (m MatchQuery) Match(r *http.Request) bool {
return matchedKeys == len(m)
}

// MatchWithError returns true if r matches m.
func (m MatchQuery) MatchWithError(r *http.Request) (bool, error) {
return m.Match(r), nil
}

// CELLibrary produces options that expose this matcher for use in CEL
// expression matchers.
//
Expand Down Expand Up @@ -944,6 +969,11 @@ func (m MatchHeader) Match(r *http.Request) bool {
return matchHeaders(r.Header, http.Header(m), r.Host, repl)
}

// MatchWithError returns true if r matches m.
func (m MatchHeader) MatchWithError(r *http.Request) (bool, error) {
return m.Match(r), nil
}

// CELLibrary produces options that expose this matcher for use in CEL
// expression matchers.
//
Expand Down Expand Up @@ -1093,6 +1123,11 @@ func (m MatchHeaderRE) Match(r *http.Request) bool {
return true
}

// MatchWithError returns true if r matches m.
func (m MatchHeaderRE) MatchWithError(r *http.Request) (bool, error) {
return m.Match(r), nil
}

// Provision compiles m's regular expressions.
func (m MatchHeaderRE) Provision(ctx caddy.Context) error {
for _, rm := range m {
Expand Down Expand Up @@ -1214,6 +1249,11 @@ func (m MatchProtocol) Match(r *http.Request) bool {
return false
}

// MatchWithError returns true if r matches m.
func (m MatchProtocol) MatchWithError(r *http.Request) (bool, error) {
return m.Match(r), nil
}

// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// iterate to merge multiple matchers into one
Expand Down Expand Up @@ -1270,6 +1310,11 @@ func (m MatchTLS) Match(r *http.Request) bool {
return true
}

// MatchWithError returns true if r matches m.
func (m MatchTLS) MatchWithError(r *http.Request) (bool, error) {
return m.Match(r), nil
}

// UnmarshalCaddyfile parses Caddyfile tokens for this matcher. Syntax:
//
// ... tls [early_data]
Expand Down Expand Up @@ -1356,6 +1401,11 @@ func (m MatchNot) Match(r *http.Request) bool {
return true
}

// MatchWithError returns true if r matches m.
func (m MatchNot) MatchWithError(r *http.Request) (bool, error) {
return m.Match(r), nil
}

// MatchRegexp is an embedable type for matching
// using regular expressions. It adds placeholders
// to the request's replacer.
Expand Down Expand Up @@ -1528,20 +1578,29 @@ 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 = (*MatchNot)(nil)
_ caddy.Provisioner = (*MatchNot)(nil)
_ caddy.Provisioner = (*MatchRegexp)(nil)
_ RequestMatcher = (*MatchHost)(nil)
_ RequestMatcherWithError = (*MatchHost)(nil)
_ caddy.Provisioner = (*MatchHost)(nil)
_ RequestMatcher = (*MatchPath)(nil)
_ RequestMatcherWithError = (*MatchPath)(nil)
_ RequestMatcher = (*MatchPathRE)(nil)
_ RequestMatcherWithError = (*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)
_ RequestMatcher = (*MatchProtocol)(nil)
_ RequestMatcherWithError = (*MatchProtocol)(nil)
_ RequestMatcher = (*MatchNot)(nil)
_ RequestMatcherWithError = (*MatchNot)(nil)
_ caddy.Provisioner = (*MatchNot)(nil)
_ caddy.Provisioner = (*MatchRegexp)(nil)

_ caddyfile.Unmarshaler = (*MatchHost)(nil)
_ caddyfile.Unmarshaler = (*MatchPath)(nil)
Expand Down
Loading

0 comments on commit 54a305c

Please sign in to comment.