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 <[email protected]>
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: [email protected]
For additional commands, e-mail: [email protected]