aboutsummaryrefslogtreecommitdiffhomepage
path: root/modules/caddyhttp/celmatcher.go
diff options
context:
space:
mode:
authorFrancis Lavoie <[email protected]>2024-10-02 08:34:04 -0400
committerGitHub <[email protected]>2024-10-02 06:34:04 -0600
commit792f1c7ed759b97ee6dc80246cf2de054c09a12f (patch)
treebe3533923d2ba7dfb5a3fde75d440e1aa7463b88 /modules/caddyhttp/celmatcher.go
parentc8adb1b553412253d5f166065635ab809d3eef33 (diff)
downloadcaddy-792f1c7ed759b97ee6dc80246cf2de054c09a12f.tar.gz
caddy-792f1c7ed759b97ee6dc80246cf2de054c09a12f.zip
caddyhttp: Escaping placeholders in CEL, add `vars` and `vars_regexp` (#6594)
* caddyhttp: Escaping placeholders in CEL * Simplify some of the test cases * Implement vars and vars_regexp in CEL * dupl lint is dumb * Better consts for the placeholder CEL shortcut * Bump CEL version, register a few extensions * Refactor s390x test script for readability * Add retries for s390x to smooth over flakiness * Switch to `ph` for the CEL shortcut (match it in templates cause why not)
Diffstat (limited to 'modules/caddyhttp/celmatcher.go')
-rw-r--r--modules/caddyhttp/celmatcher.go51
1 files changed, 34 insertions, 17 deletions
diff --git a/modules/caddyhttp/celmatcher.go b/modules/caddyhttp/celmatcher.go
index a5565eb98..2a03ebba7 100644
--- a/modules/caddyhttp/celmatcher.go
+++ b/modules/caddyhttp/celmatcher.go
@@ -126,6 +126,10 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error {
// light (and possibly naïve) syntactic sugar
m.expandedExpr = placeholderRegexp.ReplaceAllString(m.Expr, placeholderExpansion)
+ // as a second pass, we'll strip the escape character from an escaped
+ // placeholder, so that it can be used as an input to other CEL functions
+ m.expandedExpr = escapedPlaceholderRegexp.ReplaceAllString(m.expandedExpr, escapedPlaceholderExpansion)
+
// our type adapter expands CEL's standard type support
m.ta = celTypeAdapter{}
@@ -159,14 +163,17 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error {
// create the CEL environment
env, err := cel.NewEnv(
- cel.Function(placeholderFuncName, cel.SingletonBinaryBinding(m.caddyPlaceholderFunc), cel.Overload(
- placeholderFuncName+"_httpRequest_string",
+ cel.Function(CELPlaceholderFuncName, cel.SingletonBinaryBinding(m.caddyPlaceholderFunc), cel.Overload(
+ CELPlaceholderFuncName+"_httpRequest_string",
[]*cel.Type{httpRequestObjectType, cel.StringType},
cel.AnyType,
)),
- cel.Variable("request", httpRequestObjectType),
+ cel.Variable(CELRequestVarName, httpRequestObjectType),
cel.CustomTypeAdapter(m.ta),
ext.Strings(),
+ ext.Bindings(),
+ ext.Lists(),
+ ext.Math(),
matcherLib,
)
if err != nil {
@@ -247,7 +254,7 @@ func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
return types.NewErr(
"invalid request of type '%v' to %s(request, placeholderVarName)",
lhs.Type(),
- placeholderFuncName,
+ CELPlaceholderFuncName,
)
}
phStr, ok := rhs.(types.String)
@@ -255,7 +262,7 @@ func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
return types.NewErr(
"invalid placeholder variable name of type '%v' to %s(request, placeholderVarName)",
rhs.Type(),
- placeholderFuncName,
+ CELPlaceholderFuncName,
)
}
@@ -275,7 +282,7 @@ var httpRequestCELType = cel.ObjectType("http.Request", traits.ReceiverType)
type celHTTPRequest struct{ *http.Request }
func (cr celHTTPRequest) ResolveName(name string) (any, bool) {
- if name == "request" {
+ if name == CELRequestVarName {
return cr, true
}
return nil, false
@@ -457,15 +464,15 @@ func CELMatcherDecorator(funcName string, fac CELMatcherFactory) interpreter.Int
callArgs := call.Args()
reqAttr, ok := callArgs[0].(interpreter.InterpretableAttribute)
if !ok {
- return nil, errors.New("missing 'request' argument")
+ return nil, errors.New("missing 'req' argument")
}
nsAttr, ok := reqAttr.Attr().(interpreter.NamespacedAttribute)
if !ok {
- return nil, errors.New("missing 'request' argument")
+ return nil, errors.New("missing 'req' argument")
}
varNames := nsAttr.CandidateVariableNames()
- if len(varNames) != 1 || len(varNames) == 1 && varNames[0] != "request" {
- return nil, errors.New("missing 'request' argument")
+ if len(varNames) != 1 || len(varNames) == 1 && varNames[0] != CELRequestVarName {
+ return nil, errors.New("missing 'req' argument")
}
matcherData, ok := callArgs[1].(interpreter.InterpretableConst)
if !ok {
@@ -524,7 +531,7 @@ func celMatcherStringListMacroExpander(funcName string) cel.MacroFactory {
return nil, eh.NewError(arg.ID(), "matcher arguments must be string constants")
}
}
- return eh.NewCall(funcName, eh.NewIdent("request"), eh.NewList(matchArgs...)), nil
+ return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), eh.NewList(matchArgs...)), nil
}
}
@@ -538,7 +545,7 @@ func celMatcherStringMacroExpander(funcName string) parser.MacroExpander {
return nil, eh.NewError(0, "matcher requires one argument")
}
if isCELStringExpr(args[0]) {
- return eh.NewCall(funcName, eh.NewIdent("request"), args[0]), nil
+ return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), args[0]), nil
}
return nil, eh.NewError(args[0].ID(), "matcher argument must be a string literal")
}
@@ -572,7 +579,7 @@ func celMatcherJSONMacroExpander(funcName string) parser.MacroExpander {
return nil, eh.NewError(entry.AsMapEntry().Value().ID(), "matcher map values must be string or list literals")
}
}
- return eh.NewCall(funcName, eh.NewIdent("request"), arg), nil
+ return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), arg), nil
case ast.UnspecifiedExprKind, ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.SelectKind:
// appeasing the linter :)
}
@@ -646,7 +653,7 @@ func isCELCaddyPlaceholderCall(e ast.Expr) bool {
switch e.Kind() {
case ast.CallKind:
call := e.AsCall()
- if call.FunctionName() == "caddyPlaceholder" {
+ if call.FunctionName() == CELPlaceholderFuncName {
return true
}
case ast.UnspecifiedExprKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind:
@@ -701,8 +708,15 @@ func isCELStringListLiteral(e ast.Expr) bool {
// expressions with a proper CEL function call; this is
// just for syntactic sugar.
var (
- placeholderRegexp = regexp.MustCompile(`{([a-zA-Z][\w.-]+)}`)
- placeholderExpansion = `caddyPlaceholder(request, "${1}")`
+ // The placeholder may not be preceded by a backslash; the expansion
+ // will include the preceding character if it is not a backslash.
+ placeholderRegexp = regexp.MustCompile(`([^\\]|^){([a-zA-Z][\w.-]+)}`)
+ placeholderExpansion = `${1}ph(req, "${2}")`
+
+ // As a second pass, we need to strip the escape character in front of
+ // the placeholder, if it exists.
+ escapedPlaceholderRegexp = regexp.MustCompile(`\\{([a-zA-Z][\w.-]+)}`)
+ escapedPlaceholderExpansion = `{${1}}`
CELTypeJSON = cel.MapType(cel.StringType, cel.DynType)
)
@@ -710,7 +724,10 @@ var (
var httpRequestObjectType = cel.ObjectType("http.Request")
// The name of the CEL function which accesses Replacer values.
-const placeholderFuncName = "caddyPlaceholder"
+const CELPlaceholderFuncName = "ph"
+
+// The name of the CEL request variable.
+const CELRequestVarName = "req"
const MatcherNameCtxKey = "matcher_name"