This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push:
new cb3a20eaf keep StrSubstitutor from treating a value's trailing $ as an
escape (#1834)
cb3a20eaf is described below
commit cb3a20eafad7baf2d206bb1083e134c22021a574
Author: alhuda <[email protected]>
AuthorDate: Sat Oct 24 17:52:47 2026 +0530
keep StrSubstitutor from treating a value's trailing $ as an escape (#1834)
After a variable is replaced, substituteRecursive resumes scanning at
the end of the inserted value, and the escape check looks at the char
before the next prefix bounded only by the scan offset. A value ending
in the escape char therefore escaped the variable that follows it in
the template: the value lost its last char and the next variable was
left unresolved, e.g. price=5$ makes "${price}${x}" return "5${x}"
instead of "5$X".
Only accept an escape char at or after the end of the last
substitution.
---
.../java/org/apache/commons/lang3/text/StrSubstitutor.java | 5 ++++-
.../org/apache/commons/lang3/text/StrSubstitutorTest.java | 11 +++++++++++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java
b/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java
index a640b0313..0d87394ab 100644
--- a/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java
+++ b/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java
@@ -1178,12 +1178,14 @@ private int substituteRecursive(final StrBuilder buf,
final int offset, final in
char[] chars = buf.buffer;
int bufEnd = offset + length;
int pos = offset;
+ // an escape character must come from the text being scanned, not from
the end of a substituted value
+ int escapeStart = offset;
while (pos < bufEnd) {
final int startMatchLen = pfxMatcher.isMatch(chars, pos, offset,
bufEnd);
if (startMatchLen == 0) {
pos++;
} else // found variable start marker
- if (pos > offset && chars[pos - 1] == escape) {
+ if (pos > escapeStart && chars[pos - 1] == escape) {
// escaped
if (preserveEscapes) {
pos++;
@@ -1264,6 +1266,7 @@ private int substituteRecursive(final StrBuilder buf,
final int offset, final in
int change = substitute(buf, startPos, varLen,
priorVariables);
change = change + varLen - (endPos - startPos);
pos += change;
+ escapeStart = pos;
bufEnd += change;
lengthChange += change;
chars = buf.buffer; // in case buffer was
altered
diff --git
a/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java
b/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java
index 8c3f02274..2b9c70de0 100644
--- a/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java
+++ b/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java
@@ -440,6 +440,17 @@ void testReplaceEscaping() {
doTestReplace("The ${animal} jumps over the lazy dog.", "The
$${animal} jumps over the ${target}.", true);
}
+ /**
+ * Tests that an escape character at the end of a substituted value does
not escape the next variable.
+ */
+ @Test
+ void testReplaceValueEndingWithEscape() {
+ values.put("price", "5$");
+ doTestReplace("The price is 5$lazy dog.", "The price is
${price}${target}.", true);
+ values.put("animal", "$");
+ doTestReplace("The $lazy dog jumps over the $${target}.", "The
${animal}${target} jumps over the ${animal}$${target}.", true);
+ }
+
/**
* Tests when no incomplete prefix.
*/