(commons-lang) branch master updated: StringEscapeUtils.escapeEcmaScript() misses backtick/template-literal (`, ${) and inline-script parser-state sequences (
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 07ff3a98e StringEscapeUtils.escapeEcmaScript() misses
backtick/template-literal (`, ${) and inline-script parser-state sequences
(<!--, <script) - claim 'Deals correctly with quotes' is falsified by ES6
(f012).
07ff3a98e is described below
commit 07ff3a98e2f26e9d0ae8830c630adcad9d871734
Author: Gary Gregory <[email protected]>
AuthorDate: Sat Sep 5 09:03:05 2026 -0400
StringEscapeUtils.escapeEcmaScript() misses backtick/template-literal
(`, ${) and inline-script parser-state sequences (<!--, <script) - claim
'Deals correctly with quotes' is falsified by ES6 (f012).
---
src/changes/changes.xml | 2 +-
.../apache/commons/lang3/StringEscapeUtils.java | 72 ++++++++++++++--------
.../commons/lang3/StringEscapeUtilsTest.java | 49 +++++++++++++++
3 files changed, 98 insertions(+), 25 deletions(-)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 77bbd68b2..2e64418cf 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -261,7 +261,7 @@ java.lang.NullPointerException: Cannot invoke
<action type="fix" dev="ggregory" due-to="Gary
Gregory">WordUtils.wrap(wrapLongWords=false, the 2-arg default) copies the
entire remaining string every iteration. (f009).</action>
<action type="fix" dev="ggregory" due-to="Gary
Gregory">FastDateParser.parse throws undeclared IllegalArgumentException,
NullPointerException, and IllegalStateException on crafted date strings
(f010).</action>
<action type="fix" dev="ggregory" due-to="Gary
Gregory">Memoizer: default caches the first failure forever, has no size bound
or eviction, and runs the user computation inside the ConcurrentHashMap bin
lock (blocking unrelated keys, deadlocking reentrant use) (f011).</action>
-
+ <action type="fix" dev="ggregory" due-to="Gary
Gregory">StringEscapeUtils.escapeEcmaScript() misses backtick/template-literal
(`, ${) and inline-script parser-state sequences (<!--, <script) - claim
'Deals correctly with quotes' is falsified by ES6 (f012).</action>
<!-- ADD -->
<action type="add" dev="ggregory" due-to="Gary
Gregory">Add JavaVersion.JAVA_27.</action>
<action type="add" dev="ggregory" due-to="Gary
Gregory">Add SystemUtils.IS_JAVA_27.</action>
diff --git a/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java
b/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java
index 91b8aabf7..cf35295a5 100644
--- a/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java
+++ b/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java
@@ -132,6 +132,8 @@ public int translate(final CharSequence input, final int
index, final Writer out
new String[][] {
{"'", "\\'"},
{"\"", "\\\""},
+ {"`", "\\`"},
+ {"${", "\\${"},
{"\\", "\\\\"},
{"/", "\\/"}
}),
@@ -435,24 +437,36 @@ public static final String escapeCsv(final String input) {
/**
* Escapes the characters in a {@link String} using EcmaScript String
rules.
- * <p>Escapes any values it finds into their EcmaScript String form.
- * Deals correctly with quotes and control-chars (tab, backslash, cr, ff,
etc.) </p>
- *
- * <p>So a tab becomes the characters {@code '\\'} and
- * {@code 't'}.</p>
- *
- * <p>The only difference between Java strings and EcmaScript strings
- * is that in EcmaScript, a single quote and forward-slash (/) are
escaped.</p>
- *
- * <p>Note that EcmaScript is best known by the JavaScript and
ActionScript dialects.</p>
+ * <p>
+ * Escapes any values it finds into their EcmaScript String form. Escapes
the EcmaScript string delimiters (single quote, double quote and (since ES6) the
+ * backtick) as well as the template-literal interpolation sequence
<code>${</code> and control-chars (tab, backslash, cr, ff, etc.).
+ * </p>
+ * <p>
+ * So a tab becomes the characters {@code '\\'} and {@code 't'}.
+ * </p>
+ * <p>
+ * The differences between Java strings and EcmaScript strings handled
here are that in EcmaScript, a single quote, the backtick, the <code>${</code>
+ * sequence and forward-slash (/) are escaped.
+ * </p>
+ * <p>
+ * <strong>Scope:</strong> the output is a correctly escaped EcmaScript
string literal for any of the three string delimiters, but it is <em>not</em>
made
+ * safe for direct embedding inside an HTML inline {@code <script>} block:
HTML parser-state sequences such as {@code <!--} and {@code <script} pass
through
+ * unchanged (a literal {@code </script>} is neutralized by the
forward-slash escape). HTML-context encoding must be applied separately when
the result is
+ * placed in HTML.
+ * </p>
+ * <p>
+ * Note that EcmaScript is best known by the JavaScript and ActionScript
dialects.
+ * </p>
+ * <p>
+ * Example:
+ * </p>
*
- * <p>Example:</p>
* <pre>
* input string: He didn't say, "Stop!"
* output string: He didn\'t say, \"Stop!\"
* </pre>
*
- * @param input String to escape values in, may be null
+ * @param input String to escape values in, may be null
* @return String with escaped values, {@code null} if null string input
* @since 3.0
*/
@@ -527,24 +541,34 @@ public static final String escapeJava(final String input)
{
/**
* Escapes the characters in a {@link String} using Json String rules.
- * <p>Escapes any values it finds into their Json String form.
- * Deals correctly with quotes and control-chars (tab, backslash, cr, ff,
etc.) </p>
- *
- * <p>So a tab becomes the characters {@code '\\'} and
- * {@code 't'}.</p>
- *
- * <p>The only difference between Java strings and Json strings
- * is that in Json, forward-slash (/) is escaped.</p>
- *
- * <p>See https://www.ietf.org/rfc/rfc4627.txt for further details.</p>
+ * <p>
+ * Escapes any values it finds into their JSON String form. Deals
correctly with quotes and control-chars (tab, backslash, cr, ff, etc.)
+ * </p>
+ * <p>
+ * So a tab becomes the characters {@code '\\'} and {@code 't'}.
+ * </p>
+ * <p>
+ * The only difference between Java strings and Json strings is that in
Json, forward-slash (/) is escaped.
+ * </p>
+ * <p>
+ * <strong>Scope:</strong> the output is a correctly escaped JSON string,
but it is <em>not</em> made safe for direct embedding inside an HTML inline
+ * {@code <script>} block: the backtick, <code>${</code>, and HTML
parser-state sequences such as {@code <!--} and {@code <script} pass through
unchanged
+ * (JSON offers no backslash escape for them; only a literal {@code
</script>} is neutralized by the forward-slash escape). HTML-context encoding
must be
+ * applied separately when the result is placed in HTML.
+ * </p>
+ * <p>
+ * See https://www.ietf.org/rfc/rfc4627.txt for further details.
+ * </p>
+ * <p>
+ * Example:
+ * </p>
*
- * <p>Example:</p>
* <pre>
* input string: He didn't say, "Stop!"
* output string: He didn't say, \"Stop!\"
* </pre>
*
- * @param input String to escape values in, may be null
+ * @param input String to escape values in, may be null
* @return String with escaped values, {@code null} if null string input
* @since 3.2
*/
diff --git a/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java
b/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java
index b4e87000c..4af256cd8 100644
--- a/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java
@@ -150,6 +150,45 @@ void testEscapeEcmaScript() {
StringEscapeUtils.escapeEcmaScript("document.getElementById(\"test\").value =
'<script>alert('aaa');</script>';"));
}
+ @Test
+ void testEscapeEcmaScriptInlineScriptSequences() {
+ // HTML parser-state sequences remain unchanged, as documented for
this string escaper.
+ assertEquals("<!--", StringEscapeUtils.escapeEcmaScript("<!--"));
+ assertEquals("<script", StringEscapeUtils.escapeEcmaScript("<script"));
+ assertEquals("<!--<script>",
StringEscapeUtils.escapeEcmaScript("<!--<script>"));
+ assertEquals("<\\/script>",
StringEscapeUtils.escapeEcmaScript("</script>"));
+ }
+
+ @Test
+ void testEscapeEcmaScriptLineSeparators() {
+ assertEquals("\\u2028\\u2029",
StringEscapeUtils.escapeEcmaScript("\u2028\u2029"));
+ }
+
+ @Test
+ void testEscapeEcmaScriptTemplateLiterals() throws IOException {
+ final String[][] cases = {
+ {"`", "\\`"},
+ {"${", "\\${"},
+ {"${alert(document.cookie)}", "\\${alert(document.cookie)}"},
+ {"`;alert(document.cookie);//",
"\\`;alert(document.cookie);\\/\\/"},
+ {"Hello `${name}`!", "Hello \\`\\${name}\\`!"},
+ {"${first}${second}", "\\${first}\\${second}"},
+ {"$${name}", "$\\${name}"},
+ {"\\`", "\\\\\\`"},
+ {"\\${name}", "\\\\\\${name}"},
+ {"$ {name} { } $", "$ {name} { } $"}
+ };
+ for (final String[] pair : cases) {
+ final String input = pair[0];
+ final String expected = pair[1];
+ assertEquals(expected, StringEscapeUtils.escapeEcmaScript(input),
input);
+ final StringWriter writer = new StringWriter();
+ StringEscapeUtils.ESCAPE_ECMASCRIPT.translate(input, writer);
+ assertEquals(expected, writer.toString(), input);
+ assertEquals(input,
StringEscapeUtils.unescapeEcmaScript(expected), input);
+ }
+ }
+
/**
* Tests https://issues.apache.org/jira/browse/LANG-339
*/
@@ -248,6 +287,16 @@ void testEscapeJson() {
assertEquals(expected, StringEscapeUtils.escapeJson(input));
}
+ @Test
+ void testEscapeJsonTemplateLiteralAndInlineScriptSequences() {
+ // JSON escaping preserves these sequences and does not provide
HTML-context encoding.
+ assertEquals("`${alert(document.cookie)}`",
StringEscapeUtils.escapeJson("`${alert(document.cookie)}`"));
+ assertEquals("<!--", StringEscapeUtils.escapeJson("<!--"));
+ assertEquals("<script", StringEscapeUtils.escapeJson("<script"));
+ assertEquals("<!--<script>",
StringEscapeUtils.escapeJson("<!--<script>"));
+ assertEquals("<\\/script>", StringEscapeUtils.escapeJson("</script>"));
+ }
+
@Test
void testEscapeXml() throws Exception {
assertEquals("<abc>", StringEscapeUtils.escapeXml("<abc>"));