mirror of
https://github.com/apache/struts.git
synced 2026-08-06 15:17:00 +00:00
WW-5618 feat(json): add configurable limits to JSON plugin (#1625)
Add configurable limits to the JSON plugin to prevent denial-of-service attacks via malicious payloads (deeply nested objects, huge arrays, long strings). Changes: - Extract JSONReader interface from class, create StrutsJSONReader impl with maxElements, maxDepth, maxStringLength, maxKeyLength enforcement - Rename DefaultJSONWriter to StrutsJSONWriter (Struts* naming convention) - Add JSONBeanSelectionProvider for bean aliasing via constants - Update JSONUtil with @Inject for reader/writer, add instance deserializeInput() with maxLength check, deprecate static deserialize() - Wire limits into JSONInterceptor with @Inject from constants - Register beans and defaults in struts-plugin.xml Default limits: 10K elements, 64 depth, 2MB length, 256KB strings, 512 keys. All configurable via struts.xml constants or per-action interceptor params. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.struts2.json;
|
||||
|
||||
import org.apache.struts2.config.AbstractBeanSelectionProvider;
|
||||
import org.apache.struts2.config.ConfigurationException;
|
||||
import org.apache.struts2.inject.ContainerBuilder;
|
||||
import org.apache.struts2.inject.Scope;
|
||||
import org.apache.struts2.util.location.LocatableProperties;
|
||||
|
||||
public class JSONBeanSelectionProvider extends AbstractBeanSelectionProvider {
|
||||
|
||||
@Override
|
||||
public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
|
||||
alias(JSONReader.class, JSONConstants.JSON_READER, builder, props, Scope.PROTOTYPE);
|
||||
alias(JSONWriter.class, JSONConstants.JSON_WRITER, builder, props, Scope.PROTOTYPE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,6 +29,12 @@ package org.apache.struts2.json;
|
||||
public class JSONConstants {
|
||||
|
||||
public static final String JSON_WRITER = "struts.json.writer";
|
||||
public static final String JSON_READER = "struts.json.reader";
|
||||
public static final String RESULT_EXCLUDE_PROXY_PROPERTIES = "struts.json.result.excludeProxyProperties";
|
||||
public static final String DATE_FORMAT = "struts.json.dateformat";
|
||||
public static final String JSON_MAX_ELEMENTS = "struts.json.maxElements";
|
||||
public static final String JSON_MAX_DEPTH = "struts.json.maxDepth";
|
||||
public static final String JSON_MAX_LENGTH = "struts.json.maxLength";
|
||||
public static final String JSON_MAX_STRING_LENGTH = "struts.json.maxStringLength";
|
||||
public static final String JSON_MAX_KEY_LENGTH = "struts.json.maxKeyLength";
|
||||
}
|
||||
|
||||
@@ -71,6 +71,13 @@ public class JSONInterceptor extends AbstractInterceptor {
|
||||
private String jsonContentType = "application/json";
|
||||
private String jsonRpcContentType = "application/json-rpc";
|
||||
|
||||
private JSONUtil jsonUtil;
|
||||
private int maxElements = JSONReader.DEFAULT_MAX_ELEMENTS;
|
||||
private int maxDepth = JSONReader.DEFAULT_MAX_DEPTH;
|
||||
private int maxLength = 2_097_152; // 2MB
|
||||
private int maxStringLength = JSONReader.DEFAULT_MAX_STRING_LENGTH;
|
||||
private int maxKeyLength = JSONReader.DEFAULT_MAX_KEY_LENGTH;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public String intercept(ActionInvocation invocation) throws Exception {
|
||||
HttpServletRequest request = ServletActionContext.getRequest();
|
||||
@@ -91,7 +98,8 @@ public class JSONInterceptor extends AbstractInterceptor {
|
||||
|
||||
if (jsonContentType.equalsIgnoreCase(requestContentType)) {
|
||||
// load JSON object
|
||||
Object obj = JSONUtil.deserialize(request.getReader());
|
||||
applyLimitsToReader();
|
||||
Object obj = jsonUtil.deserializeInput(request.getReader(), maxLength);
|
||||
|
||||
// JSON array (this.root cannot be null in this case)
|
||||
if(obj instanceof List && this.root != null) {
|
||||
@@ -133,7 +141,8 @@ public class JSONInterceptor extends AbstractInterceptor {
|
||||
Object result;
|
||||
if (this.enableSMD) {
|
||||
// load JSON object
|
||||
Object obj = JSONUtil.deserialize(request.getReader());
|
||||
applyLimitsToReader();
|
||||
Object obj = jsonUtil.deserializeInput(request.getReader(), maxLength);
|
||||
|
||||
if (obj instanceof Map) {
|
||||
Map smd = (Map) obj;
|
||||
@@ -168,8 +177,6 @@ public class JSONInterceptor extends AbstractInterceptor {
|
||||
result = rpcResponse;
|
||||
}
|
||||
|
||||
JSONUtil jsonUtil = invocation.getInvocationContext().getContainer().getInstance(JSONUtil.class);
|
||||
|
||||
String json = jsonUtil.serialize(result, excludeProperties, getIncludeProperties(),
|
||||
ignoreHierarchy, excludeNullProperties);
|
||||
json = addCallbackIfApplicable(request, json);
|
||||
@@ -185,6 +192,14 @@ public class JSONInterceptor extends AbstractInterceptor {
|
||||
return invocation.invoke();
|
||||
}
|
||||
|
||||
private void applyLimitsToReader() {
|
||||
JSONReader reader = jsonUtil.getReader();
|
||||
reader.setMaxElements(maxElements);
|
||||
reader.setMaxDepth(maxDepth);
|
||||
reader.setMaxStringLength(maxStringLength);
|
||||
reader.setMaxKeyLength(maxKeyLength);
|
||||
}
|
||||
|
||||
protected String readContentType(HttpServletRequest request) {
|
||||
String contentType = request.getHeader("Content-Type");
|
||||
LOG.debug("Content Type from request: {}", contentType);
|
||||
@@ -564,4 +579,34 @@ public class JSONInterceptor extends AbstractInterceptor {
|
||||
public void setJsonRpcContentType(String jsonRpcContentType) {
|
||||
this.jsonRpcContentType = jsonRpcContentType;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setJsonUtil(JSONUtil jsonUtil) {
|
||||
this.jsonUtil = jsonUtil;
|
||||
}
|
||||
|
||||
@Inject(value = JSONConstants.JSON_MAX_ELEMENTS, required = false)
|
||||
public void setMaxElements(String maxElements) {
|
||||
this.maxElements = Integer.parseInt(maxElements);
|
||||
}
|
||||
|
||||
@Inject(value = JSONConstants.JSON_MAX_DEPTH, required = false)
|
||||
public void setMaxDepth(String maxDepth) {
|
||||
this.maxDepth = Integer.parseInt(maxDepth);
|
||||
}
|
||||
|
||||
@Inject(value = JSONConstants.JSON_MAX_LENGTH, required = false)
|
||||
public void setMaxLength(String maxLength) {
|
||||
this.maxLength = Integer.parseInt(maxLength);
|
||||
}
|
||||
|
||||
@Inject(value = JSONConstants.JSON_MAX_STRING_LENGTH, required = false)
|
||||
public void setMaxStringLength(String maxStringLength) {
|
||||
this.maxStringLength = Integer.parseInt(maxStringLength);
|
||||
}
|
||||
|
||||
@Inject(value = JSONConstants.JSON_MAX_KEY_LENGTH, required = false)
|
||||
public void setMaxKeyLength(String maxKeyLength) {
|
||||
this.maxKeyLength = Integer.parseInt(maxKeyLength);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,285 +18,25 @@
|
||||
*/
|
||||
package org.apache.struts2.json;
|
||||
|
||||
import java.text.CharacterIterator;
|
||||
import java.text.StringCharacterIterator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Deserializes and object from a JSON string
|
||||
* Deserializes an object from a JSON string.
|
||||
* </p>
|
||||
*/
|
||||
public class JSONReader {
|
||||
private static final Object OBJECT_END = new Object();
|
||||
private static final Object ARRAY_END = new Object();
|
||||
private static final Object COLON = new Object();
|
||||
private static final Object COMMA = new Object();
|
||||
private static Map<Character, Character> escapes = new HashMap<Character, Character>();
|
||||
public interface JSONReader {
|
||||
|
||||
static {
|
||||
escapes.put('"', '"');
|
||||
escapes.put('\\', '\\');
|
||||
escapes.put('/', '/');
|
||||
escapes.put('b', '\b');
|
||||
escapes.put('f', '\f');
|
||||
escapes.put('n', '\n');
|
||||
escapes.put('r', '\r');
|
||||
escapes.put('t', '\t');
|
||||
}
|
||||
int DEFAULT_MAX_ELEMENTS = 10_000;
|
||||
int DEFAULT_MAX_DEPTH = 64;
|
||||
int DEFAULT_MAX_STRING_LENGTH = 262_144; // 256KB
|
||||
int DEFAULT_MAX_KEY_LENGTH = 512;
|
||||
|
||||
private CharacterIterator it;
|
||||
private char c;
|
||||
private Object token;
|
||||
private StringBuilder buf = new StringBuilder();
|
||||
Object read(String string) throws JSONException;
|
||||
|
||||
protected char next() {
|
||||
this.c = this.it.next();
|
||||
void setMaxElements(int maxElements);
|
||||
|
||||
return this.c;
|
||||
}
|
||||
void setMaxDepth(int maxDepth);
|
||||
|
||||
protected void skipWhiteSpace() {
|
||||
while (Character.isWhitespace(this.c)) {
|
||||
this.next();
|
||||
}
|
||||
}
|
||||
void setMaxStringLength(int maxStringLength);
|
||||
|
||||
public Object read(String string) throws JSONException {
|
||||
this.it = new StringCharacterIterator(string);
|
||||
this.c = this.it.first();
|
||||
|
||||
return this.read();
|
||||
}
|
||||
|
||||
protected Object read() throws JSONException {
|
||||
Object ret;
|
||||
|
||||
this.skipWhiteSpace();
|
||||
|
||||
if (this.c == '"') {
|
||||
this.next();
|
||||
ret = this.string('"');
|
||||
} else if (this.c == '\'') {
|
||||
this.next();
|
||||
ret = this.string('\'');
|
||||
} else if (this.c == '[') {
|
||||
this.next();
|
||||
ret = this.array();
|
||||
} else if (this.c == ']') {
|
||||
ret = ARRAY_END;
|
||||
this.next();
|
||||
} else if (this.c == ',') {
|
||||
ret = COMMA;
|
||||
this.next();
|
||||
} else if (this.c == '{') {
|
||||
this.next();
|
||||
ret = this.object();
|
||||
} else if (this.c == '}') {
|
||||
ret = OBJECT_END;
|
||||
this.next();
|
||||
} else if (this.c == ':') {
|
||||
ret = COLON;
|
||||
this.next();
|
||||
} else if ((this.c == 't') && (this.next() == 'r') && (this.next() == 'u') && (this.next() == 'e')) {
|
||||
ret = Boolean.TRUE;
|
||||
this.next();
|
||||
} else if ((this.c == 'f') && (this.next() == 'a') && (this.next() == 'l') && (this.next() == 's')
|
||||
&& (this.next() == 'e')) {
|
||||
ret = Boolean.FALSE;
|
||||
this.next();
|
||||
} else if ((this.c == 'n') && (this.next() == 'u') && (this.next() == 'l') && (this.next() == 'l')) {
|
||||
ret = null;
|
||||
this.next();
|
||||
} else if (Character.isDigit(this.c) || (this.c == '-')) {
|
||||
ret = this.number();
|
||||
} else {
|
||||
throw buildInvalidInputException();
|
||||
}
|
||||
|
||||
this.token = ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Map object() throws JSONException {
|
||||
Map ret = new HashMap();
|
||||
Object next = this.read();
|
||||
if (next != OBJECT_END) {
|
||||
String key = (String) next;
|
||||
while (this.token != OBJECT_END) {
|
||||
this.read(); // should be a colon
|
||||
|
||||
if (this.token != OBJECT_END) {
|
||||
ret.put(key, this.read());
|
||||
|
||||
if (this.read() == COMMA) {
|
||||
Object name = this.read();
|
||||
|
||||
if (name instanceof String) {
|
||||
key = (String) name;
|
||||
} else
|
||||
throw buildInvalidInputException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected JSONException buildInvalidInputException() {
|
||||
return new JSONException("Input string is not well formed JSON (invalid char " + this.c + ")");
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected List array() throws JSONException {
|
||||
List ret = new ArrayList();
|
||||
Object value = this.read();
|
||||
|
||||
while (this.token != ARRAY_END) {
|
||||
ret.add(value);
|
||||
|
||||
Object read = this.read();
|
||||
if (read == COMMA) {
|
||||
value = this.read();
|
||||
} else if (read != ARRAY_END) {
|
||||
throw buildInvalidInputException();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected Object number() throws JSONException {
|
||||
this.buf.setLength(0);
|
||||
boolean toDouble = false;
|
||||
|
||||
if (this.c == '-') {
|
||||
this.add();
|
||||
}
|
||||
|
||||
this.addDigits();
|
||||
|
||||
if (this.c == '.') {
|
||||
toDouble = true;
|
||||
this.add();
|
||||
this.addDigits();
|
||||
}
|
||||
|
||||
if ((this.c == 'e') || (this.c == 'E')) {
|
||||
toDouble = true;
|
||||
this.add();
|
||||
|
||||
if ((this.c == '+') || (this.c == '-')) {
|
||||
this.add();
|
||||
}
|
||||
|
||||
this.addDigits();
|
||||
}
|
||||
|
||||
if (toDouble) {
|
||||
try {
|
||||
return Double.parseDouble(this.buf.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
throw buildInvalidInputException();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return Long.parseLong(this.buf.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
throw buildInvalidInputException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Object string(char quote) {
|
||||
this.buf.setLength(0);
|
||||
|
||||
while ((this.c != quote) && (this.c != CharacterIterator.DONE)) {
|
||||
if (this.c == '\\') {
|
||||
this.next();
|
||||
|
||||
if (this.c == 'u') {
|
||||
this.add(this.unicode());
|
||||
} else {
|
||||
Object value = escapes.get(this.c);
|
||||
|
||||
if (value != null) {
|
||||
this.add((Character) value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.add();
|
||||
}
|
||||
}
|
||||
|
||||
this.next();
|
||||
|
||||
return this.buf.toString();
|
||||
}
|
||||
|
||||
protected void add(char cc) {
|
||||
this.buf.append(cc);
|
||||
this.next();
|
||||
}
|
||||
|
||||
protected void add() {
|
||||
this.add(this.c);
|
||||
}
|
||||
|
||||
protected void addDigits() {
|
||||
while (Character.isDigit(this.c)) {
|
||||
this.add();
|
||||
}
|
||||
}
|
||||
|
||||
protected char unicode() {
|
||||
int value = 0;
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
switch (this.next()) {
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
value = (value << 4) + (this.c - '0');
|
||||
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
case 'b':
|
||||
case 'c':
|
||||
case 'd':
|
||||
case 'e':
|
||||
case 'f':
|
||||
value = (value << 4) + (this.c - 'W');
|
||||
|
||||
break;
|
||||
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'E':
|
||||
case 'F':
|
||||
value = (value << 4) + (this.c - '7');
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (char) value;
|
||||
}
|
||||
void setMaxKeyLength(int maxKeyLength);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ import java.util.Arrays;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.struts2.inject.Container;
|
||||
import org.apache.struts2.inject.Inject;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -60,16 +59,21 @@ public class JSONUtil {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(JSONUtil.class);
|
||||
|
||||
private JSONReader reader;
|
||||
private JSONWriter writer;
|
||||
|
||||
public void setWriter(JSONWriter writer) {
|
||||
this.writer = writer;
|
||||
@Inject
|
||||
public void setReader(JSONReader reader) {
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
public JSONReader getReader() {
|
||||
return reader;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setContainer(Container container) {
|
||||
setWriter(container.getInstance(JSONWriter.class, container.getInstance(String.class,
|
||||
JSONConstants.JSON_WRITER)));
|
||||
public void setWriter(JSONWriter writer) {
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -284,6 +288,34 @@ public class JSONUtil {
|
||||
writer.write(serialize(object, excludeProperties, includeProperties, true, excludeNullProperties, cacheBeanInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes an object from JSON using the injected reader with limit enforcement.
|
||||
*
|
||||
* @param reader Reader to read a JSON string from
|
||||
* @param maxLength maximum allowed length of the JSON input
|
||||
* @return deserialized object
|
||||
* @throws JSONException when IOException happens or limits are exceeded
|
||||
*/
|
||||
public Object deserializeInput(Reader reader, int maxLength) throws JSONException {
|
||||
BufferedReader bufferReader = new BufferedReader(reader);
|
||||
String line;
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
try {
|
||||
while ((line = bufferReader.readLine()) != null) {
|
||||
buffer.append(line);
|
||||
if (buffer.length() > maxLength) {
|
||||
throw new JSONException("JSON input exceeds maximum allowed length ("
|
||||
+ maxLength + "). Use " + JSONConstants.JSON_MAX_LENGTH + " to increase the limit.");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new JSONException(e);
|
||||
}
|
||||
|
||||
return this.reader.read(buffer.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a object from JSON
|
||||
*
|
||||
@@ -291,9 +323,11 @@ public class JSONUtil {
|
||||
* string in JSON
|
||||
* @return desrialized object
|
||||
* @throws JSONException in case of error during serialize
|
||||
* @deprecated Use instance method {@link #deserializeInput(Reader, int)} with injected JSONUtil instead
|
||||
*/
|
||||
@Deprecated( forRemoval = true, since = "7.2.0")
|
||||
public static Object deserialize(String json) throws JSONException {
|
||||
JSONReader reader = new JSONReader();
|
||||
StrutsJSONReader reader = new StrutsJSONReader();
|
||||
return reader.read(json);
|
||||
}
|
||||
|
||||
@@ -305,7 +339,9 @@ public class JSONUtil {
|
||||
* @return deserialized object
|
||||
* @throws JSONException
|
||||
* when IOException happens
|
||||
* @deprecated Use instance method {@link #deserializeInput(Reader, int)} with injected JSONUtil instead
|
||||
*/
|
||||
@Deprecated( forRemoval = true, since = "7.2.0")
|
||||
public static Object deserialize(Reader reader) throws JSONException {
|
||||
// read content
|
||||
BufferedReader bufferReader = new BufferedReader(reader);
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
/*
|
||||
* 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.struts2.json;
|
||||
|
||||
import java.text.CharacterIterator;
|
||||
import java.text.StringCharacterIterator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Deserializes an object from a JSON string with configurable limits
|
||||
* to prevent denial-of-service attacks via malicious payloads.
|
||||
* </p>
|
||||
*/
|
||||
public class StrutsJSONReader implements JSONReader {
|
||||
private static final Object OBJECT_END = new Object();
|
||||
private static final Object ARRAY_END = new Object();
|
||||
private static final Object COLON = new Object();
|
||||
private static final Object COMMA = new Object();
|
||||
private static final Map<Character, Character> escapes = Map.of(
|
||||
'"', '"',
|
||||
'\\', '\\',
|
||||
'/', '/',
|
||||
'b', '\b',
|
||||
'f', '\f',
|
||||
'n', '\n',
|
||||
'r', '\r',
|
||||
't', '\t'
|
||||
);
|
||||
|
||||
private CharacterIterator it;
|
||||
private char c;
|
||||
private Object token;
|
||||
private final StringBuilder buf = new StringBuilder();
|
||||
|
||||
private int maxElements = DEFAULT_MAX_ELEMENTS;
|
||||
private int maxDepth = DEFAULT_MAX_DEPTH;
|
||||
private int maxStringLength = DEFAULT_MAX_STRING_LENGTH;
|
||||
private int maxKeyLength = DEFAULT_MAX_KEY_LENGTH;
|
||||
private int depth;
|
||||
|
||||
@Override
|
||||
public void setMaxElements(int maxElements) {
|
||||
this.maxElements = maxElements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxDepth(int maxDepth) {
|
||||
this.maxDepth = maxDepth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxStringLength(int maxStringLength) {
|
||||
this.maxStringLength = maxStringLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxKeyLength(int maxKeyLength) {
|
||||
this.maxKeyLength = maxKeyLength;
|
||||
}
|
||||
|
||||
protected char next() {
|
||||
this.c = this.it.next();
|
||||
|
||||
return this.c;
|
||||
}
|
||||
|
||||
protected void skipWhiteSpace() {
|
||||
while (Character.isWhitespace(this.c)) {
|
||||
this.next();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(String string) throws JSONException {
|
||||
this.it = new StringCharacterIterator(string);
|
||||
this.c = this.it.first();
|
||||
this.depth = 0;
|
||||
|
||||
return this.read();
|
||||
}
|
||||
|
||||
protected Object read() throws JSONException {
|
||||
Object ret;
|
||||
|
||||
this.skipWhiteSpace();
|
||||
|
||||
if (this.c == '"') {
|
||||
this.next();
|
||||
ret = this.string('"');
|
||||
} else if (this.c == '\'') {
|
||||
this.next();
|
||||
ret = this.string('\'');
|
||||
} else if (this.c == '[') {
|
||||
this.next();
|
||||
ret = this.array();
|
||||
} else if (this.c == ']') {
|
||||
ret = ARRAY_END;
|
||||
this.next();
|
||||
} else if (this.c == ',') {
|
||||
ret = COMMA;
|
||||
this.next();
|
||||
} else if (this.c == '{') {
|
||||
this.next();
|
||||
ret = this.object();
|
||||
} else if (this.c == '}') {
|
||||
ret = OBJECT_END;
|
||||
this.next();
|
||||
} else if (this.c == ':') {
|
||||
ret = COLON;
|
||||
this.next();
|
||||
} else if ((this.c == 't') && (this.next() == 'r') && (this.next() == 'u') && (this.next() == 'e')) {
|
||||
ret = Boolean.TRUE;
|
||||
this.next();
|
||||
} else if ((this.c == 'f') && (this.next() == 'a') && (this.next() == 'l') && (this.next() == 's')
|
||||
&& (this.next() == 'e')) {
|
||||
ret = Boolean.FALSE;
|
||||
this.next();
|
||||
} else if ((this.c == 'n') && (this.next() == 'u') && (this.next() == 'l') && (this.next() == 'l')) {
|
||||
ret = null;
|
||||
this.next();
|
||||
} else if (Character.isDigit(this.c) || (this.c == '-')) {
|
||||
ret = this.number();
|
||||
} else {
|
||||
throw buildInvalidInputException();
|
||||
}
|
||||
|
||||
this.token = ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected Map<String, Object> object() throws JSONException {
|
||||
if (this.depth >= this.maxDepth) {
|
||||
throw new JSONException("JSON object nesting exceeds maximum allowed depth ("
|
||||
+ this.maxDepth + "). Use " + JSONConstants.JSON_MAX_DEPTH + " to increase the limit.");
|
||||
}
|
||||
this.depth++;
|
||||
try {
|
||||
Map<String, Object> ret = new HashMap<>();
|
||||
Object next = this.read();
|
||||
if (next != OBJECT_END) {
|
||||
String key = (String) next;
|
||||
validateKeyLength(key);
|
||||
while (this.token != OBJECT_END) {
|
||||
this.read(); // should be a colon
|
||||
|
||||
if (this.token != OBJECT_END) {
|
||||
if (ret.size() >= this.maxElements) {
|
||||
throw new JSONException("JSON object exceeds maximum allowed elements ("
|
||||
+ this.maxElements + "). Use " + JSONConstants.JSON_MAX_ELEMENTS + " to increase the limit.");
|
||||
}
|
||||
ret.put(key, this.read());
|
||||
|
||||
if (this.read() == COMMA) {
|
||||
Object name = this.read();
|
||||
|
||||
if (name instanceof String nextKey) {
|
||||
key = nextKey;
|
||||
validateKeyLength(key);
|
||||
} else {
|
||||
throw buildInvalidInputException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
} finally {
|
||||
this.depth--;
|
||||
}
|
||||
}
|
||||
|
||||
private void validateKeyLength(String key) throws JSONException {
|
||||
if (key.length() > this.maxKeyLength) {
|
||||
throw new JSONException("JSON object key exceeds maximum allowed length ("
|
||||
+ this.maxKeyLength + "). Use " + JSONConstants.JSON_MAX_KEY_LENGTH + " to increase the limit.");
|
||||
}
|
||||
}
|
||||
|
||||
protected JSONException buildInvalidInputException() {
|
||||
return new JSONException("Input string is not well formed JSON (invalid char " + this.c + ")");
|
||||
}
|
||||
|
||||
|
||||
protected List<Object> array() throws JSONException {
|
||||
if (this.depth >= this.maxDepth) {
|
||||
throw new JSONException("JSON array nesting exceeds maximum allowed depth ("
|
||||
+ this.maxDepth + "). Use " + JSONConstants.JSON_MAX_DEPTH + " to increase the limit.");
|
||||
}
|
||||
this.depth++;
|
||||
try {
|
||||
List<Object> ret = new ArrayList<>();
|
||||
Object value = this.read();
|
||||
|
||||
while (this.token != ARRAY_END) {
|
||||
if (ret.size() >= this.maxElements) {
|
||||
throw new JSONException("JSON array exceeds maximum allowed elements ("
|
||||
+ this.maxElements + "). Use " + JSONConstants.JSON_MAX_ELEMENTS + " to increase the limit.");
|
||||
}
|
||||
ret.add(value);
|
||||
|
||||
Object read = this.read();
|
||||
if (read == COMMA) {
|
||||
value = this.read();
|
||||
} else if (read != ARRAY_END) {
|
||||
throw buildInvalidInputException();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
} finally {
|
||||
this.depth--;
|
||||
}
|
||||
}
|
||||
|
||||
protected Object number() throws JSONException {
|
||||
this.buf.setLength(0);
|
||||
boolean toDouble = false;
|
||||
|
||||
if (this.c == '-') {
|
||||
this.add();
|
||||
}
|
||||
|
||||
this.addDigits();
|
||||
|
||||
if (this.c == '.') {
|
||||
toDouble = true;
|
||||
this.add();
|
||||
this.addDigits();
|
||||
}
|
||||
|
||||
if ((this.c == 'e') || (this.c == 'E')) {
|
||||
toDouble = true;
|
||||
this.add();
|
||||
|
||||
if ((this.c == '+') || (this.c == '-')) {
|
||||
this.add();
|
||||
}
|
||||
|
||||
this.addDigits();
|
||||
}
|
||||
|
||||
if (toDouble) {
|
||||
try {
|
||||
return Double.parseDouble(this.buf.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
throw buildInvalidInputException();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return Long.parseLong(this.buf.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
throw buildInvalidInputException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Object string(char quote) throws JSONException {
|
||||
this.buf.setLength(0);
|
||||
|
||||
while ((this.c != quote) && (this.c != CharacterIterator.DONE)) {
|
||||
if (this.c == '\\') {
|
||||
this.next();
|
||||
|
||||
if (this.c == 'u') {
|
||||
this.add(this.unicode());
|
||||
} else {
|
||||
Character value = escapes.get(this.c);
|
||||
|
||||
if (value != null) {
|
||||
this.add(value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.add();
|
||||
}
|
||||
if (this.buf.length() > this.maxStringLength) {
|
||||
throw new JSONException("JSON string exceeds maximum allowed length ("
|
||||
+ this.maxStringLength + "). Use " + JSONConstants.JSON_MAX_STRING_LENGTH + " to increase the limit.");
|
||||
}
|
||||
}
|
||||
|
||||
this.next();
|
||||
|
||||
return this.buf.toString();
|
||||
}
|
||||
|
||||
protected void add(char cc) {
|
||||
this.buf.append(cc);
|
||||
this.next();
|
||||
}
|
||||
|
||||
protected void add() {
|
||||
this.add(this.c);
|
||||
}
|
||||
|
||||
protected void addDigits() {
|
||||
while (Character.isDigit(this.c)) {
|
||||
this.add();
|
||||
}
|
||||
}
|
||||
|
||||
protected char unicode() {
|
||||
int value = 0;
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
value = switch (this.next()) {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> (value << 4) + (this.c - '0');
|
||||
case 'a', 'b', 'c', 'd', 'e', 'f' -> (value << 4) + (this.c - 'W');
|
||||
case 'A', 'B', 'C', 'D', 'E', 'F' -> (value << 4) + (this.c - '7');
|
||||
default -> value;
|
||||
};
|
||||
}
|
||||
|
||||
return (char) value;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -67,9 +67,9 @@ import java.util.regex.Pattern;
|
||||
* references are detected they will be nulled out.
|
||||
* </p>
|
||||
*/
|
||||
public class DefaultJSONWriter implements JSONWriter {
|
||||
public class StrutsJSONWriter implements JSONWriter {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(DefaultJSONWriter.class);
|
||||
private static final Logger LOG = LogManager.getLogger(StrutsJSONWriter.class);
|
||||
|
||||
private static final char[] hex = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
+64
@@ -27,16 +27,28 @@ import org.apache.struts2.json.JSONConstants;
|
||||
|
||||
public class JSONConstantConfig extends ConstantConfig {
|
||||
private BeanConfig jsonWriter;
|
||||
private BeanConfig jsonReader;
|
||||
private Boolean jsonResultExcludeProxyProperties;
|
||||
private String jsonDateFormat;
|
||||
private Integer jsonMaxElements;
|
||||
private Integer jsonMaxDepth;
|
||||
private Integer jsonMaxLength;
|
||||
private Integer jsonMaxStringLength;
|
||||
private Integer jsonMaxKeyLength;
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAllAsStringsMap() {
|
||||
Map<String, String> map = super.getAllAsStringsMap();
|
||||
|
||||
map.put(JSONConstants.JSON_WRITER, beanConfToString(jsonWriter));
|
||||
map.put(JSONConstants.JSON_READER, beanConfToString(jsonReader));
|
||||
map.put(JSONConstants.RESULT_EXCLUDE_PROXY_PROPERTIES, Objects.toString(jsonResultExcludeProxyProperties, null));
|
||||
map.put(JSONConstants.DATE_FORMAT, jsonDateFormat);
|
||||
map.put(JSONConstants.JSON_MAX_ELEMENTS, Objects.toString(jsonMaxElements, null));
|
||||
map.put(JSONConstants.JSON_MAX_DEPTH, Objects.toString(jsonMaxDepth, null));
|
||||
map.put(JSONConstants.JSON_MAX_LENGTH, Objects.toString(jsonMaxLength, null));
|
||||
map.put(JSONConstants.JSON_MAX_STRING_LENGTH, Objects.toString(jsonMaxStringLength, null));
|
||||
map.put(JSONConstants.JSON_MAX_KEY_LENGTH, Objects.toString(jsonMaxKeyLength, null));
|
||||
|
||||
return map;
|
||||
}
|
||||
@@ -68,4 +80,56 @@ public class JSONConstantConfig extends ConstantConfig {
|
||||
public void setJsonDateFormat(String jsonDateFormat) {
|
||||
this.jsonDateFormat = jsonDateFormat;
|
||||
}
|
||||
|
||||
public BeanConfig getJsonReader() {
|
||||
return jsonReader;
|
||||
}
|
||||
|
||||
public void setJsonReader(BeanConfig jsonReader) {
|
||||
this.jsonReader = jsonReader;
|
||||
}
|
||||
|
||||
public void setJsonReader(Class<?> clazz) {
|
||||
this.jsonReader = new BeanConfig(clazz, clazz.getName());
|
||||
}
|
||||
|
||||
public Integer getJsonMaxElements() {
|
||||
return jsonMaxElements;
|
||||
}
|
||||
|
||||
public void setJsonMaxElements(Integer jsonMaxElements) {
|
||||
this.jsonMaxElements = jsonMaxElements;
|
||||
}
|
||||
|
||||
public Integer getJsonMaxDepth() {
|
||||
return jsonMaxDepth;
|
||||
}
|
||||
|
||||
public void setJsonMaxDepth(Integer jsonMaxDepth) {
|
||||
this.jsonMaxDepth = jsonMaxDepth;
|
||||
}
|
||||
|
||||
public Integer getJsonMaxLength() {
|
||||
return jsonMaxLength;
|
||||
}
|
||||
|
||||
public void setJsonMaxLength(Integer jsonMaxLength) {
|
||||
this.jsonMaxLength = jsonMaxLength;
|
||||
}
|
||||
|
||||
public Integer getJsonMaxStringLength() {
|
||||
return jsonMaxStringLength;
|
||||
}
|
||||
|
||||
public void setJsonMaxStringLength(Integer jsonMaxStringLength) {
|
||||
this.jsonMaxStringLength = jsonMaxStringLength;
|
||||
}
|
||||
|
||||
public Integer getJsonMaxKeyLength() {
|
||||
return jsonMaxKeyLength;
|
||||
}
|
||||
|
||||
public void setJsonMaxKeyLength(Integer jsonMaxKeyLength) {
|
||||
this.jsonMaxKeyLength = jsonMaxKeyLength;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,20 @@
|
||||
"https://struts.apache.org/dtds/struts-6.0.dtd">
|
||||
|
||||
<struts>
|
||||
<bean type="org.apache.struts2.json.JSONWriter" name="struts" class="org.apache.struts2.json.DefaultJSONWriter"
|
||||
<bean type="org.apache.struts2.json.JSONWriter" name="struts" class="org.apache.struts2.json.StrutsJSONWriter"
|
||||
scope="prototype"/>
|
||||
<bean type="org.apache.struts2.json.JSONReader" name="struts" class="org.apache.struts2.json.StrutsJSONReader"
|
||||
scope="prototype"/>
|
||||
<constant name="struts.json.writer" value="struts"/>
|
||||
<!-- TODO: Make DefaultJSONWriter thread-safe to remove "prototype"s -->
|
||||
<bean class="org.apache.struts2.json.JSONUtil" scope="prototype"/>
|
||||
|
||||
<constant name="struts.json.writer" value="struts"/>
|
||||
<constant name="struts.json.reader" value="struts"/>
|
||||
<constant name="struts.json.maxElements" value="10000"/>
|
||||
<constant name="struts.json.maxDepth" value="64"/>
|
||||
<constant name="struts.json.maxLength" value="2097152"/>
|
||||
<constant name="struts.json.maxStringLength" value="262144"/>
|
||||
<constant name="struts.json.maxKeyLength" value="512"/>
|
||||
|
||||
<package name="json-default" extends="struts-default">
|
||||
|
||||
<result-types>
|
||||
@@ -54,4 +62,6 @@
|
||||
</interceptors>
|
||||
|
||||
</package>
|
||||
|
||||
<bean-selection name="jsonBeans" class="org.apache.struts2.json.JSONBeanSelectionProvider"/>
|
||||
</struts>
|
||||
|
||||
@@ -44,11 +44,11 @@ public class JSONEnumTest extends TestCase {
|
||||
bean1.setEnumField(AnEnum.ValueA);
|
||||
bean1.setEnumBean(AnEnumBean.Two);
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
jsonWriter.setEnumAsBean(false);
|
||||
String json = jsonWriter.write(bean1);
|
||||
|
||||
Map result = (Map) JSONUtil.deserialize(json);
|
||||
Map result = (Map) new StrutsJSONReader().read(json);
|
||||
assertEquals("str", result.get("stringField"));
|
||||
assertEquals(true, result.get("booleanField"));
|
||||
assertEquals("s", result.get("charField")); // note: this is a
|
||||
@@ -86,11 +86,11 @@ public class JSONEnumTest extends TestCase {
|
||||
bean1.setEnumField(AnEnum.ValueA);
|
||||
bean1.setEnumBean(AnEnumBean.Two);
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
jsonWriter.setEnumAsBean(true);
|
||||
String json = jsonWriter.write(bean1);
|
||||
|
||||
Map result = (Map) JSONUtil.deserialize(json);
|
||||
Map result = (Map) new StrutsJSONReader().read(json);
|
||||
assertEquals("str", result.get("stringField"));
|
||||
assertEquals(true, result.get("booleanField"));
|
||||
assertEquals("s", result.get("charField")); // note: this is a
|
||||
|
||||
@@ -41,6 +41,15 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
this.request.setContent(content.getBytes());
|
||||
}
|
||||
|
||||
private JSONInterceptor createInterceptor() {
|
||||
JSONInterceptor interceptor = new JSONInterceptor();
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setReader(new StrutsJSONReader());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
interceptor.setJsonUtil(jsonUtil);
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
public void testBadJSON1() throws Exception {
|
||||
tryBadJSON("bad-1.txt");
|
||||
}
|
||||
@@ -70,7 +79,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
setRequestContent(fileName);
|
||||
this.request.addHeader("Content-Type", "application/json; charset=UTF-8");
|
||||
|
||||
JSONInterceptor interceptor = new JSONInterceptor();
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
interceptor.setEnableSMD(true);
|
||||
SMDActionTest1 action = new SMDActionTest1();
|
||||
|
||||
@@ -91,7 +100,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
setRequestContent("smd-3.txt");
|
||||
this.request.addHeader("Content-Type", "application/json-rpc");
|
||||
|
||||
JSONInterceptor interceptor = new JSONInterceptor();
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
SMDActionTest1 action = new SMDActionTest1();
|
||||
|
||||
this.invocation.setAction(action);
|
||||
@@ -110,7 +119,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
setRequestContent("smd-14.txt");
|
||||
this.request.addHeader("Content-Type", "application/json-rpc");
|
||||
|
||||
JSONInterceptor interceptor = new JSONInterceptor();
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
interceptor.setEnableSMD(true);
|
||||
SMDActionTest2 action = new SMDActionTest2();
|
||||
|
||||
@@ -128,7 +137,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
setRequestContent("smd-15.txt");
|
||||
this.request.addHeader("Content-Type", "application/json-rpc");
|
||||
|
||||
JSONInterceptor interceptor = new JSONInterceptor();
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
interceptor.setEnableSMD(true);
|
||||
SMDActionTest2 action = new SMDActionTest2();
|
||||
|
||||
@@ -146,7 +155,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
setRequestContent("smd-4.txt");
|
||||
this.request.addHeader("Content-Type", "application/json-rpc");
|
||||
|
||||
JSONInterceptor interceptor = new JSONInterceptor();
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
interceptor.setEnableSMD(true);
|
||||
SMDActionTest1 action = new SMDActionTest1();
|
||||
|
||||
@@ -170,7 +179,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
setRequestContent("smd-9.txt");
|
||||
this.request.addHeader("Content-Type", "application/json-rpc");
|
||||
|
||||
JSONInterceptor interceptor = new JSONInterceptor();
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
interceptor.setEnableSMD(true);
|
||||
SMDActionTest1 action = new SMDActionTest1();
|
||||
|
||||
@@ -191,7 +200,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
setRequestContent("smd-6.txt");
|
||||
this.request.addHeader("Content-Type", "application/json-rpc");
|
||||
|
||||
JSONInterceptor interceptor = new JSONInterceptor();
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
interceptor.setEnableSMD(true);
|
||||
SMDActionTest1 action = new SMDActionTest1();
|
||||
|
||||
@@ -226,7 +235,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
setRequestContent("smd-10.txt");
|
||||
this.request.addHeader("Content-Type", "application/json-rpc");
|
||||
|
||||
JSONInterceptor interceptor = new JSONInterceptor();
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
interceptor.setEnableSMD(true);
|
||||
SMDActionTest2 action = new SMDActionTest2();
|
||||
|
||||
@@ -251,7 +260,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
setRequestContent("smd-7.txt");
|
||||
this.request.addHeader("Content-Type", "application/json-rpc");
|
||||
|
||||
JSONInterceptor interceptor = new JSONInterceptor();
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
interceptor.setEnableSMD(true);
|
||||
SMDActionTest1 action = new SMDActionTest1();
|
||||
|
||||
@@ -300,7 +309,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
this.request.addHeader("Content-Type", "application/json");
|
||||
|
||||
// interceptor
|
||||
JSONInterceptor interceptor = new JSONInterceptor();
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
TestAction action = new TestAction();
|
||||
|
||||
this.invocation.setAction(action);
|
||||
@@ -315,7 +324,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
this.request.addHeader("Content-Type", "application/json");
|
||||
|
||||
// interceptor
|
||||
JSONInterceptor interceptor = new JSONInterceptor();
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
TestAction action = new TestAction();
|
||||
|
||||
this.invocation.setAction(action);
|
||||
@@ -437,7 +446,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
this.request.addHeader("Content-Type", "application/json");
|
||||
|
||||
// interceptor
|
||||
JSONInterceptor interceptor = new JSONInterceptor();
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
interceptor.setRoot("bean");
|
||||
TestAction4 action = new TestAction4();
|
||||
|
||||
@@ -462,7 +471,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
this.request.addHeader("Content-Type", "application/json");
|
||||
|
||||
// interceptor
|
||||
JSONInterceptor interceptor = new JSONInterceptor();
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
interceptor.setRoot("beans");
|
||||
TestAction5 action = new TestAction5();
|
||||
|
||||
@@ -488,7 +497,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
this.request.addHeader("Content-Type", "application/json");
|
||||
|
||||
// interceptor
|
||||
JSONInterceptor interceptor = new JSONInterceptor();
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
interceptor.setRoot("anotherBean.yetAnotherBean.beans");
|
||||
TestAction5 action = new TestAction5();
|
||||
|
||||
@@ -509,6 +518,63 @@ public class JSONInterceptorTest extends StrutsTestCase {
|
||||
assertEquals(beans.get(0).getByteField(), 3);
|
||||
}
|
||||
|
||||
public void testMaxLengthExceededThrows() throws Exception {
|
||||
// Body is 27 bytes, set maxLength to 10
|
||||
this.request.setContent("{\"a\":1, \"b\":2, \"c\":\"hello\"}".getBytes());
|
||||
this.request.addHeader("Content-Type", "application/json");
|
||||
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
interceptor.setMaxLength("10");
|
||||
TestAction action = new TestAction();
|
||||
|
||||
this.invocation.setAction(action);
|
||||
|
||||
try {
|
||||
interceptor.intercept(this.invocation);
|
||||
fail("Should have thrown JSONException for exceeding maxLength");
|
||||
} catch (JSONException e) {
|
||||
assertTrue(e.getMessage().contains(JSONConstants.JSON_MAX_LENGTH));
|
||||
}
|
||||
}
|
||||
|
||||
public void testMaxDepthEnforcedThroughInterceptor() throws Exception {
|
||||
// Nested 3 levels deep, set maxDepth to 2
|
||||
this.request.setContent("{\"a\":{\"b\":{\"c\":1}}}".getBytes());
|
||||
this.request.addHeader("Content-Type", "application/json");
|
||||
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
interceptor.setMaxDepth("2");
|
||||
TestAction action = new TestAction();
|
||||
|
||||
this.invocation.setAction(action);
|
||||
|
||||
try {
|
||||
interceptor.intercept(this.invocation);
|
||||
fail("Should have thrown JSONException for exceeding maxDepth");
|
||||
} catch (JSONException e) {
|
||||
assertTrue(e.getMessage().contains("maximum allowed depth"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testMaxElementsEnforcedThroughInterceptor() throws Exception {
|
||||
// JSON object with 5 keys, set maxElements to 3
|
||||
this.request.setContent("{\"a\":1, \"b\":2, \"c\":3, \"d\":4, \"e\":5}".getBytes());
|
||||
this.request.addHeader("Content-Type", "application/json");
|
||||
|
||||
JSONInterceptor interceptor = createInterceptor();
|
||||
interceptor.setMaxElements("3");
|
||||
TestAction action = new TestAction();
|
||||
|
||||
this.invocation.setAction(action);
|
||||
|
||||
try {
|
||||
interceptor.intercept(this.invocation);
|
||||
fail("Should have thrown JSONException for exceeding maxElements");
|
||||
} catch (JSONException e) {
|
||||
assertTrue(e.getMessage().contains("maximum allowed elements"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.apache.struts2.junit.util.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.beans.IntrospectionException;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
@@ -74,9 +73,8 @@ public class JSONPopulatorTest {
|
||||
|
||||
@Test
|
||||
public void testPrimitiveBean() throws Exception {
|
||||
StringReader stringReader = new StringReader(TestUtils.readContent(JSONInterceptorTest.class
|
||||
.getResource("json-7.txt")));
|
||||
Object json = JSONUtil.deserialize(stringReader);
|
||||
String text = TestUtils.readContent(JSONInterceptorTest.class.getResource("json-7.txt"));
|
||||
Object json = new StrutsJSONReader().read(text);
|
||||
assertNotNull(json);
|
||||
assertTrue(json instanceof Map);
|
||||
Map<?, ?> jsonMap = (Map<?, ?>) json;
|
||||
@@ -96,7 +94,7 @@ public class JSONPopulatorTest {
|
||||
@Test
|
||||
public void testObjectBean() throws Exception {
|
||||
String text = TestUtils.readContent(JSONInterceptorTest.class.getResource("json-7.txt"));
|
||||
Object json = JSONUtil.deserialize(text);
|
||||
Object json = new StrutsJSONReader().read(text);
|
||||
assertNotNull(json);
|
||||
assertTrue(json instanceof Map);
|
||||
Map<?, ?> jsonMap = (Map<?, ?>) json;
|
||||
@@ -162,9 +160,8 @@ public class JSONPopulatorTest {
|
||||
|
||||
@Test
|
||||
public void testObjectBeanWithStrings() throws Exception {
|
||||
StringReader stringReader = new StringReader(TestUtils.readContent(JSONInterceptorTest.class
|
||||
.getResource("json-8.txt")));
|
||||
Object json = JSONUtil.deserialize(stringReader);
|
||||
String text = TestUtils.readContent(JSONInterceptorTest.class.getResource("json-8.txt"));
|
||||
Object json = new StrutsJSONReader().read(text);
|
||||
assertNotNull(json);
|
||||
assertTrue(json instanceof Map);
|
||||
Map<?, ?> jsonMap = (Map<?, ?>) json;
|
||||
@@ -187,7 +184,7 @@ public class JSONPopulatorTest {
|
||||
@Test
|
||||
public void testInfiniteLoop() {
|
||||
try {
|
||||
JSONReader reader = new JSONReader();
|
||||
JSONReader reader = new StrutsJSONReader();
|
||||
reader.read("[1,\"a]");
|
||||
fail("Should have thrown an exception");
|
||||
} catch (JSONException e) {
|
||||
@@ -198,7 +195,7 @@ public class JSONPopulatorTest {
|
||||
@Test
|
||||
public void testParseBadInput() {
|
||||
try {
|
||||
JSONReader reader = new JSONReader();
|
||||
JSONReader reader = new StrutsJSONReader();
|
||||
reader.read("[1,\"a\"1]");
|
||||
fail("Should have thrown an exception");
|
||||
} catch (JSONException e) {
|
||||
|
||||
@@ -29,7 +29,7 @@ import static org.junit.Assert.assertNotNull;
|
||||
* Time: 17.26
|
||||
*/
|
||||
public class JSONReaderTest {
|
||||
private JSONReader reader = new JSONReader();
|
||||
private StrutsJSONReader reader = new StrutsJSONReader();
|
||||
|
||||
@Test
|
||||
public void testExponentialNumber() throws Exception {
|
||||
|
||||
@@ -66,7 +66,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
map.put("createtime", new Date());
|
||||
try {
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
jsonUtil.serialize(map, JSONUtil.CACHE_BEAN_INFO_DEFAULT);
|
||||
} catch (JSONException e) {
|
||||
fail(e.getMessage());
|
||||
@@ -76,14 +76,14 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
public void testJSONWriterEndlessLoopOnExludedProperties() throws JSONException {
|
||||
Pattern all = Pattern.compile(".*");
|
||||
|
||||
JSONWriter writer = new DefaultJSONWriter();
|
||||
JSONWriter writer = new StrutsJSONWriter();
|
||||
writer.write(Arrays.asList("a", "b"), Arrays.asList(all), null, false);
|
||||
}
|
||||
|
||||
public void testSMDDisabledSMD() throws Exception {
|
||||
JSONResult result = new JSONResult();
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
SMDActionTest1 action = new SMDActionTest1();
|
||||
stack.push(action);
|
||||
@@ -102,7 +102,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
JSONResult result = new JSONResult();
|
||||
result.setEnableSMD(true);
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
SMDActionTest1 action = new SMDActionTest1();
|
||||
stack.push(action);
|
||||
@@ -122,7 +122,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
JSONResult result = new JSONResult();
|
||||
result.setEnableSMD(true);
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
SMDActionTest2 action = new SMDActionTest2();
|
||||
stack.push(action);
|
||||
@@ -142,7 +142,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
JSONResult result = new JSONResult();
|
||||
result.setExcludeNullProperties(true);
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
TestAction action = new TestAction();
|
||||
stack.push(action);
|
||||
@@ -161,7 +161,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
public void testNotTraverseOrIncludeProxyInfo() throws Exception {
|
||||
JSONResult result = new JSONResult();
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
DefaultJSONWriter writer = new DefaultJSONWriter();
|
||||
StrutsJSONWriter writer = new StrutsJSONWriter();
|
||||
writer.setProxyService(proxyService);
|
||||
jsonUtil.setWriter(writer);
|
||||
result.setJsonUtil(jsonUtil);
|
||||
@@ -195,7 +195,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
JSONResult result = new JSONResult();
|
||||
result.setWrapPrefix("_prefix_");
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
TestAction2 action = new TestAction2();
|
||||
stack.push(action);
|
||||
@@ -214,7 +214,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
JSONResult result = new JSONResult();
|
||||
result.setWrapSuffix("_suffix_");
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
TestAction2 action = new TestAction2();
|
||||
stack.push(action);
|
||||
@@ -233,7 +233,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
JSONResult result = new JSONResult();
|
||||
result.setDefaultDateFormat("MM-dd-yyyy");
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
|
||||
|
||||
@@ -254,7 +254,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
result.setWrapPrefix("_prefix_");
|
||||
result.setWrapSuffix("_suffix_");
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
TestAction2 action = new TestAction2();
|
||||
stack.push(action);
|
||||
@@ -274,7 +274,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
result.setExcludeNullProperties(true);
|
||||
result.setPrefix(true);
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
TestAction action = new TestAction();
|
||||
stack.push(action);
|
||||
@@ -294,7 +294,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
public void test() throws Exception {
|
||||
JSONResult result = new JSONResult();
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
|
||||
TestAction action = new TestAction();
|
||||
@@ -380,7 +380,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
JSONResult result = new JSONResult();
|
||||
result.setIgnoreHierarchy(false);
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
|
||||
TestAction3 action = new TestAction3();
|
||||
@@ -399,7 +399,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
public void testCommentWrap() throws Exception {
|
||||
JSONResult result = new JSONResult();
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
|
||||
TestAction action = new TestAction();
|
||||
@@ -481,7 +481,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
|
||||
private void executeTest2Action(JSONResult result) throws Exception {
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
TestAction action = new TestAction();
|
||||
stack.push(action);
|
||||
@@ -526,7 +526,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
JSONResult result = new JSONResult();
|
||||
result.setCallbackParameter("callback");
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
request.addParameter("callback", "exec");
|
||||
|
||||
@@ -543,7 +543,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
JSONResult result = new JSONResult();
|
||||
result.setNoCache(true);
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
|
||||
executeTest2Action(result);
|
||||
@@ -557,7 +557,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
JSONResult result = new JSONResult();
|
||||
result.setContentType("some_super_content");
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
|
||||
executeTest2Action(result);
|
||||
@@ -569,7 +569,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
JSONResult result = new JSONResult();
|
||||
result.setStatusCode(HttpServletResponse.SC_CONTINUE);
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
|
||||
executeTest2Action(result);
|
||||
@@ -584,7 +584,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
JSONResult result = new JSONResult();
|
||||
result.setEnumAsBean(true);
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
|
||||
executeTest2Action(result);
|
||||
@@ -605,7 +605,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
JSONResult result = new JSONResult();
|
||||
result.setIncludeProperties("foo");
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
TestAction action = new TestAction();
|
||||
stack.push(action);
|
||||
@@ -625,7 +625,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
JSONResult result = new JSONResult();
|
||||
result.setIncludeProperties("^list\\[\\d+\\]\\.booleanField");
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
TestAction action = new TestAction();
|
||||
stack.push(action);
|
||||
@@ -652,7 +652,7 @@ public class JSONResultTest extends StrutsTestCase {
|
||||
JSONResult result = new JSONResult();
|
||||
result.setIncludeProperties("^set\\[\\d+\\]\\.list\\[\\d+\\]\\.booleanField");
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
result.setJsonUtil(jsonUtil);
|
||||
TestAction action = new TestAction();
|
||||
stack.push(action);
|
||||
|
||||
@@ -44,10 +44,10 @@ public class JSONUtilTest extends TestCase {
|
||||
bean1.setEnumBean(AnEnumBean.Two);
|
||||
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
String json = jsonUtil.serialize(bean1, JSONUtil.CACHE_BEAN_INFO_DEFAULT);
|
||||
|
||||
Map result = (Map) JSONUtil.deserialize(json);
|
||||
Map result = (Map) new StrutsJSONReader().read(json);
|
||||
assertEquals("str", result.get("stringField"));
|
||||
assertEquals(true, result.get("booleanField"));
|
||||
assertEquals("s", result.get("charField")); // note: this is a
|
||||
@@ -73,7 +73,7 @@ public class JSONUtilTest extends TestCase {
|
||||
List<Pattern> includeProperties = JSONUtil.processIncludePatterns(JSONUtil.asSet("listOfLists,listOfLists\\[\\d+\\]\\[\\d+\\]"), JSONUtil.REGEXP_PATTERN);
|
||||
|
||||
JSONUtil jsonUtil = new JSONUtil();
|
||||
jsonUtil.setWriter(new DefaultJSONWriter());
|
||||
jsonUtil.setWriter(new StrutsJSONWriter());
|
||||
String actual = jsonUtil.serialize(bean, null, new ArrayList<Pattern>(includeProperties), false, false);
|
||||
|
||||
assertEquals("{\"listOfLists\":[[\"1\",\"2\"],[\"3\",\"4\"],[\"5\",\"6\"],[\"7\",\"8\"],[\"9\",\"0\"]]}", actual);
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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.struts2.json;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class StrutsJSONReaderTest {
|
||||
|
||||
@Test
|
||||
public void testArrayExceedingMaxElementsThrows() {
|
||||
var reader = new StrutsJSONReader();
|
||||
reader.setMaxElements(3);
|
||||
String json = "[1, 2, 3, 4]";
|
||||
var ex = assertThrows(JSONException.class, () -> reader.read(json));
|
||||
assertTrue(ex.getMessage().contains("maximum allowed elements (3)"));
|
||||
assertTrue(ex.getMessage().contains(JSONConstants.JSON_MAX_ELEMENTS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrayAtExactMaxElementsAllowed() throws Exception {
|
||||
var reader = new StrutsJSONReader();
|
||||
reader.setMaxElements(3);
|
||||
// Exactly 3 elements: check is >= before add, so 3 elements fit (size 0,1,2 when checked)
|
||||
var result = reader.read("[1, 2, 3]");
|
||||
assertNotNull(result);
|
||||
assertEquals(3, ((List<?>) result).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testObjectExceedingMaxElementsThrows() {
|
||||
var reader = new StrutsJSONReader();
|
||||
reader.setMaxElements(2);
|
||||
String json = "{\"a\":1, \"b\":2, \"c\":3}";
|
||||
var ex = assertThrows(JSONException.class, () -> reader.read(json));
|
||||
assertTrue(ex.getMessage().contains("maximum allowed elements (2)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestingExceedingMaxDepthThrows() {
|
||||
var reader = new StrutsJSONReader();
|
||||
reader.setMaxDepth(2);
|
||||
String json = "{\"a\":{\"b\":{\"c\":1}}}";
|
||||
var ex = assertThrows(JSONException.class, () -> reader.read(json));
|
||||
assertTrue(ex.getMessage().contains("maximum allowed depth (2)"));
|
||||
assertTrue(ex.getMessage().contains(JSONConstants.JSON_MAX_DEPTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrayNestingExceedingMaxDepthThrows() {
|
||||
var reader = new StrutsJSONReader();
|
||||
reader.setMaxDepth(2);
|
||||
String json = "[[[1]]]";
|
||||
var ex = assertThrows(JSONException.class, () -> reader.read(json));
|
||||
assertTrue(ex.getMessage().contains("maximum allowed depth (2)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringAtExactMaxStringLengthAllowed() throws Exception {
|
||||
var reader = new StrutsJSONReader();
|
||||
reader.setMaxStringLength(5);
|
||||
// String "abcde" has length exactly 5, should be allowed (check is >)
|
||||
Object result = reader.read("\"abcde\"");
|
||||
assertEquals("abcde", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringExceedingMaxStringLengthThrows() {
|
||||
var reader = new StrutsJSONReader();
|
||||
reader.setMaxStringLength(5);
|
||||
String json = "\"abcdefghij\"";
|
||||
var ex = assertThrows(JSONException.class, () -> reader.read(json));
|
||||
assertTrue(ex.getMessage().contains("maximum allowed length (5)"));
|
||||
assertTrue(ex.getMessage().contains(JSONConstants.JSON_MAX_STRING_LENGTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testObjectKeyExceedingMaxKeyLengthThrowsOnFirstKey() {
|
||||
var reader = new StrutsJSONReader();
|
||||
reader.setMaxKeyLength(3);
|
||||
String json = "{\"longkey\":1}";
|
||||
var ex = assertThrows(JSONException.class, () -> reader.read(json));
|
||||
assertTrue(ex.getMessage().contains("maximum allowed length (3)"));
|
||||
assertTrue(ex.getMessage().contains(JSONConstants.JSON_MAX_KEY_LENGTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testObjectKeyExceedingMaxKeyLengthThrowsOnSubsequentKey() {
|
||||
var reader = new StrutsJSONReader();
|
||||
reader.setMaxKeyLength(3);
|
||||
// First key "a" is within limit, second key "longkey" exceeds it
|
||||
String json = "{\"a\":1, \"longkey\":2}";
|
||||
var ex = assertThrows(JSONException.class, () -> reader.read(json));
|
||||
assertTrue(ex.getMessage().contains("maximum allowed length (3)"));
|
||||
assertTrue(ex.getMessage().contains(JSONConstants.JSON_MAX_KEY_LENGTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testObjectKeyAtExactMaxKeyLengthAllowed() throws Exception {
|
||||
var reader = new StrutsJSONReader();
|
||||
reader.setMaxKeyLength(3);
|
||||
// Key "abc" has length exactly 3, should be allowed (check is >)
|
||||
var result = reader.read("{\"abc\":1}");
|
||||
assertTrue(result instanceof Map);
|
||||
assertEquals(1L, ((Map<?, ?>) result).get("abc"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultLimitsAllowTypicalPayload() throws Exception {
|
||||
var reader = new StrutsJSONReader();
|
||||
var json = "{\"name\":\"test\", \"values\":[1, 2, 3], \"nested\":{\"key\":\"value\"}}";
|
||||
assertTrue(reader.read(json) instanceof Map);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDepthCounterResetsAfterParsing() throws Exception {
|
||||
var reader = new StrutsJSONReader();
|
||||
reader.setMaxDepth(3);
|
||||
|
||||
// First parse: uses depth up to 2
|
||||
assertTrue(reader.read("{\"a\":{\"b\":1}}") instanceof Map);
|
||||
|
||||
// Second parse: should work fine if depth was reset
|
||||
assertTrue(reader.read("{\"a\":{\"b\":1}}") instanceof Map);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixedNestingDepth() {
|
||||
var reader = new StrutsJSONReader();
|
||||
reader.setMaxDepth(2);
|
||||
// array inside object inside array = depth 3, should fail at depth 2
|
||||
String json = "[{\"a\":[1]}]";
|
||||
var ex = assertThrows(JSONException.class, () -> reader.read(json));
|
||||
assertTrue(ex.getMessage().contains("maximum allowed depth"));
|
||||
}
|
||||
}
|
||||
+24
-23
@@ -22,6 +22,7 @@ import org.apache.struts2.json.annotations.JSONFieldBridge;
|
||||
import org.apache.struts2.junit.util.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
@@ -43,7 +44,7 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DefaultJSONWriterTest {
|
||||
public class StrutsJSONWriterTest {
|
||||
|
||||
@Test
|
||||
public void testWrite() throws Exception {
|
||||
@@ -58,10 +59,10 @@ public class DefaultJSONWriterTest {
|
||||
bean1.setEnumField(AnEnum.ValueA);
|
||||
bean1.setEnumBean(AnEnumBean.Two);
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
jsonWriter.setEnumAsBean(false);
|
||||
String json = jsonWriter.write(bean1);
|
||||
TestUtils.assertEquals(DefaultJSONWriter.class.getResource("jsonwriter-write-bean-01.txt"), json);
|
||||
TestUtils.assertEquals(StrutsJSONWriter.class.getResource("jsonwriter-write-bean-01.txt"), json);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -83,11 +84,11 @@ public class DefaultJSONWriterTest {
|
||||
m.put("c", "z");
|
||||
bean1.setMap(m);
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
jsonWriter.setEnumAsBean(false);
|
||||
jsonWriter.setIgnoreHierarchy(false);
|
||||
String json = jsonWriter.write(bean1, null, null, true);
|
||||
TestUtils.assertEquals(DefaultJSONWriter.class.getResource("jsonwriter-write-bean-03.txt"), json);
|
||||
TestUtils.assertEquals(StrutsJSONWriter.class.getResource("jsonwriter-write-bean-03.txt"), json);
|
||||
}
|
||||
|
||||
private static class BeanWithMap extends Bean {
|
||||
@@ -114,13 +115,13 @@ public class DefaultJSONWriterTest {
|
||||
bean1.setLongField(100);
|
||||
bean1.setEnumField(AnEnum.ValueA);
|
||||
bean1.setEnumBean(AnEnumBean.Two);
|
||||
bean1.setUrl(new URL("http://www.google.com"));
|
||||
bean1.setUrl(URI.create("https://www.google.com").toURL());
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
jsonWriter.setEnumAsBean(false);
|
||||
jsonWriter.setIgnoreHierarchy(false);
|
||||
String json = jsonWriter.write(bean1);
|
||||
TestUtils.assertEquals(DefaultJSONWriter.class.getResource("jsonwriter-write-bean-02.txt"), json);
|
||||
TestUtils.assertEquals(StrutsJSONWriter.class.getResource("jsonwriter-write-bean-02.txt"), json);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -139,11 +140,11 @@ public class DefaultJSONWriterTest {
|
||||
errors.add("Field is required");
|
||||
bean1.setErrors(errors);
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
jsonWriter.setEnumAsBean(false);
|
||||
jsonWriter.setIgnoreHierarchy(false);
|
||||
String json = jsonWriter.write(bean1);
|
||||
TestUtils.assertEquals(DefaultJSONWriter.class.getResource("jsonwriter-write-bean-04.txt"), json);
|
||||
TestUtils.assertEquals(StrutsJSONWriter.class.getResource("jsonwriter-write-bean-04.txt"), json);
|
||||
}
|
||||
|
||||
private static class BeanWithList extends Bean {
|
||||
@@ -178,7 +179,7 @@ public class DefaultJSONWriterTest {
|
||||
SingleDateBean dateBean = new SingleDateBean();
|
||||
dateBean.setDate(sdf.parse("2012-12-23 10:10:10 GMT"));
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
jsonWriter.setEnumAsBean(false);
|
||||
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
|
||||
@@ -193,7 +194,7 @@ public class DefaultJSONWriterTest {
|
||||
SingleDateBean dateBean = new SingleDateBean();
|
||||
dateBean.setDate(sdf.parse("2012-12-23 10:10:10 GMT"));
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
jsonWriter.setEnumAsBean(false);
|
||||
jsonWriter.setDateFormatter("MM-dd-yyyy");
|
||||
String json = jsonWriter.write(dateBean);
|
||||
@@ -205,7 +206,7 @@ public class DefaultJSONWriterTest {
|
||||
TemporalBean bean = new TemporalBean();
|
||||
bean.setLocalDate(LocalDate.of(2026, 2, 27));
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
String json = jsonWriter.write(bean);
|
||||
assertTrue(json.contains("\"localDate\":\"2026-02-27\""));
|
||||
}
|
||||
@@ -215,7 +216,7 @@ public class DefaultJSONWriterTest {
|
||||
TemporalBean bean = new TemporalBean();
|
||||
bean.setLocalDateTime(LocalDateTime.of(2026, 2, 27, 12, 0, 0));
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
String json = jsonWriter.write(bean);
|
||||
assertTrue(json.contains("\"localDateTime\":\"2026-02-27T12:00:00\""));
|
||||
}
|
||||
@@ -225,7 +226,7 @@ public class DefaultJSONWriterTest {
|
||||
TemporalBean bean = new TemporalBean();
|
||||
bean.setLocalTime(LocalTime.of(12, 0, 0));
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
String json = jsonWriter.write(bean);
|
||||
assertTrue(json.contains("\"localTime\":\"12:00:00\""));
|
||||
}
|
||||
@@ -235,7 +236,7 @@ public class DefaultJSONWriterTest {
|
||||
TemporalBean bean = new TemporalBean();
|
||||
bean.setZonedDateTime(ZonedDateTime.of(2026, 2, 27, 12, 0, 0, 0, ZoneId.of("Europe/Paris")));
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
String json = jsonWriter.write(bean);
|
||||
assertTrue(json.contains("\"zonedDateTime\":\"2026-02-27T12:00:00+01:00[Europe\\/Paris]\""));
|
||||
}
|
||||
@@ -245,7 +246,7 @@ public class DefaultJSONWriterTest {
|
||||
TemporalBean bean = new TemporalBean();
|
||||
bean.setOffsetDateTime(OffsetDateTime.of(2026, 2, 27, 12, 0, 0, 0, ZoneOffset.ofHours(1)));
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
String json = jsonWriter.write(bean);
|
||||
assertTrue(json.contains("\"offsetDateTime\":\"2026-02-27T12:00:00+01:00\""));
|
||||
}
|
||||
@@ -255,7 +256,7 @@ public class DefaultJSONWriterTest {
|
||||
TemporalBean bean = new TemporalBean();
|
||||
bean.setInstant(Instant.parse("2026-02-27T11:00:00Z"));
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
String json = jsonWriter.write(bean);
|
||||
assertTrue(json.contains("\"instant\":\"2026-02-27T11:00:00Z\""));
|
||||
}
|
||||
@@ -265,7 +266,7 @@ public class DefaultJSONWriterTest {
|
||||
TemporalBean bean = new TemporalBean();
|
||||
bean.setCustomFormatDate(LocalDate.of(2026, 2, 27));
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
String json = jsonWriter.write(bean);
|
||||
assertTrue(json.contains("\"customFormatDate\":\"27\\/02\\/2026\""));
|
||||
}
|
||||
@@ -275,7 +276,7 @@ public class DefaultJSONWriterTest {
|
||||
TemporalBean bean = new TemporalBean();
|
||||
bean.setCustomFormatDateTime(LocalDateTime.of(2026, 2, 27, 14, 30));
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
String json = jsonWriter.write(bean);
|
||||
assertTrue(json.contains("\"customFormatDateTime\":\"27\\/02\\/2026 14:30\""));
|
||||
}
|
||||
@@ -284,7 +285,7 @@ public class DefaultJSONWriterTest {
|
||||
public void testSerializeNullTemporalField() throws Exception {
|
||||
TemporalBean bean = new TemporalBean();
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
String json = jsonWriter.write(bean, null, null, true);
|
||||
assertFalse(json.contains("\"localDate\""));
|
||||
}
|
||||
@@ -294,7 +295,7 @@ public class DefaultJSONWriterTest {
|
||||
TemporalBean bean = new TemporalBean();
|
||||
bean.setCustomFormatInstant(Instant.parse("2026-02-27T11:00:00Z"));
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
String json = jsonWriter.write(bean);
|
||||
assertTrue(json.contains("\"customFormatInstant\":\"2026-02-27 11:00:00\""));
|
||||
}
|
||||
@@ -308,7 +309,7 @@ public class DefaultJSONWriterTest {
|
||||
TemporalBean bean = new TemporalBean();
|
||||
bean.setCalendar(cal);
|
||||
|
||||
JSONWriter jsonWriter = new DefaultJSONWriter();
|
||||
JSONWriter jsonWriter = new StrutsJSONWriter();
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
|
||||
String json = jsonWriter.write(bean);
|
||||
assertTrue(json.contains("\"calendar\":\"2012-12-23T10:10:10\""));
|
||||
+1
-1
@@ -12,5 +12,5 @@
|
||||
"longField":100,
|
||||
"objectField":null,
|
||||
"stringField":"str",
|
||||
"url":"http:\/\/www.google.com"
|
||||
"url":"https:\/\/www.google.com"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user