Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 22 additions & 17 deletions lift.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,16 @@ const (
VarPrefix = "vars"
)

var (
// replace is truly hack city. these are 20 variable names for values that are
// lifted out of expressions via liftLiterals.
replace = []string{
"a", "b", "c", "d", "e",
"f", "g", "h", "i", "j",
"k", "l", "m", "n", "o",
"p", "q", "r", "s", "t",
"u", "v", "w", "x", "y",
"z",
}
)
// replace is truly hack city. these are 20 variable names for values that are
// lifted out of expressions via liftLiterals.
var replace = []string{
"a", "b", "c", "d", "e",
"f", "g", "h", "i", "j",
"k", "l", "m", "n", "o",
"p", "q", "r", "s", "t",
"u", "v", "w", "x", "y",
"z",
}

// LiftedArgs represents a set of variables that have been lifted from expressions and
// replaced with identifiers, eg `id == "foo"` becomes `id == vars.a`, with "foo" lifted
Expand Down Expand Up @@ -304,8 +302,9 @@ func (l *liftParser) consumeString(quoteChar byte) argMapValue {
for l.idx < len(l.expr) {
char := l.expr[l.idx]

if char == '\\' && l.peek() == quoteChar {
// If we're escaping the quote character, ignore it.
if char == '\\' && l.idx+1 < len(l.expr) {
// Escape sequence: skip the backslash and whatever follows it.
// This correctly handles \\, \", \', \n, \t, etc.
l.idx += 2
length += 2
continue
Expand All @@ -325,11 +324,17 @@ func (l *liftParser) consumeString(quoteChar byte) argMapValue {
length++
}

// Should never happen: we should always find the ending string quote, as the
// expression should have already been validated.
panic(fmt.Sprintf("unable to parse quoted string: `%s` (offset %d)", l.expr, offset))
// this is a grossly invalid expr, eg: `event.data.id == "foo\"`
// in this case, we can never parse this string. we always fix this by treating the last backslash
// as a \ literal, innit bruv
if length > 0 && offset+length <= len(l.expr) && l.expr[offset+length-1] == quoteChar {
length--
}

return argMapValue{offset: offset, length: length}
}

// nolint:unused
func (l *liftParser) peek() byte {
if (l.idx + 1) >= len(l.expr) {
return 0x0
Expand Down
25 changes: 25 additions & 0 deletions lift_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,31 @@ func TestLiftLiterals(t *testing.T) {
"a": "test/yolo",
},
},
{
name: "escaped backslash before closing quote",
expr: `event.name == "foo\\"`,
expectedStr: "event.name == vars.a",
expectedArgs: map[string]any{
"a": `foo\\`,
},
},
{
name: "escaped backslash before closing quote in compound expression",
expr: `async.data.branch.playgroundId == "oqdqzbuppgbtrljtpbmyi\\" && "team/mly6i259eym3jkyvq6txyciu/repo/qhq2ioy772sqo9sboe48kfwv" == async.data.spaceID`,
expectedStr: `async.data.branch.playgroundId == vars.a && vars.b == async.data.spaceID`,
expectedArgs: map[string]any{
"a": `oqdqzbuppgbtrljtpbmyi\\`,
"b": "team/mly6i259eym3jkyvq6txyciu/repo/qhq2ioy772sqo9sboe48kfwv",
},
},
{
name: "trailing escaped quote treated as literal backslash",
expr: `event.data.id == "foo\"`,
expectedStr: `event.data.id == vars.a`,
expectedArgs: map[string]any{
"a": `foo\`,
},
},
}

for _, test := range tests {
Expand Down
Loading