Skip to content

Commit 503def1

Browse files
authored
Merge pull request #1167 from alliasgher/fix-envsubst-strict-transforms
envsubst: enforce strict mode for transformation operators
2 parents 2dbd9d3 + 8c54c72 commit 503def1

2 files changed

Lines changed: 42 additions & 1 deletion

File tree

envsubst/eval_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,28 @@ func TestExpandStrict(t *testing.T) {
292292
output: "default",
293293
wantErr: nil,
294294
},
295+
// missing with a transformation operator should still error.
296+
// Regression coverage for fluxcd/flux2#5836.
297+
{params: map[string]string{}, input: "${#missing}", wantErr: errVarNotSet},
298+
{params: map[string]string{}, input: "${missing^}", wantErr: errVarNotSet},
299+
{params: map[string]string{}, input: "${missing^^}", wantErr: errVarNotSet},
300+
{params: map[string]string{}, input: "${missing,}", wantErr: errVarNotSet},
301+
{params: map[string]string{}, input: "${missing,,}", wantErr: errVarNotSet},
302+
{params: map[string]string{}, input: "${missing:0}", wantErr: errVarNotSet},
303+
{params: map[string]string{}, input: "${missing:0:10}", wantErr: errVarNotSet},
304+
{params: map[string]string{}, input: "${missing/pattern/replacement}", wantErr: errVarNotSet},
305+
{params: map[string]string{}, input: "${missing//pattern/replacement}", wantErr: errVarNotSet},
306+
{params: map[string]string{}, input: "${missing/#pattern/replacement}", wantErr: errVarNotSet},
307+
{params: map[string]string{}, input: "${missing/%pattern/replacement}", wantErr: errVarNotSet},
308+
{params: map[string]string{}, input: "${missing#pattern}", wantErr: errVarNotSet},
309+
{params: map[string]string{}, input: "${missing##pattern}", wantErr: errVarNotSet},
310+
{params: map[string]string{}, input: "${missing%pattern}", wantErr: errVarNotSet},
311+
{params: map[string]string{}, input: "${missing%%pattern}", wantErr: errVarNotSet},
312+
// Default-providing operators must still succeed when the var is
313+
// missing — exclusion must not regress these cases.
314+
{params: map[string]string{}, input: "${missing:-fallback}", output: "fallback"},
315+
{params: map[string]string{}, input: "${missing:=assigned}", output: "assigned"},
316+
{params: map[string]string{}, input: "${missing=assigned}", output: "assigned"},
295317
}
296318

297319
for _, expr := range expressions {

envsubst/template.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,12 @@ func (t *Template) evalFunc(s *state, node *parse.FuncNode) error {
131131

132132
v, exists := s.mapper(node.Param)
133133

134-
if node.Name == "" && !exists {
134+
// A variable is missing in strict mode (mapper returns exists=false) when
135+
// it is referenced bare (${var}) or with a transformation operator that
136+
// reads the value (e.g. ${#var}, ${var^^}, ${var/foo/bar}). Default-
137+
// providing operators (-, :-, +, :+, =, :=, ?, :?) intentionally handle
138+
// the unset case, so leave them alone.
139+
if !exists && !isDefaultOp(node.Name) {
135140
return fmt.Errorf("%w: %q", errVarNotSet, node.Param)
136141
}
137142
fn := lookupFunc(node.Name, len(args))
@@ -140,6 +145,20 @@ func (t *Template) evalFunc(s *state, node *parse.FuncNode) error {
140145
return err
141146
}
142147

148+
// isDefaultOp reports whether name is a POSIX-style parameter expansion
149+
// operator that tolerates an unset variable and supplies its own fallback
150+
// value (e.g. ${var:-default}, ${var:+set}, ${var:=assign}, ${var:?err}).
151+
// These operators are excluded from the strict-mode "variable not set"
152+
// check because the unset case is part of their contract.
153+
func isDefaultOp(name string) bool {
154+
switch name {
155+
case "-", "+", "=", "?",
156+
":-", ":+", ":=", ":?":
157+
return true
158+
}
159+
return false
160+
}
161+
143162
// lookupFunc returns the parameters substitution function by name. If the
144163
// named function does not exists, a default function is returned.
145164
func lookupFunc(name string, args int) substituteFunc {

0 commit comments

Comments
 (0)