Return-Path: X-Original-To: apmail-sling-commits-archive@www.apache.org Delivered-To: apmail-sling-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 02639DC54 for ; Fri, 9 Nov 2012 18:11:19 +0000 (UTC) Received: (qmail 53700 invoked by uid 500); 9 Nov 2012 18:11:18 -0000 Delivered-To: apmail-sling-commits-archive@sling.apache.org Received: (qmail 53657 invoked by uid 500); 9 Nov 2012 18:11:18 -0000 Mailing-List: contact commits-help@sling.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@sling.apache.org Delivered-To: mailing list commits@sling.apache.org Received: (qmail 53650 invoked by uid 99); 9 Nov 2012 18:11:18 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 09 Nov 2012 18:11:18 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 09 Nov 2012 18:11:14 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 5999C2388900; Fri, 9 Nov 2012 18:10:52 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1407568 - in /sling/trunk/bundles/servlets/get/src/main/java/org/apache/sling/servlets/get/impl/helpers: JsonObjectCreator.java JsonRendererServlet.java JsonResourceWriter.java ResourceTraversor.java Date: Fri, 09 Nov 2012 18:10:51 -0000 To: commits@sling.apache.org From: cziegeler@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20121109181052.5999C2388900@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org 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 true 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 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 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 currentQueue; - private LinkedList currentQueue; + private LinkedList nextQueue; - private LinkedList 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(); - nextQueue = new LinkedList(); - startObject = adapt(resource); + currentQueue = new LinkedList(); + nextQueue = new LinkedList(); + 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 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(); + nextQueue = new LinkedList(); + } + 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; } - }