1 /*
   2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
   3  *
   4  * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
   5  *
   6  * The contents of this file are subject to the terms of either the GNU
   7  * General Public License Version 2 only ("GPL") or the Common Development
   8  * and Distribution License("CDDL") (collectively, the "License").  You
   9  * may not use this file except in compliance with the License.  You can
  10  * obtain a copy of the License at
  11  * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
  12  * or packager/legal/LICENSE.txt.  See the License for the specific
  13  * language governing permissions and limitations under the License.
  14  *
  15  * When distributing the software, include this License Header Notice in each
  16  * file and include the License file at packager/legal/LICENSE.txt.
  17  *
  18  * GPL Classpath Exception:
  19  * Oracle designates this particular file as subject to the "Classpath"
  20  * exception as provided by Oracle in the GPL Version 2 section of the License
  21  * file that accompanied this code.
  22  *
  23  * Modifications:
  24  * If applicable, add the following below the License Header, with the fields
  25  * enclosed by brackets [] replaced by your own identifying information:
  26  * "Portions Copyright [year] [name of copyright owner]"
  27  *
  28  * Contributor(s):
  29  * If you wish your version of this file to be governed by only the CDDL or
  30  * only the GPL Version 2, indicate your decision by adding "[Contributor]
  31  * elects to include this software in this distribution under the [CDDL or GPL
  32  * Version 2] license."  If you don't indicate a single choice of license, a
  33  * recipient has the option to distribute your version of this file under
  34  * either the CDDL, the GPL Version 2 or to extend the choice of license to
  35  * its licensees as provided above.  However, if you add GPL Version 2 code
  36  * and therefore, elected the GPL Version 2 license, then the option applies
  37  * only if the new code is made subject to such option by the copyright
  38  * holder.
  39  */
  40 package com.sun.org.apache.xerces.internal.utils;
  41 
  42 import com.sun.org.apache.xerces.internal.impl.Constants;
  43 import com.sun.org.apache.xerces.internal.utils.XMLSecurityManager.Limit;
  44 import java.util.Formatter;
  45 import java.util.HashMap;
  46 import java.util.Map;
  47 
  48 /**
  49  * A helper for analyzing entity expansion limits
  50  *
  51  * @author Joe Wang Oracle Corp.
  52  *
  53  */
  54 public final class XMLLimitAnalyzer {
  55 
  56     /**
  57      * Map old property names with the new ones
  58      */
  59     public static enum NameMap {
  60         ENTITY_EXPANSION_LIMIT(Constants.SP_ENTITY_EXPANSION_LIMIT, Constants.ENTITY_EXPANSION_LIMIT),
  61         MAX_OCCUR_NODE_LIMIT(Constants.SP_MAX_OCCUR_LIMIT, Constants.MAX_OCCUR_LIMIT),
  62         ELEMENT_ATTRIBUTE_LIMIT(Constants.SP_ELEMENT_ATTRIBUTE_LIMIT, Constants.ELEMENT_ATTRIBUTE_LIMIT);
  63 
  64         final String newName;
  65         final String oldName;
  66 
  67         NameMap(String newName, String oldName) {
  68             this.newName = newName;
  69             this.oldName = oldName;
  70         }
  71 
  72         String getOldName(String newName) {
  73             if (newName.equals(this.newName)) {
  74                 return oldName;
  75             }
  76             return null;
  77         }
  78     }
  79 
  80     /**
  81      * Max value accumulated for each property
  82      */
  83     private final int[] values;
  84     /**
  85      * Names of the entities corresponding to their max values
  86      */
  87     private final String[] names;
  88     /**
  89      * Total value of accumulated entities
  90      */
  91     private final int[] totalValue;
  92 
  93     /**
  94      * Maintain values of the top 10 elements in the process of parsing
  95      */
  96     private final Map<String, Integer>[] caches;
  97 
  98     private String entityStart, entityEnd;
  99     /**
 100      * Default constructor. Establishes default values for known security
 101      * vulnerabilities.
 102      */
 103     @SuppressWarnings({"rawtypes", "unchecked"})
 104     public XMLLimitAnalyzer() {
 105         values = new int[Limit.values().length];
 106         totalValue = new int[Limit.values().length];
 107         names = new String[Limit.values().length];
 108         caches = new Map[Limit.values().length];
 109     }
 110 
 111     /**
 112      * Add the value to the current max count for the specified property
 113      * To find the max value of all entities, set no limit
 114      *
 115      * @param limit the type of the property
 116      * @param entityName the name of the entity
 117      * @param value the value of the entity
 118      */
 119     public void addValue(Limit limit, String entityName, int value) {
 120         addValue(limit.ordinal(), entityName, value);
 121     }
 122 
 123     /**
 124      * Add the value to the current count by the index of the property
 125      * @param index the index of the property
 126      * @param entityName the name of the entity
 127      * @param value the value of the entity
 128      */
 129     public void addValue(int index, String entityName, int value) {
 130         if (index == Limit.ENTITY_EXPANSION_LIMIT.ordinal() ||
 131                 index == Limit.MAX_OCCUR_NODE_LIMIT.ordinal() ||
 132                 index == Limit.ELEMENT_ATTRIBUTE_LIMIT.ordinal() ||
 133                 index == Limit.TOTAL_ENTITY_SIZE_LIMIT.ordinal() ||
 134                 index == Limit.ENTITY_REPLACEMENT_LIMIT.ordinal()
 135                 ) {
 136             totalValue[index] += value;
 137             return;
 138         }
 139         if (index == Limit.MAX_ELEMENT_DEPTH_LIMIT.ordinal() ||
 140                 index == Limit.MAX_NAME_LIMIT.ordinal()) {
 141             values[index] = value;
 142             totalValue[index] = value;
 143             return;
 144         }
 145 
 146         Map<String, Integer> cache;
 147         if (caches[index] == null) {
 148             cache = new HashMap<>(10);
 149             caches[index] = cache;
 150         } else {
 151             cache = caches[index];
 152         }
 153 
 154         int accumulatedValue = value;
 155         if (cache.containsKey(entityName)) {
 156             accumulatedValue += cache.get(entityName);
 157             cache.put(entityName, accumulatedValue);
 158         } else {
 159             cache.put(entityName, value);
 160         }
 161 
 162         if (accumulatedValue > values[index]) {
 163             values[index] = accumulatedValue;
 164             names[index] = entityName;
 165         }
 166 
 167 
 168         if (index == Limit.GENERAL_ENTITY_SIZE_LIMIT.ordinal() ||
 169                 index == Limit.PARAMETER_ENTITY_SIZE_LIMIT.ordinal()) {
 170             totalValue[Limit.TOTAL_ENTITY_SIZE_LIMIT.ordinal()] += value;
 171         }
 172     }
 173 
 174     /**
 175      * Return the value of the current max count for the specified property
 176      *
 177      * @param limit the property
 178      * @return the value of the property
 179      */
 180     public int getValue(Limit limit) {
 181         return getValue(limit.ordinal());
 182     }
 183 
 184     public int getValue(int index) {
 185         if (index == Limit.ENTITY_REPLACEMENT_LIMIT.ordinal()) {
 186             return totalValue[index];
 187         }
 188         return values[index];
 189     }
 190     /**
 191      * Return the total value accumulated so far
 192      *
 193      * @param limit the property
 194      * @return the accumulated value of the property
 195      */
 196     public int getTotalValue(Limit limit) {
 197         return totalValue[limit.ordinal()];
 198     }
 199 
 200     public int getTotalValue(int index) {
 201         return totalValue[index];
 202     }
 203     /**
 204      * Return the current max value (count or length) by the index of a property
 205      * @param index the index of a property
 206      * @return count of a property
 207      */
 208     public int getValueByIndex(int index) {
 209         return values[index];
 210     }
 211 
 212     public void startEntity(String name) {
 213         entityStart = name;
 214     }
 215 
 216     public boolean isTracking(String name) {
 217         if (entityStart == null) {
 218             return false;
 219         }
 220         return entityStart.equals(name);
 221     }
 222     /**
 223      * Stop tracking the entity
 224      * @param limit the limit property
 225      * @param name the name of an entity
 226      */
 227     public void endEntity(Limit limit, String name) {
 228         entityStart = "";
 229         Map<String, Integer> cache = caches[limit.ordinal()];
 230         if (cache != null) {
 231             cache.remove(name);
 232         }
 233     }
 234 
 235     /**
 236      * Resets the current value of the specified limit.
 237      * @param limit The limit to be reset.
 238      */
 239     public void reset(Limit limit) {
 240         if (limit.ordinal() == Limit.TOTAL_ENTITY_SIZE_LIMIT.ordinal()) {
 241             totalValue[limit.ordinal()] = 0;
 242         } else if (limit.ordinal() == Limit.GENERAL_ENTITY_SIZE_LIMIT.ordinal()) {
 243             names[limit.ordinal()] = null;
 244             values[limit.ordinal()] = 0;
 245             caches[limit.ordinal()] = null;
 246             totalValue[limit.ordinal()] = 0;
 247         }
 248     }
 249 
 250     public void debugPrint(XMLSecurityManager securityManager) {
 251         Formatter formatter = new Formatter();
 252         System.out.println(formatter.format("%30s %15s %15s %15s %30s",
 253                 "Property","Limit","Total size","Size","Entity Name"));
 254 
 255         for (Limit limit : Limit.values()) {
 256             formatter = new Formatter();
 257             System.out.println(formatter.format("%30s %15d %15d %15d %30s",
 258                     limit.name(),
 259                     securityManager.getLimit(limit),
 260                     totalValue[limit.ordinal()],
 261                     values[limit.ordinal()],
 262                     names[limit.ordinal()]));
 263         }
 264     }
 265 }