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