(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 (&lt;!--, &lt;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("&lt;abc&gt;", StringEscapeUtils.escapeXml("<abc>"));

Reply via email to