From 309ad2b0f64c29cb964f50269753e0393fee21e2 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 13 Sep 2024 19:38:29 +0100 Subject: [PATCH] Expression STRICT_ESCAPES mode --- .../common/expression/Expression.java | 43 +++++++++ .../common/expression/ExpressionTestCase.java | 94 +++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/expression/src/main/java/io/smallrye/common/expression/Expression.java b/expression/src/main/java/io/smallrye/common/expression/Expression.java index 555a04d3..a42554c5 100644 --- a/expression/src/main/java/io/smallrye/common/expression/Expression.java +++ b/expression/src/main/java/io/smallrye/common/expression/Expression.java @@ -195,6 +195,10 @@ static final class Itr { this.str = str; } + boolean hasPrev() { + return idx > 0; + } + boolean hasNext() { return idx < str.length(); } @@ -258,6 +262,25 @@ private static Node parseString(Itr itr, final boolean allowExpr, final boolean // treat as plain content continue; } + if (flags.contains(Flag.STRICT_ESCAPES)) { + if (!itr.hasNext()) { + continue; + } else if (itr.peekNext() == '$') { + list.add(new LiteralNode(itr.getStr(), start, itr.getNextIdx())); + start = itr.getNextIdx(); + continue; + } else if (itr.peekNext() != '{') { + continue; + } else if (itr.hasPrev()) { + itr.prev(); + if (itr.hasPrev() && itr.peekPrev() == '$') { + itr.next(); + start = itr.getNextIdx(); + continue; + } + itr.next(); + } + } // check to see if it's a dangling $ if (!itr.hasNext()) { if (!flags.contains(Flag.LENIENT_SYNTAX)) { @@ -614,6 +637,21 @@ private static Node parseString(Itr itr, final boolean allowExpr, final boolean start = itr.getNextIdx(); continue; } + } else if (flags.contains(Flag.STRICT_ESCAPES)) { + int escape = itr.getNextIdx(); + if (itr.hasNext() && itr.peekNext() == '$') { + itr.next(); + if (itr.hasNext() && itr.peekNext() == '{') { + list.add(new LiteralNode(itr.getStr(), start, escape - 1)); + list.add(new LiteralNode(itr.getStr(), escape, itr.getNextIdx())); + start = itr.getNextIdx(); + } else if (itr.hasNext() && itr.peekNext() == '$') { + list.add(new LiteralNode(itr.getStr(), start, escape)); + start = itr.getPrevIdx(); + itr.prev(); + } + } + continue; } // TP 42 // otherwise, just... @@ -694,6 +732,11 @@ public enum Flag { * character. */ ESCAPES, + /** + * Escaping $ with $$ or /$ only applies when { follows + * the initial escaped $. + */ + STRICT_ESCAPES, /** * Treat expressions containing a double-colon delimiter as special, encoding the entire content into the key. */ diff --git a/expression/src/test/java/io/smallrye/common/expression/ExpressionTestCase.java b/expression/src/test/java/io/smallrye/common/expression/ExpressionTestCase.java index 53046ca1..0a320380 100644 --- a/expression/src/test/java/io/smallrye/common/expression/ExpressionTestCase.java +++ b/expression/src/test/java/io/smallrye/common/expression/ExpressionTestCase.java @@ -693,4 +693,98 @@ void expressions() { b.append(c.getExpandedDefault()); })); } + + @Test + void strictEscapes() { + assertEquals("$", Expression.compile("$", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("$$", Expression.compile("$$", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("\\$", Expression.compile("\\$", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("\\$$", Expression.compile("\\$$", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("$$foo", Expression.compile("$$foo", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("foo$$", Expression.compile("foo$$", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("$$foo", Expression.compile("$$foo", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("foo$$bar", Expression.compile("foo$$bar", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("${foo}", Expression.compile("$${foo}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("$${foo}", Expression.compile("$$${foo}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("$${foo}$", Expression.compile("$$${foo}$", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("$${foo}$$", Expression.compile("$$${foo}$$", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("foo${bar}", Expression.compile("foo$${bar}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("foo$${bar}", Expression.compile("foo$$${bar}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("foo$$$${bar}", Expression.compile("foo$$$$${bar}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("foo$$$${bar}$$$baz", Expression.compile("foo$$$$${bar}$$$baz", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("foo$$$$", Expression.compile("foo$$$$", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("${foo:bar}", Expression.compile("$${foo:bar}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("$${foo:bar}", Expression.compile("$$${foo:bar}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("${foo:}", Expression.compile("$${foo:${bar}}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("${foo:${bar}}", Expression.compile("$${foo:$${bar}}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + + assertEquals("", Expression.compile("${foo}", STRICT_ESCAPES).evaluate((c, b) -> { + assertEquals("foo", c.getKey()); + })); + assertEquals("", Expression.compile("${foo}${bar}", STRICT_ESCAPES).evaluate((c, b) -> { + if ("foo".equals(c.getKey())) + assertEquals("foo", c.getKey()); + if ("bar".equals(c.getKey())) + assertEquals("bar", c.getKey()); + })); + assertEquals("foobar", Expression.compile("foo${foo}${bar}bar", STRICT_ESCAPES).evaluate((c, b) -> { + if ("foo".equals(c.getKey())) + assertEquals("foo", c.getKey()); + if ("bar".equals(c.getKey())) + assertEquals("bar", c.getKey()); + })); + assertEquals("foo${foo}bar", Expression.compile("foo$${foo}${bar}bar", STRICT_ESCAPES).evaluate((c, b) -> { + if ("bar".equals(c.getKey())) + assertEquals("bar", c.getKey()); + })); + assertEquals("foo${foo}bar", Expression.compile("foo$${foo${bar}}bar", STRICT_ESCAPES).evaluate((c, b) -> { + if ("bar".equals(c.getKey())) + assertEquals("bar", c.getKey()); + })); + assertEquals("", Expression.compile("${}", STRICT_ESCAPES).evaluate((c, b) -> { + assertEquals("", c.getKey()); + })); + assertEquals("", Expression.compile("${:}", STRICT_ESCAPES).evaluate((c, b) -> { + assertEquals("", c.getKey()); + })); + + assertEquals("${foo}", Expression.compile("\\${foo}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("${foo}bar", Expression.compile("\\${foo}bar", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("\\$\\{%s}", Expression.compile("\\$\\{%s}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("foo${bar}", Expression.compile("foo\\${bar}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("foo\\${bar}", Expression.compile("foo\\\\${bar}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + + assertEquals("foo\\${bar}", Expression.compile("foo\\$${bar}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("foo$${bar}", Expression.compile("foo$\\${bar}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + assertEquals("foo$$\\{bar}", Expression.compile("foo$$\\{bar}", STRICT_ESCAPES).evaluate((c, b) -> { + })); + } }