This is an automated email from the ASF dual-hosted git repository.

remm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/main by this push:
     new 50a9edfdb2 Improve JSON filter
50a9edfdb2 is described below

commit 50a9edfdb230c06de40ccc69be8fe4947ebd3fce
Author: remm <r...@apache.org>
AuthorDate: Thu Mar 2 14:38:00 2023 +0100

    Improve JSON filter
    
    Add more capabilities (filtering from a writer, as in the PR, etc). Some
    common chars have a special more readable escape sequence in RFC 8259.
    Based on parts of PR#539 submitted by Thomas Meyer.
---
 java/org/apache/tomcat/util/json/JSONFilter.java   | 104 ++++++++++++++++++---
 .../apache/tomcat/util/json/TestJSONFilter.java    |  35 ++++---
 webapps/docs/changelog.xml                         |   9 ++
 3 files changed, 122 insertions(+), 26 deletions(-)

diff --git a/java/org/apache/tomcat/util/json/JSONFilter.java 
b/java/org/apache/tomcat/util/json/JSONFilter.java
index b5992d95d6..fe771b55cd 100644
--- a/java/org/apache/tomcat/util/json/JSONFilter.java
+++ b/java/org/apache/tomcat/util/json/JSONFilter.java
@@ -23,39 +23,119 @@ package org.apache.tomcat.util.json;
  */
 public class JSONFilter {
 
-    private JSONFilter() {
-        // Utility class. Hide the default constructor.
+    /**
+     * Escape the given char.
+     * @param c the char
+     * @return a char array with the escaped sequence
+     */
+    public static char[] escape(char c) {
+        if (c < 0x20 || c == 0x22 || c == 0x5c) {
+            char popular = getPopularChar(c);
+            if (popular > 0) {
+                return new char[] { '\\', popular };
+            } else {
+                StringBuilder escaped = new StringBuilder(6);
+                escaped.append("\\u");
+                escaped.append(String.format("%04X", Integer.valueOf(c)));
+                return escaped.toString().toCharArray();
+            }
+        } else {
+            char[] result = new char[1];
+            result[0] = c;
+            return result;
+        }
     }
 
+    /**
+     * Escape the given string.
+     * @param input the string
+     * @return the escaped string
+     */
     public static String escape(String input) {
+        return escape((CharSequence) input, 0, input.length()).toString();
+    }
+
+    /**
+     * Escape the given char sequence.
+     * @param input the char sequence
+     * @return the escaped char sequence
+     */
+    public static CharSequence escape(CharSequence input) {
+        return escape(input, 0, input.length());
+    }
+
+    /**
+     * Escape the given char sequence.
+     * @param input the char sequence
+     * @param off the offset on which escaping will start
+     * @param length the length which should be escaped
+     * @return the escaped char sequence corresponding to the specified range
+     */
+    public static CharSequence escape(CharSequence input, int off, int length) 
{
         /*
          * While any character MAY be escaped, only U+0000 to U+001F (control
          * characters), U+0022 (quotation mark) and U+005C (reverse solidus)
          * MUST be escaped.
          */
-        char[] chars = input.toCharArray();
+        //char[] chars = input.toCharArray();
         StringBuilder escaped = null;
-        int lastUnescapedStart = 0;
-        for (int i = 0; i < chars.length; i++) {
-            if (chars[i] < 0x20 || chars[i] == 0x22 || chars[i] == 0x5c) {
+        int lastUnescapedStart = off;
+        for (int i = off; i < length; i++) {
+            char c = input.charAt(i);
+            if (c < 0x20 || c == 0x22 || c == 0x5c) {
                 if (escaped == null) {
-                    escaped = new StringBuilder(chars.length + 20);
+                    escaped = new StringBuilder(length + 20);
                 }
                 if (lastUnescapedStart < i) {
                     escaped.append(input.subSequence(lastUnescapedStart, i));
                 }
                 lastUnescapedStart = i + 1;
-                escaped.append("\\u");
-                escaped.append(String.format("%04X", 
Integer.valueOf(chars[i])));
+                char popular = getPopularChar(c);
+                if (popular > 0) {
+                    escaped.append('\\').append(popular);
+                } else {
+                    escaped.append("\\u");
+                    escaped.append(String.format("%04X", Integer.valueOf(c)));
+                }
             }
         }
         if (escaped == null) {
-            return input;
+            if (off == 0 && length == input.length()) {
+                return input;
+            } else {
+                return input.subSequence(off, length - off);
+            }
         } else {
-            if (lastUnescapedStart < chars.length) {
-                escaped.append(input.subSequence(lastUnescapedStart, 
chars.length));
+            if (lastUnescapedStart < length) {
+                escaped.append(input.subSequence(lastUnescapedStart, length));
             }
             return escaped.toString();
         }
     }
+
+    private JSONFilter() {
+        // Utility class. Hide the default constructor.
+    }
+
+    private static char getPopularChar(char c) {
+        switch (c) {
+            case '"':
+            case '\\':
+            case '/':
+                return c;
+            case 0x8:
+                return 'b';
+            case 0xc:
+                return 'f';
+            case 0xa:
+                return 'n';
+            case 0xd:
+                return 'r';
+            case 0x9:
+                return 't';
+            default:
+                return 0;
+        }
+    }
+
 }
diff --git a/test/org/apache/tomcat/util/json/TestJSONFilter.java 
b/test/org/apache/tomcat/util/json/TestJSONFilter.java
index 7abac71ce2..136f2f1919 100644
--- a/test/org/apache/tomcat/util/json/TestJSONFilter.java
+++ b/test/org/apache/tomcat/util/json/TestJSONFilter.java
@@ -29,6 +29,10 @@ import org.junit.runners.Parameterized.Parameter;
 @RunWith(Parameterized.class)
 public class TestJSONFilter {
 
+    // Use something ...
+    private static final char SUB = 0x1A;
+    private static final char STX = 0x02;
+
     @Parameterized.Parameters(name = "{index}: input[{0}], output[{1}]")
     public static Collection<Object[]> parameters() {
         Collection<Object[]> parameterSets = new ArrayList<>();
@@ -37,34 +41,37 @@ public class TestJSONFilter {
         parameterSets.add(new String[] { "", "" });
 
         // Must escape
-        parameterSets.add(new String[] { "\"", "\\u0022" });
-        parameterSets.add(new String[] { "\\", "\\u005C" });
+        parameterSets.add(new String[] { "\"", "\\\"" });
+        parameterSets.add(new String[] { "\\", "\\\\" });
         // Sample of controls
-        parameterSets.add(new String[] { "\t", "\\u0009" });
-        parameterSets.add(new String[] { "\n", "\\u000A" });
-        parameterSets.add(new String[] { "\r", "\\u000D" });
+        parameterSets.add(new String[] { "\t", "\\t" });
+        parameterSets.add(new String[] { "\n", "\\n" });
+        parameterSets.add(new String[] { "\r", "\\r" });
 
         // No escape
         parameterSets.add(new String[] { "aaa", "aaa" });
 
         // Start
-        parameterSets.add(new String[] { "\naaa", "\\u000Aaaa" });
-        parameterSets.add(new String[] { "\n\naaa", "\\u000A\\u000Aaaa" });
+        parameterSets.add(new String[] { "\naaa", "\\naaa" });
+        parameterSets.add(new String[] { "\n\naaa", "\\n\\naaa" });
 
         // Middle
-        parameterSets.add(new String[] { "aaa\naaa", "aaa\\u000Aaaa" });
-        parameterSets.add(new String[] { "aaa\n\naaa", "aaa\\u000A\\u000Aaaa" 
});
+        parameterSets.add(new String[] { "aaa\naaa", "aaa\\naaa" });
+        parameterSets.add(new String[] { "aaa\n\naaa", "aaa\\n\\naaa" });
 
         // End
-        parameterSets.add(new String[] { "aaa\n", "aaa\\u000A" });
-        parameterSets.add(new String[] { "aaa\n\n", "aaa\\u000A\\u000A" });
+        parameterSets.add(new String[] { "aaa\n", "aaa\\n" });
+        parameterSets.add(new String[] { "aaa\n\n", "aaa\\n\\n" });
 
         // Start, middle and end
-        parameterSets.add(new String[] { "\naaa\naaa\n", 
"\\u000Aaaa\\u000Aaaa\\u000A" });
-        parameterSets.add(new String[] { "\n\naaa\n\naaa\n\n", 
"\\u000A\\u000Aaaa\\u000A\\u000Aaaa\\u000A\\u000A" });
+        parameterSets.add(new String[] { "\naaa\naaa\n", "\\naaa\\naaa\\n" });
+        parameterSets.add(new String[] { "\n\naaa\n\naaa\n\n", 
"\\n\\naaa\\n\\naaa\\n\\n" });
 
         // Multiple
-        parameterSets.add(new String[] { "\n\n", "\\u000A\\u000A" });
+        parameterSets.add(new String[] { "\n\n", "\\n\\n" });
+        parameterSets.add(new String[] { "\n" + STX + "\n", "\\n\\u0002\\n" });
+        parameterSets.add(new String[] { "\n" + STX + "\n" + SUB, 
"\\n\\u0002\\n\\u001A" });
+        parameterSets.add(new String[] { SUB + "\n" + STX + "\n" + SUB, 
"\\u001A\\n\\u0002\\n\\u001A" });
 
         return parameterSets;
     }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 879c79f20e..64564ef3a8 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -119,6 +119,15 @@
       </fix>
     </changelog>
   </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        JSON filter should support specific escaping for common special
+        characters as defined in RFC 8259. Based on code submitted by
+        Thomas Meyer. (remm)
+      </fix>
+    </changelog>
+  </subsection>
   <subsection name="Other">
     <changelog>
       <add>


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to