diff options
author | Francis Lavoie <[email protected]> | 2024-04-17 14:19:14 -0400 |
---|---|---|
committer | GitHub <[email protected]> | 2024-04-17 12:19:14 -0600 |
commit | 9cd472c0313b01e71d1f142769c3653058d75c86 (patch) | |
tree | 2393ff4a9a2280e35acfacf4109ca99075c26a43 /modules | |
parent | e0daa39cd3373d26dcf78ae0d30ac24d1df5dd57 (diff) | |
download | caddy-9cd472c0313b01e71d1f142769c3653058d75c86.tar.gz caddy-9cd472c0313b01e71d1f142769c3653058d75c86.zip |
caddyfile: Populate regexp matcher names by default (#6145)
* caddyfile: Populate regexp matcher names by default
* Some lint cleanup that my VSCode complained about
* Pass down matcher name through expression matcher
* Compat with #6113: fix adapt test, set both styles in replacer
Diffstat (limited to 'modules')
-rw-r--r-- | modules/caddyhttp/celmatcher.go | 47 | ||||
-rw-r--r-- | modules/caddyhttp/celmatcher_test.go | 8 | ||||
-rw-r--r-- | modules/caddyhttp/fileserver/matcher_test.go | 4 | ||||
-rw-r--r-- | modules/caddyhttp/matchers.go | 63 | ||||
-rw-r--r-- | modules/caddyhttp/routes.go | 2 | ||||
-rw-r--r-- | modules/caddyhttp/vars.go | 5 |
6 files changed, 108 insertions, 21 deletions
diff --git a/modules/caddyhttp/celmatcher.go b/modules/caddyhttp/celmatcher.go index e1f15e869..d4016478e 100644 --- a/modules/caddyhttp/celmatcher.go +++ b/modules/caddyhttp/celmatcher.go @@ -62,7 +62,12 @@ type MatchExpression struct { // The CEL expression to evaluate. Any Caddy placeholders // will be expanded and situated into proper CEL function // calls before evaluating. - Expr string + Expr string `json:"expr,omitempty"` + + // Name is an optional name for this matcher. + // This is used to populate the name for regexp + // matchers that appear in the expression. + Name string `json:"name,omitempty"` expandedExpr string prg cel.Program @@ -81,12 +86,36 @@ func (MatchExpression) CaddyModule() caddy.ModuleInfo { // MarshalJSON marshals m's expression. func (m MatchExpression) MarshalJSON() ([]byte, error) { - return json.Marshal(m.Expr) + // if the name is empty, then we can marshal just the expression string + if m.Name == "" { + return json.Marshal(m.Expr) + } + // otherwise, we need to marshal the full object, using an + // anonymous struct to avoid infinite recursion + return json.Marshal(struct { + Expr string `json:"expr"` + Name string `json:"name"` + }{ + Expr: m.Expr, + Name: m.Name, + }) } // UnmarshalJSON unmarshals m's expression. func (m *MatchExpression) UnmarshalJSON(data []byte) error { - return json.Unmarshal(data, &m.Expr) + // if the data is a string, then it's just the expression + if data[0] == '"' { + return json.Unmarshal(data, &m.Expr) + } + // otherwise, it's a full object, so unmarshal it, + // using an temp map to avoid infinite recursion + var tmpJson map[string]any + err := json.Unmarshal(data, &tmpJson) + *m = MatchExpression{ + Expr: tmpJson["expr"].(string), + Name: tmpJson["name"].(string), + } + return err } // Provision sets ups m. @@ -109,6 +138,11 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error { matcherLibProducers = append(matcherLibProducers, p) } } + + // add the matcher name to the context so that the matcher name + // can be used by regexp matchers being provisioned + ctx = ctx.WithValue(MatcherNameCtxKey, m.Name) + // Assemble the compilation and program options from the different library // producers into a single cel.Library implementation. matcherEnvOpts := []cel.EnvOption{} @@ -197,6 +231,11 @@ func (m *MatchExpression) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { // quoted string; commonly quotes are used in Caddyfile to // define the expression m.Expr = d.Val() + + // use the named matcher's name, to fill regexp + // matchers names by default + m.Name = d.GetContextString(caddyfile.MatcherNameCtxKey) + return nil } @@ -673,6 +712,8 @@ var httpRequestObjectType = cel.ObjectType("http.Request") // The name of the CEL function which accesses Replacer values. const placeholderFuncName = "caddyPlaceholder" +const MatcherNameCtxKey = "matcher_name" + // Interface guards var ( _ caddy.Provisioner = (*MatchExpression)(nil) diff --git a/modules/caddyhttp/celmatcher_test.go b/modules/caddyhttp/celmatcher_test.go index e67b87c77..25fdcf45e 100644 --- a/modules/caddyhttp/celmatcher_test.go +++ b/modules/caddyhttp/celmatcher_test.go @@ -380,7 +380,9 @@ func TestMatchExpressionMatch(t *testing.T) { for _, tst := range matcherTests { tc := tst t.Run(tc.name, func(t *testing.T) { - err := tc.expression.Provision(caddy.Context{}) + caddyCtx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + err := tc.expression.Provision(caddyCtx) if err != nil { if !tc.wantErr { t.Errorf("MatchExpression.Provision() error = %v, wantErr %v", err, tc.wantErr) @@ -482,7 +484,9 @@ func TestMatchExpressionProvision(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := tt.expression.Provision(caddy.Context{}); (err != nil) != tt.wantErr { + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + if err := tt.expression.Provision(ctx); (err != nil) != tt.wantErr { t.Errorf("MatchExpression.Provision() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/modules/caddyhttp/fileserver/matcher_test.go b/modules/caddyhttp/fileserver/matcher_test.go index 5ffb63b6a..527c16bd1 100644 --- a/modules/caddyhttp/fileserver/matcher_test.go +++ b/modules/caddyhttp/fileserver/matcher_test.go @@ -360,7 +360,9 @@ func TestMatchExpressionMatch(t *testing.T) { for _, tst := range expressionTests { tc := tst t.Run(tc.name, func(t *testing.T) { - err := tc.expression.Provision(caddy.Context{}) + caddyCtx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + err := tc.expression.Provision(caddyCtx) if err != nil { if !tc.wantErr { t.Errorf("MatchExpression.Provision() error = %v, wantErr %v", err, tc.wantErr) diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go index 6e770c5e6..b1da14686 100644 --- a/modules/caddyhttp/matchers.go +++ b/modules/caddyhttp/matchers.go @@ -675,7 +675,10 @@ func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) { []*cel.Type{cel.StringType}, func(data ref.Val) (RequestMatcher, error) { pattern := data.(types.String) - matcher := MatchPathRE{MatchRegexp{Pattern: string(pattern)}} + matcher := MatchPathRE{MatchRegexp{ + Name: ctx.Value(MatcherNameCtxKey).(string), + Pattern: string(pattern), + }} err := matcher.Provision(ctx) return matcher, err }, @@ -694,7 +697,14 @@ func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) { return nil, err } strParams := params.([]string) - matcher := MatchPathRE{MatchRegexp{Name: strParams[0], Pattern: strParams[1]}} + name := strParams[0] + if name == "" { + name = ctx.Value(MatcherNameCtxKey).(string) + } + matcher := MatchPathRE{MatchRegexp{ + Name: name, + Pattern: strParams[1], + }} err = matcher.Provision(ctx) return matcher, err }, @@ -1023,6 +1033,11 @@ func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { val = second } + // Default to the named matcher's name, if no regexp name is provided + if name == "" { + name = d.GetContextString(caddyfile.MatcherNameCtxKey) + } + // If there's already a pattern for this field // then we would end up overwriting the old one if (*m)[field] != nil { @@ -1099,7 +1114,10 @@ func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) { } strParams := params.([]string) matcher := MatchHeaderRE{} - matcher[strParams[0]] = &MatchRegexp{Pattern: strParams[1], Name: ""} + matcher[strParams[0]] = &MatchRegexp{ + Pattern: strParams[1], + Name: ctx.Value(MatcherNameCtxKey).(string), + } err = matcher.Provision(ctx) return matcher, err }, @@ -1118,8 +1136,15 @@ func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) { return nil, err } strParams := params.([]string) + name := strParams[0] + if name == "" { + name = ctx.Value(MatcherNameCtxKey).(string) + } matcher := MatchHeaderRE{} - matcher[strParams[1]] = &MatchRegexp{Pattern: strParams[2], Name: strParams[0]} + matcher[strParams[1]] = &MatchRegexp{ + Pattern: strParams[2], + Name: name, + } err = matcher.Provision(ctx) return matcher, err }, @@ -1284,7 +1309,6 @@ type MatchRegexp struct { Pattern string `json:"pattern"` compiled *regexp.Regexp - phPrefix string } // Provision compiles the regular expression. @@ -1294,10 +1318,6 @@ func (mre *MatchRegexp) Provision(caddy.Context) error { return fmt.Errorf("compiling matcher regexp %s: %v", mre.Pattern, err) } mre.compiled = re - mre.phPrefix = regexpPlaceholderPrefix - if mre.Name != "" { - mre.phPrefix += "." + mre.Name - } return nil } @@ -1321,16 +1341,25 @@ func (mre *MatchRegexp) Match(input string, repl *caddy.Replacer) bool { // save all capture groups, first by index for i, match := range matches { - key := mre.phPrefix + "." + strconv.Itoa(i) - repl.Set(key, match) + keySuffix := "." + strconv.Itoa(i) + if mre.Name != "" { + repl.Set(regexpPlaceholderPrefix+"."+mre.Name+keySuffix, match) + } + repl.Set(regexpPlaceholderPrefix+keySuffix, match) } // then by name for i, name := range mre.compiled.SubexpNames() { - if i != 0 && name != "" { - key := mre.phPrefix + "." + name - repl.Set(key, matches[i]) + // skip the first element (the full match), and empty names + if i == 0 || name == "" { + continue + } + + keySuffix := "." + name + if mre.Name != "" { + repl.Set(regexpPlaceholderPrefix+"."+mre.Name+keySuffix, matches[i]) } + repl.Set(regexpPlaceholderPrefix+keySuffix, matches[i]) } return true @@ -1357,6 +1386,12 @@ func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { default: return d.ArgErr() } + + // Default to the named matcher's name, if no regexp name is provided + if mre.Name == "" { + mre.Name = d.GetContextString(caddyfile.MatcherNameCtxKey) + } + if d.NextBlock(0) { return d.Err("malformed path_regexp matcher: blocks are not supported") } diff --git a/modules/caddyhttp/routes.go b/modules/caddyhttp/routes.go index 9be3d01a4..9db4f2520 100644 --- a/modules/caddyhttp/routes.go +++ b/modules/caddyhttp/routes.go @@ -311,7 +311,7 @@ func wrapRoute(route Route) Middleware { // we need to pull this particular MiddlewareHandler // pointer into its own stack frame to preserve it so it // won't be overwritten in future loop iterations. -func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler, metrics *Metrics) Middleware { +func wrapMiddleware(_ caddy.Context, mh MiddlewareHandler, metrics *Metrics) Middleware { handlerToUse := mh if metrics != nil { // wrap the middleware with metrics instrumentation diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go index 9908ada9b..9e86dd716 100644 --- a/modules/caddyhttp/vars.go +++ b/modules/caddyhttp/vars.go @@ -242,6 +242,11 @@ func (m *MatchVarsRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { val = second } + // Default to the named matcher's name, if no regexp name is provided + if name == "" { + name = d.GetContextString(caddyfile.MatcherNameCtxKey) + } + (*m)[field] = &MatchRegexp{Pattern: val, Name: name} if d.NextBlock(0) { return d.Err("malformed vars_regexp matcher: blocks are not supported") |