Avoid invalid JSON in JSONErrorReportValve output · apache/tomcat@b336f4e · GitHub
Skip to content

Commit

Permalink
Avoid invalid JSON in JSONErrorReportValve output
Browse files Browse the repository at this point in the history
  • Loading branch information
markt-asf committed Nov 9, 2022
1 parent ef1f4ee commit b336f4e
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 3 deletions.
7 changes: 4 additions & 3 deletions java/org/apache/catalina/valves/JsonErrorReportValve.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.catalina.connector.Response;
import org.apache.coyote.ActionCode;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.json.JSONFilter;
import org.apache.tomcat.util.res.StringManager;

/**
Expand Down Expand Up @@ -82,9 +83,9 @@ protected void report(Request request, Response response, Throwable throwable) {
}
}
String jsonReport = "{\n" +
" \"type\": \"" + type + "\",\n" +
" \"message\": \"" + message + "\",\n" +
" \"description\": \"" + description + "\"\n" +
" \"type\": \"" + JSONFilter.escape(type) + "\",\n" +
" \"message\": \"" + JSONFilter.escape(message) + "\",\n" +
" \"description\": \"" + JSONFilter.escape(description) + "\"\n" +
"}";
try {
try {
Expand Down
61 changes: 61 additions & 0 deletions java/org/apache/tomcat/util/json/JSONFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.util.json;

/**
* Provides escaping of values so they can be included in a JSON document.
* Escaping is based on the definition of JSON found in
* <a href="https://www.rfc-editor.org/rfc/rfc8259.html">RFC 8259</a>.
*/
public class JSONFilter {

private JSONFilter() {
// Utility class. Hide the default constructor.
}

public static String escape(String input) {
/*
* 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();
StringBuffer escaped = null;
int lastUnescapedStart = 0;
for (int i = 0; i < chars.length; i++) {
if (chars[i] < 0x20 || chars[i] == 0x22 || chars[i] == 0x5c) {
if (escaped == null) {
escaped = new StringBuffer(chars.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])));
}
}
if (escaped == null) {
return input;
} else {
if (lastUnescapedStart < chars.length) {
escaped.append(input.subSequence(lastUnescapedStart, chars.length));
}
return escaped.toString();
}
}
}
82 changes: 82 additions & 0 deletions test/org/apache/tomcat/util/json/TestJSONFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.util.json;

import java.util.ArrayList;
import java.util.Collection;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;


@RunWith(Parameterized.class)
public class TestJSONFilter {

@Parameterized.Parameters(name = "{index}: input[{0}], output[{1}]")
public static Collection<Object[]> parameters() {
Collection<Object[]> parameterSets = new ArrayList<>();

// Empty
parameterSets.add(new String[] { "", "" });

// Must escape
parameterSets.add(new String[] { "\"", "\\u0022" });
parameterSets.add(new String[] { "\\", "\\u005C" });
// Sample of controls
parameterSets.add(new String[] { "\t", "\\u0009" });
parameterSets.add(new String[] { "\n", "\\u000A" });
parameterSets.add(new String[] { "\r", "\\u000D" });

// No escape
parameterSets.add(new String[] { "aaa", "aaa" });

// Start
parameterSets.add(new String[] { "\naaa", "\\u000Aaaa" });
parameterSets.add(new String[] { "\n\naaa", "\\u000A\\u000Aaaa" });

// Middle
parameterSets.add(new String[] { "aaa\naaa", "aaa\\u000Aaaa" });
parameterSets.add(new String[] { "aaa\n\naaa", "aaa\\u000A\\u000Aaaa" });

// End
parameterSets.add(new String[] { "aaa\n", "aaa\\u000A" });
parameterSets.add(new String[] { "aaa\n\n", "aaa\\u000A\\u000A" });

// 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" });

// Multiple
parameterSets.add(new String[] { "\n\n", "\\u000A\\u000A" });

return parameterSets;
}

@Parameter(0)
public String input;

@Parameter(1)
public String output;

@Test
public void testStringEscaping() {
Assert.assertEquals(output, JSONFilter.escape(input));;
}
}
5 changes: 5 additions & 0 deletions webapps/docs/changelog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@
<bug>66338</bug>: Fix a regression that caused a nuance in refactoring
for <code>ErrorReportValve</code>. (lihan)
</fix>
<fix>
Escape values used to construct output for the
<code>JsonErrorReportValve</code> to ensure that it always outputs valid
JSON. (markt)
</fix>
</changelog>
</subsection>
<subsection name="Coyote">
Expand Down

0 comments on commit b336f4e

Please sign in to comment.