Author: cziegeler
Date: Fri Nov 9 18:10:50 2012
New Revision: 1407568
URL: http://svn.apache.org/viewvc?rev=1407568&view=rev
Log:
SLING-2320 : Current DOS-prevention for infinity.json can prevent enumeration
of children
Added:
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonObjectCreator.java
(with props)
Removed:
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonResourceWriter.java
Modified:
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonRendererServlet.java
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/ResourceTraversor.java
Added:
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonObjectCreator.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonObjectCreator.java?rev=1407568&view=auto
==============================================================================
---
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonObjectCreator.java
(added)
+++
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonObjectCreator.java
Fri Nov 9 18:10:50 2012
@@ -0,0 +1,229 @@
+/*
+ * 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.sling.servlets.get.impl.helpers;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
+
+/**
+ * Creates a JSONObject from a resource
+ *
+ */
+public abstract class JsonObjectCreator {
+
+ /**
+ * Dump given resource in JSON, optionally recursing into its objects
+ * @param tidy if <code>true</code> the json dump is nicely formatted
+ */
+ public static JSONObject create(final Resource resource, final int
maxRecursionLevels)
+ throws JSONException {
+ return create(resource, 0, maxRecursionLevels);
+ }
+
+
+ /** Dump given resource in JSON, optionally recursing into its objects */
+ private static JSONObject create(final Resource resource,
+ final int currentRecursionLevel,
+ final int maxRecursionLevels)
+ throws JSONException {
+ final ValueMap valueMap = resource.adaptTo(ValueMap.class);
+
+ @SuppressWarnings("unchecked")
+ final Map propertyMap = (valueMap != null)
+ ? valueMap
+ : resource.adaptTo(Map.class);
+
+ final JSONObject obj = new JSONObject();
+
+ if (propertyMap == null) {
+
+ // no map available, try string
+ final String value = resource.adaptTo(String.class);
+ if (value != null) {
+
+ // single value property or just plain String resource or...
+ obj.put(ResourceUtil.getName(resource), value);
+
+ } else {
+
+ // Try multi-value "property"
+ final String[] values = resource.adaptTo(String[].class);
+ if (values != null) {
+ obj.put(ResourceUtil.getName(resource), new
JSONArray(Arrays.asList(values)));
+ }
+
+ }
+
+ } else {
+
+ @SuppressWarnings("unchecked")
+ final Iterator<Map.Entry> props =
propertyMap.entrySet().iterator();
+
+ // the node's actual properties
+ while (props.hasNext()) {
+ @SuppressWarnings("unchecked")
+ final Map.Entry prop = props.next();
+
+ createProperty(obj, valueMap, prop.getKey().toString(),
+ prop.getValue());
+ }
+ }
+
+ // the child nodes
+ if (recursionLevelActive(currentRecursionLevel, maxRecursionLevels)) {
+ final Iterator<Resource> children =
ResourceUtil.listChildren(resource);
+ while (children.hasNext()) {
+ final Resource n = children.next();
+ createSingleResource(n, obj, currentRecursionLevel,
+ maxRecursionLevels);
+ }
+ }
+
+ return obj;
+ }
+
+ /** Used to format date values */
+ private static final String ECMA_DATE_FORMAT = "EEE MMM dd yyyy HH:mm:ss
'GMT'Z";
+
+ /** Used to format date values */
+ private static final Locale DATE_FORMAT_LOCALE = Locale.US;
+
+ private static final DateFormat CALENDAR_FORMAT = new
SimpleDateFormat(ECMA_DATE_FORMAT, DATE_FORMAT_LOCALE);
+
+ private static synchronized String format(final Calendar date) {
+ return CALENDAR_FORMAT.format(date.getTime());
+ }
+
+ /** Dump only a value in the correct format */
+ private static Object getValue(final Object value) {
+ if ( value instanceof InputStream ) {
+ // input stream is already handled
+ return 0;
+ } else if ( value instanceof Calendar ) {
+ return format((Calendar)value);
+ } else if ( value instanceof Boolean ) {
+ return value;
+ } else if ( value instanceof Long ) {
+ return value;
+ } else if ( value instanceof Integer ) {
+ return value;
+ } else if ( value instanceof Double ) {
+ return value;
+ } else {
+ return value.toString();
+ }
+ }
+
+ /** Dump a single node */
+ private static void createSingleResource(final Resource n, final
JSONObject parent,
+ final int currentRecursionLevel, final int maxRecursionLevels)
+ throws JSONException {
+ if (recursionLevelActive(currentRecursionLevel, maxRecursionLevels)) {
+ parent.put(ResourceUtil.getName(n), create(n,
currentRecursionLevel + 1, maxRecursionLevels));
+ }
+ }
+
+ /** true if the current recursion level is active */
+ private static boolean recursionLevelActive(final int
currentRecursionLevel,
+ final int maxRecursionLevels) {
+ return maxRecursionLevels < 0
+ || currentRecursionLevel < maxRecursionLevels;
+ }
+
+ /**
+ * Write a single property
+ */
+ private static void createProperty(final JSONObject obj,
+ final ValueMap valueMap,
+ final String key,
+ final Object value)
+ throws JSONException {
+ Object[] values = null;
+ if (value.getClass().isArray()) {
+ values = (Object[])value;
+ // write out empty array
+ if ( values.length == 0 ) {
+ obj.put(key, new JSONArray());
+ return;
+ }
+ }
+
+ // special handling for binaries: we dump the length and not the data!
+ if (value instanceof InputStream
+ || (values != null && values[0] instanceof InputStream)) {
+ // TODO for now we mark binary properties with an initial colon in
+ // their name
+ // (colon is not allowed as a JCR property name)
+ // in the name, and the value should be the size of the binary data
+ if (values == null) {
+ obj.put(":" + key, getLength(valueMap, -1, key,
(InputStream)value));
+ } else {
+ final JSONArray result = new JSONArray();
+ for (int i = 0; i < values.length; i++) {
+ result.put(getLength(valueMap, i, key,
(InputStream)values[i]));
+ }
+ obj.put(":" + key, result);
+ }
+ return;
+ }
+
+ if (!value.getClass().isArray()) {
+ obj.put(key, getValue(value));
+ } else {
+ final JSONArray result = new JSONArray();
+ for (Object v : values) {
+ result.put(getValue(v));
+ }
+ obj.put(key, result);
+ }
+ }
+
+ private static long getLength(final ValueMap valueMap,
+ final int index,
+ final String key,
+ final InputStream stream) {
+ try {
+ stream.close();
+ } catch (IOException ignore) {}
+ long length = -1;
+ if ( valueMap != null ) {
+ if ( index == -1 ) {
+ length = valueMap.get(key, length);
+ } else {
+ Long[] lengths = valueMap.get(key, Long[].class);
+ if ( lengths != null && lengths.length > index ) {
+ length = lengths[index];
+ }
+ }
+ }
+ return length;
+ }
+}
Propchange:
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonObjectCreator.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonObjectCreator.java
------------------------------------------------------------------------------
svn:keywords = author date id revision rev url
Propchange:
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonObjectCreator.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified:
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonRendererServlet.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonRendererServlet.java?rev=1407568&r1=1407567&r2=1407568&view=diff
==============================================================================
---
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonRendererServlet.java
(original)
+++
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/JsonRendererServlet.java
Fri Nov 9 18:10:50 2012
@@ -18,14 +18,12 @@ package org.apache.sling.servlets.get.im
import java.io.IOException;
-import javax.jcr.RepositoryException;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.sling.api.SlingException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
-import org.apache.sling.api.request.RecursionTooDeepException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceNotFoundException;
import org.apache.sling.api.resource.ResourceUtil;
@@ -98,18 +96,16 @@ public class JsonRendererServlet extends
// We check the tree to see if the nr of nodes isn't bigger than the
allowed nr.
boolean allowDump = true;
- long allowedLevel = 0;
- boolean tidy = isTidy(req);
+ int allowedLevel = 0;
+ final boolean tidy = isTidy(req);
ResourceTraversor traversor = null;
try {
traversor = new ResourceTraversor(maxRecursionLevels,
maximumResults, r, tidy);
- traversor.collectResources();
- } catch (RecursionTooDeepException e) {
- allowDump = false;
- allowedLevel = Integer.parseInt(e.getMessage()); // this is to
avoid depending on a SNAPSHOT version of the SLing API.
- } catch (RepositoryException e) {
- reportException(e);
- } catch (JSONException e) {
+ allowedLevel = traversor.collectResources();
+ if ( allowedLevel != -1 ) {
+ allowDump = false;
+ }
+ } catch (final JSONException e) {
reportException(e);
}
try {
Modified:
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/ResourceTraversor.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/ResourceTraversor.java?rev=1407568&r1=1407567&r2=1407568&view=diff
==============================================================================
---
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/ResourceTraversor.java
(original)
+++
sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers/ResourceTraversor.java
Fri Nov 9 18:10:50 2012
@@ -17,90 +17,87 @@
package org.apache.sling.servlets.get.impl.helpers;
+import java.util.Iterator;
+import java.util.LinkedList;
+
import org.apache.sling.api.request.RecursionTooDeepException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
-import java.io.StringWriter;
-import java.util.Iterator;
-import java.util.LinkedList;
-
-import javax.jcr.RepositoryException;
-
public class ResourceTraversor {
+ public static final class Entry {
+ public final Resource resource;
+ public final JSONObject json;
+
+ public Entry(final Resource r, final JSONObject o) {
+ this.resource = r;
+ this.json = o;
+ }
+ }
+
private long count;
private long maxResources;
- private int maxRecursionLevels;
+ private final int maxRecursionLevels;
- private JSONObject startObject;
+ private final JSONObject startObject;
- private String startingPath;
+ private LinkedList<Entry> currentQueue;
- private LinkedList<Resource> currentQueue;
+ private LinkedList<Entry> nextQueue;
- private LinkedList<Resource> nextQueue;
+ private final Resource startResource;
- private Resource startResource;
-
- private JsonResourceWriter jsResourceWriter;
-
- private boolean tidy;
-
- public ResourceTraversor(int levels, long maxNodes, Resource resource,
boolean tidy) throws RepositoryException,
- JSONException {
- this.setMaxNodes(maxNodes);
+ public ResourceTraversor(final int levels, final long maxResources, final
Resource resource, final boolean tidy)
+ throws JSONException {
+ this.maxResources = maxResources;
this.maxRecursionLevels = levels;
this.startResource = resource;
- this.tidy = tidy;
- startingPath = resource.getPath();
- jsResourceWriter = new JsonResourceWriter(null);
- currentQueue = new LinkedList<Resource>();
- nextQueue = new LinkedList<Resource>();
- startObject = adapt(resource);
+ currentQueue = new LinkedList<Entry>();
+ nextQueue = new LinkedList<Entry>();
+ this.startObject = this.adapt(resource);
}
/**
* Recursive descent from startResource, collecting JSONObjects into
* startObject. Throws a RecursionTooDeepException if the maximum number of
- * nodes is reached on a "deep" traversal (where "deep" === level greateer
+ * nodes is reached on a "deep" traversal (where "deep" === level greater
* than 1).
*
- * @throws RepositoryException
- * @throws RecursionTooDeepException When the resource has more child nodes
- * then allowed.
+ * @return -1 if everything went fine, a positive valuew when the resource
+ * has more child nodes then allowed.
* @throws JSONException
*/
- public void collectResources() throws RepositoryException,
RecursionTooDeepException, JSONException {
- collectChildren(startResource, 0);
+ public int collectResources() throws RecursionTooDeepException,
JSONException {
+ return collectChildren(startResource, this.startObject, 0);
}
/**
* @param resource
* @param currentLevel
- * @throws RecursionTooDeepException
* @throws JSONException
- * @throws RepositoryException
*/
- private void collectChildren(Resource resource, int currentLevel) throws
RecursionTooDeepException, JSONException,
- RepositoryException {
+ private int collectChildren(final Resource resource,
+ final JSONObject jsonObj,
+ int currentLevel)
+ throws JSONException {
if (maxRecursionLevels == -1 || currentLevel < maxRecursionLevels) {
final Iterator<Resource> children =
ResourceUtil.listChildren(resource);
while (children.hasNext()) {
count++;
- Resource res = children.next();
+ final Resource res = children.next();
// SLING-2320: always allow enumeration of one's children;
// DOS-limitation is for deeper traversals.
if (count > maxResources && maxRecursionLevels != 1) {
- throw new
RecursionTooDeepException(String.valueOf(currentLevel));
+ return currentLevel;
}
- collectResource(res, currentLevel);
- nextQueue.addLast(res);
+ final JSONObject json = collectResource(res, jsonObj);
+ nextQueue.addLast(new Entry(res, json));
}
}
@@ -108,11 +105,15 @@ public class ResourceTraversor {
if (currentQueue.isEmpty()) {
currentLevel++;
currentQueue = nextQueue;
- nextQueue = new LinkedList<Resource>();
+ nextQueue = new LinkedList<Entry>();
+ }
+ final Entry nextResource = currentQueue.removeFirst();
+ final int maxLevel = collectChildren(nextResource.resource,
nextResource.json, currentLevel);
+ if ( maxLevel != -1 ) {
+ return maxLevel;
}
- Resource nextResource = currentQueue.removeFirst();
- collectChildren(nextResource, currentLevel);
}
+ return -1;
}
/**
@@ -120,15 +121,13 @@ public class ResourceTraversor {
*
* @param resource The resource to add
* @param level The level where this resource is located.
- * @throws RepositoryException
* @throws JSONException
*/
- protected void collectResource(Resource resource, int level) throws
RepositoryException, JSONException {
-
- if (!resource.getPath().equals(startingPath)) {
- JSONObject o = adapt(resource);
- getParentJSONObject(resource,
level).put(ResourceUtil.getName(resource), o);
- }
+ private JSONObject collectResource(Resource resource, final JSONObject
parent)
+ throws JSONException {
+ final JSONObject o = adapt(resource);
+ parent.put(ResourceUtil.getName(resource), o);
+ return o;
}
/**
@@ -138,66 +137,18 @@ public class ResourceTraversor {
* @return The JSON representation of the Resource
* @throws JSONException
*/
- private JSONObject adapt(Resource resource) throws JSONException {
- // TODO Find a better way to adapt a Resource to a JSONObject.
- StringWriter writer = new StringWriter();
- jsResourceWriter.dump(resource, writer, 0, tidy);
- return new JSONObject(writer.getBuffer().toString());
+ private JSONObject adapt(final Resource resource) throws JSONException {
+ return JsonObjectCreator.create(resource, 0);
}
/**
- * Get the JSON Object where this resource should be added in.
- *
- * @param resource
- * @param level
- * @return
- * @throws RepositoryException
- * @throws JSONException
- */
- private JSONObject getParentJSONObject(Resource resource, int level)
throws RepositoryException, JSONException {
- String path = resource.getPath();
- // The root node.
- if (path.equals(startingPath)) {
- return startObject;
- }
-
- // Some level deeper
- String pathDiff = path.substring(startingPath.length());
- String[] names = pathDiff.split("/");
- JSONObject o = startObject;
- for (String name : names) {
- try {
- o = o.getJSONObject(name);
- } catch (JSONException e) {
- }
- }
- return o;
-
- }
-
- /**
- * @return The number of nodes this visitor found.
+ * @return The number of resources this visitor found.
*/
public long getCount() {
return count;
}
- /**
- * @param maxNodes the maxNodes to set
- */
- public void setMaxNodes(long maxNodes) {
- this.maxResources = maxNodes;
- }
-
- /**
- * @return the maxNodes
- */
- public long getMaxNodes() {
- return maxResources;
- }
-
public JSONObject getJSONObject() {
return startObject;
}
-
}