added JSON constraints to protect clients from malicious or buggy servers
git-svn-id: https://svn.apache.org/repos/asf/chemistry/opencmis/trunk@1751876 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/JSONConstraints.java b/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/JSONConstraints.java
new file mode 100644
index 0000000..3b8fcfe
--- /dev/null
+++ b/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/JSONConstraints.java
@@ -0,0 +1,87 @@
+/*
+ * 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.chemistry.opencmis.commons.impl;
+
+public class JSONConstraints {
+
+ private JSONConstraints() {
+ }
+
+ public static final int MAX_OBJECT_SIZE;
+ public static final int MAX_ARRAY_SIZE;
+ public static final int MAX_DEPTH;
+
+ public static final int MAX_OBJECT_SIZE_DEFAULT = 10000;
+ public static final int MAX_ARRAY_SIZE_DEFAULT = 1000000;
+ public static final int MAX_DEPTH_DEFAULT = 200;
+
+ public static final String MAX_OBJECT_SIZE_SYSTEM_PROPERTY = "org.apache.chemistry.opencmis.JSONConstraints.maxObjectSize";
+ public static final String MAX_ARRAY_SIZE_SYSTEM_PROPERTY = "org.apache.chemistry.opencmis.JSONConstraints.maxArraySize";
+ public static final String MAX_DEPTH_SYSTEM_PROPERTY = "org.apache.chemistry.opencmis.JSONConstraints.maxDepth";
+
+ static {
+ int maxObjectSize = MAX_OBJECT_SIZE_DEFAULT;
+ try {
+ String maxObjectSizeStr = System.getProperty(MAX_OBJECT_SIZE_SYSTEM_PROPERTY);
+ if (maxObjectSizeStr != null) {
+ maxObjectSize = Integer.parseInt(maxObjectSizeStr);
+
+ // check for sane values
+ if (maxObjectSize < 100) {
+ maxObjectSize = MAX_OBJECT_SIZE_DEFAULT;
+ }
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ MAX_OBJECT_SIZE = maxObjectSize;
+
+ int maxArraySize = MAX_ARRAY_SIZE_DEFAULT;
+ try {
+ String maxArraySizeStr = System.getProperty(MAX_ARRAY_SIZE_SYSTEM_PROPERTY);
+ if (maxArraySizeStr != null) {
+ maxArraySize = Integer.parseInt(maxArraySizeStr);
+
+ // check for sane values
+ if (maxArraySize < 1000) {
+ maxArraySize = MAX_ARRAY_SIZE_DEFAULT;
+ }
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ MAX_ARRAY_SIZE = maxArraySize;
+
+ int maxDepth = MAX_DEPTH_DEFAULT;
+ try {
+ String maxDepthStr = System.getProperty(MAX_DEPTH_SYSTEM_PROPERTY);
+ if (maxDepthStr != null) {
+ maxDepth = Integer.parseInt(maxDepthStr);
+
+ // check for sane values
+ if (maxDepth < 10) {
+ maxDepth = MAX_DEPTH_DEFAULT;
+ }
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ MAX_DEPTH = maxDepth;
+ }
+}
diff --git a/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/json/parser/JSONParseException.java b/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/json/parser/JSONParseException.java
index 06e2087..1aa6d6c 100644
--- a/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/json/parser/JSONParseException.java
+++ b/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/json/parser/JSONParseException.java
@@ -21,10 +21,10 @@
/**
* ParseException explains why and where the error occurs in source JSON text.
*
- * (Taken from JSON.simple <http://code.google.com/p/json-simple/> and modified
- * for OpenCMIS.)
+ * (Taken from JSON.simple <http://code.google.com/p/json-simple/> and
+ * modified for OpenCMIS.)
*
- * @author FangYidong<fangyidong@yahoo.com.cn>
+ * @author FangYidong<fangyidong@yahoo.com.cn>
*/
public class JSONParseException extends Exception {
private static final long serialVersionUID = -7880698968187728548L;
@@ -33,6 +33,7 @@
public static final int ERROR_UNEXPECTED_TOKEN = 1;
public static final int ERROR_UNEXPECTED_EXCEPTION = 2;
public static final int ERROR_STRING_TOO_LONG = 3;
+ public static final int ERROR_JSON_TOO_BIG = 4;
private int errorType;
private Object unexpectedObject;
@@ -73,6 +74,9 @@
case ERROR_STRING_TOO_LONG:
sb.append("String too long");
break;
+ case ERROR_JSON_TOO_BIG:
+ sb.append("JSON too big");
+ break;
default:
sb.append("Unkown error at position ").append(position).append('.');
break;
diff --git a/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/json/parser/JSONParser.java b/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/json/parser/JSONParser.java
index 3f382d2..d47f85c 100644
--- a/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/json/parser/JSONParser.java
+++ b/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/json/parser/JSONParser.java
@@ -26,16 +26,17 @@
import java.util.List;
import java.util.Map;
+import org.apache.chemistry.opencmis.commons.impl.JSONConstraints;
import org.apache.chemistry.opencmis.commons.impl.json.JSONArray;
import org.apache.chemistry.opencmis.commons.impl.json.JSONObject;
/**
* Parser for JSON text. Please note that JSONParser is NOT thread-safe.
*
- * (Taken from JSON.simple <http://code.google.com/p/json-simple/> and modified
- * for OpenCMIS.)
+ * (Taken from JSON.simple <http://code.google.com/p/json-simple/> and
+ * modified for OpenCMIS.)
*
- * @author FangYidong<fangyidong@yahoo.com.cn>
+ * @author FangYidong<fangyidong@yahoo.com.cn>
*/
public class JSONParser {
public static final int S_INIT = 0;
@@ -202,6 +203,9 @@
statusStack.removeFirst();
String key = (String) valueStack.removeFirst();
Map<String, Object> parent = (Map<String, Object>) valueStack.getFirst();
+ if (parent.size() + 1 > JSONConstraints.MAX_OBJECT_SIZE) {
+ throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG);
+ }
parent.put(key, token.value);
status = peekStatus(statusStack);
break;
@@ -209,6 +213,12 @@
statusStack.removeFirst();
key = (String) valueStack.removeFirst();
parent = (Map<String, Object>) valueStack.getFirst();
+ if (parent.size() + 1 > JSONConstraints.MAX_OBJECT_SIZE) {
+ throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG);
+ }
+ if (valueStack.size() + 1 > JSONConstraints.MAX_DEPTH) {
+ throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG);
+ }
List<Object> newArray = createArrayContainer(containerFactory);
parent.put(key, newArray);
status = S_IN_ARRAY;
@@ -219,6 +229,12 @@
statusStack.removeFirst();
key = (String) valueStack.removeFirst();
parent = (Map<String, Object>) valueStack.getFirst();
+ if (parent.size() + 1 > JSONConstraints.MAX_OBJECT_SIZE) {
+ throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG);
+ }
+ if (valueStack.size() + 1 > JSONConstraints.MAX_DEPTH) {
+ throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG);
+ }
Map<String, Object> newObject = createObjectContainer(containerFactory);
parent.put(key, newObject);
status = S_IN_OBJECT;
@@ -236,6 +252,9 @@
break;
case Yytoken.TYPE_VALUE:
List<Object> val = (List<Object>) valueStack.getFirst();
+ if (val.size() + 1 > JSONConstraints.MAX_ARRAY_SIZE) {
+ throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG);
+ }
val.add(token.value);
break;
case Yytoken.TYPE_RIGHT_SQUARE:
@@ -249,6 +268,12 @@
break;
case Yytoken.TYPE_LEFT_BRACE:
val = (List<Object>) valueStack.getFirst();
+ if (val.size() + 1 > JSONConstraints.MAX_ARRAY_SIZE) {
+ throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG);
+ }
+ if (valueStack.size() + 1 > JSONConstraints.MAX_DEPTH) {
+ throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG);
+ }
Map<String, Object> newObject = createObjectContainer(containerFactory);
val.add(newObject);
status = S_IN_OBJECT;
@@ -257,6 +282,12 @@
break;
case Yytoken.TYPE_LEFT_SQUARE:
val = (List<Object>) valueStack.getFirst();
+ if (val.size() + 1 > JSONConstraints.MAX_ARRAY_SIZE) {
+ throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG);
+ }
+ if (valueStack.size() + 1 > JSONConstraints.MAX_DEPTH) {
+ throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG);
+ }
List<Object> newArray = createArrayContainer(containerFactory);
val.add(newArray);
status = S_IN_ARRAY;
diff --git a/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/json/parser/Yylex.java b/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/json/parser/Yylex.java
index 9d58d45..e5c35d9 100644
--- a/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/json/parser/Yylex.java
+++ b/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/json/parser/Yylex.java
@@ -22,8 +22,8 @@
import java.math.BigInteger;
/**
- * (Taken from JSON.simple <http://code.google.com/p/json-simple/> and modified
- * for OpenCMIS.)
+ * (Taken from JSON.simple <http://code.google.com/p/json-simple/> and
+ * modified for OpenCMIS.)
*/
class Yylex {
diff --git a/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/test/java/org/apache/chemistry/opencmis/commons/impl/json/JSONConstraintsTest.java b/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/test/java/org/apache/chemistry/opencmis/commons/impl/json/JSONConstraintsTest.java
new file mode 100644
index 0000000..e84a1a9
--- /dev/null
+++ b/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/test/java/org/apache/chemistry/opencmis/commons/impl/json/JSONConstraintsTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.chemistry.opencmis.commons.impl.json;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.chemistry.opencmis.commons.impl.JSONConstraints;
+import org.apache.chemistry.opencmis.commons.impl.json.parser.JSONParseException;
+import org.apache.chemistry.opencmis.commons.impl.json.parser.JSONParser;
+import org.junit.Test;
+
+public class JSONConstraintsTest {
+
+ private enum JSONElement {
+ VALUE, OBJECT, ARRAY
+ };
+
+ @SuppressWarnings("rawtypes")
+ @Test
+ public void testObjectSize() {
+ JSONParser parser = new JSONParser();
+
+ for (JSONElement jsonElement : JSONElement.values()) {
+ JSONObject jsonObject1 = createObject(JSONConstraints.MAX_OBJECT_SIZE, jsonElement);
+ try {
+ Object parsedObject = parser.parse(jsonObject1.toJSONString());
+ assertNotNull(parsedObject);
+ assertTrue(parsedObject instanceof Map);
+ assertEquals(JSONConstraints.MAX_OBJECT_SIZE, ((Map) parsedObject).size());
+ } catch (JSONParseException e) {
+ fail();
+ }
+
+ JSONObject jsonObject2 = createObject(JSONConstraints.MAX_OBJECT_SIZE + 1, jsonElement);
+ try {
+ parser.parse(jsonObject2.toJSONString());
+ fail();
+ } catch (JSONParseException e) {
+ assertEquals(JSONParseException.ERROR_JSON_TOO_BIG, e.getErrorType());
+ }
+ }
+ }
+
+ private JSONObject createObject(int size, JSONElement jsonElement) {
+ JSONObject result = new JSONObject();
+
+ for (int i = 0; i < size; i++) {
+ switch (jsonElement) {
+ case VALUE:
+ result.put("key" + i, "value" + i);
+ break;
+ case OBJECT:
+ result.put("key" + i, new JSONObject());
+ break;
+ case ARRAY:
+ result.put("key" + i, new JSONArray());
+ break;
+ default:
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Test
+ public void testArraySize() {
+ JSONParser parser = new JSONParser();
+
+ for (JSONElement jsonElement : JSONElement.values()) {
+ JSONArray jsonArray1 = createArray(JSONConstraints.MAX_ARRAY_SIZE, jsonElement);
+ try {
+ Object parsedObject = parser.parse(jsonArray1.toJSONString());
+ assertNotNull(parsedObject);
+ assertTrue(parsedObject instanceof List);
+ assertEquals(JSONConstraints.MAX_ARRAY_SIZE, ((List) parsedObject).size());
+ } catch (JSONParseException e) {
+ fail();
+ }
+
+ JSONArray jsonArray2 = createArray(JSONConstraints.MAX_ARRAY_SIZE + 1, jsonElement);
+ try {
+ parser.parse(jsonArray2.toJSONString());
+ fail();
+ } catch (JSONParseException e) {
+ assertEquals(JSONParseException.ERROR_JSON_TOO_BIG, e.getErrorType());
+ }
+ }
+ }
+
+ private JSONArray createArray(int size, JSONElement jsonElement) {
+ JSONArray result = new JSONArray();
+
+ for (int i = 0; i < size; i++) {
+ switch (jsonElement) {
+ case VALUE:
+ result.add("value" + i);
+ break;
+ case OBJECT:
+ result.add(new JSONObject());
+ break;
+ case ARRAY:
+ result.add(new JSONArray());
+ break;
+ default:
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ @Test
+ public void testDepth() {
+ JSONParser parser = new JSONParser();
+
+ JSONObject jsonObject1 = createDeepObject(JSONConstraints.MAX_DEPTH);
+ try {
+ parser.parse(jsonObject1.toJSONString());
+ } catch (JSONParseException e) {
+ fail();
+ }
+
+ JSONObject jsonObject2 = createDeepObject(JSONConstraints.MAX_DEPTH + 1);
+ try {
+ parser.parse(jsonObject2.toJSONString());
+ fail();
+ } catch (JSONParseException e) {
+ assertEquals(JSONParseException.ERROR_JSON_TOO_BIG, e.getErrorType());
+ }
+ }
+
+ private JSONObject createDeepObject(int depth) {
+ JSONObject result = new JSONObject();
+
+ JSONObject lastObject = result;
+ for (int i = 1; i < depth; i++) {
+ JSONObject newObject = new JSONObject();
+ lastObject.put("obj", newObject);
+ lastObject = newObject;
+ }
+
+ return result;
+ }
+}