diff --git a/lift.go b/lift.go index 3942a88..2907d86 100644 --- a/lift.go +++ b/lift.go @@ -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 @@ -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 @@ -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 diff --git a/lift_test.go b/lift_test.go index b6abfea..79d4f95 100644 --- a/lift_test.go +++ b/lift_test.go @@ -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 {