1 /*
   2  * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.org.apache.xerces.internal.utils;
  27 
  28 import com.sun.org.apache.xerces.internal.impl.Constants;
  29 import com.sun.org.apache.xerces.internal.utils.XMLSecurityManager.Limit;
  30 import java.util.Formatter;
  31 import java.util.HashMap;
  32 import java.util.Map;
  33 
  34 /**
  35  * A helper for analyzing entity expansion limits
  36  *
  37  * @author Joe Wang Oracle Corp.
  38  *
  39  */
  40 public final class XMLLimitAnalyzer {
  41 
  42     /**
  43      * Map old property names with the new ones
  44      */
  45     public static enum NameMap {
  46         ENTITY_EXPANSION_LIMIT(Constants.SP_ENTITY_EXPANSION_LIMIT, Constants.ENTITY_EXPANSION_LIMIT),
  47         MAX_OCCUR_NODE_LIMIT(Constants.SP_MAX_OCCUR_LIMIT, Constants.MAX_OCCUR_LIMIT),
  48         ELEMENT_ATTRIBUTE_LIMIT(Constants.SP_ELEMENT_ATTRIBUTE_LIMIT, Constants.ELEMENT_ATTRIBUTE_LIMIT);
  49 
  50         final String newName;
  51         final String oldName;
  52 
  53         NameMap(String newName, String oldName) {
  54             this.newName = newName;
  55             this.oldName = oldName;
  56         }
  57 
  58         String getOldName(String newName) {
  59             if (newName.equals(this.newName)) {
  60                 return oldName;
  61             }
  62             return null;
  63         }
  64     }
  65 
  66     /**
  67      * Max value accumulated for each property
  68      */
  69     private final int[] values;
  70     /**
  71      * Names of the entities corresponding to their max values
  72      */
  73     private final String[] names;
  74     /**
  75      * Total value of accumulated entities
  76      */
  77     private final int[] totalValue;
  78 
  79     /**
  80      * Maintain values of the top 10 elements in the process of parsing
  81      */
  82     private final Map<String, Integer>[] caches;
  83 
  84     private String entityStart, entityEnd;
  85     /**
  86      * Default constructor. Establishes default values for known security
  87      * vulnerabilities.
  88      */
  89     @SuppressWarnings({"rawtypes", "unchecked"})
  90     public XMLLimitAnalyzer() {
  91         values = new int[Limit.values().length];
  92         totalValue = new int[Limit.values().length];
  93         names = new String[Limit.values().length];
  94         caches = new Map[Limit.values().length];
  95     }
  96 
  97     /**
  98      * Add the value to the current max count for the specified property
  99      * To find the max value of all entities, set no limit
 100      *
 101      * @param limit the type of the property
 102      * @param entityName the name of the entity
 103      * @param value the value of the entity
 104      */
 105     public void addValue(Limit limit, String entityName, int value) {
 106         addValue(limit.ordinal(), entityName, value);
 107     }
 108 
 109     /**
 110      * Add the value to the current count by the index of the property
 111      * @param index the index of the property
 112      * @param entityName the name of the entity
 113      * @param value the value of the entity
 114      */
 115     public void addValue(int index, String entityName, int value) {
 116         if (index == Limit.ENTITY_EXPANSION_LIMIT.ordinal() ||
 117                 index == Limit.MAX_OCCUR_NODE_LIMIT.ordinal() ||
 118                 index == Limit.ELEMENT_ATTRIBUTE_LIMIT.ordinal() ||
 119                 index == Limit.TOTAL_ENTITY_SIZE_LIMIT.ordinal() ||
 120                 index == Limit.ENTITY_REPLACEMENT_LIMIT.ordinal()
 121                 ) {
 122             totalValue[index] += value;
 123             return;
 124         }
 125         if (index == Limit.MAX_ELEMENT_DEPTH_LIMIT.ordinal() ||
 126                 index == Limit.MAX_NAME_LIMIT.ordinal()) {
 127             values[index] = value;
 128             totalValue[index] = value;
 129             return;
 130         }
 131 
 132         Map<String, Integer> cache;
 133         if (caches[index] == null) {
 134             cache = new HashMap<>(10);
 135             caches[index] = cache;
 136         } else {
 137             cache = caches[index];
 138         }
 139 
 140         int accumulatedValue = value;
 141         if (cache.containsKey(entityName)) {
 142             accumulatedValue += cache.get(entityName);
 143             cache.put(entityName, accumulatedValue);
 144         } else {
 145             cache.put(entityName, value);
 146         }
 147 
 148         if (accumulatedValue > values[index]) {
 149             values[index] = accumulatedValue;
 150             names[index] = entityName;
 151         }
 152 
 153 
 154         if (index == Limit.GENERAL_ENTITY_SIZE_LIMIT.ordinal() ||
 155                 index == Limit.PARAMETER_ENTITY_SIZE_LIMIT.ordinal()) {
 156             totalValue[Limit.TOTAL_ENTITY_SIZE_LIMIT.ordinal()] += value;
 157         }
 158     }
 159 
 160     /**
 161      * Return the value of the current max count for the specified property
 162      *
 163      * @param limit the property
 164      * @return the value of the property
 165      */
 166     public int getValue(Limit limit) {
 167         return getValue(limit.ordinal());
 168     }
 169 
 170     public int getValue(int index) {
 171         if (index == Limit.ENTITY_REPLACEMENT_LIMIT.ordinal()) {
 172             return totalValue[index];
 173         }
 174         return values[index];
 175     }
 176     /**
 177      * Return the total value accumulated so far
 178      *
 179      * @param limit the property
 180      * @return the accumulated value of the property
 181      */
 182     public int getTotalValue(Limit limit) {
 183         return totalValue[limit.ordinal()];
 184     }
 185 
 186     public int getTotalValue(int index) {
 187         return totalValue[index];
 188     }
 189     /**
 190      * Return the current max value (count or length) by the index of a property
 191      * @param index the index of a property
 192      * @return count of a property
 193      */
 194     public int getValueByIndex(int index) {
 195         return values[index];
 196     }
 197 
 198     public void startEntity(String name) {
 199         entityStart = name;
 200     }
 201 
 202     public boolean isTracking(String name) {
 203         if (entityStart == null) {
 204             return false;
 205         }
 206         return entityStart.equals(name);
 207     }
 208     /**
 209      * Stop tracking the entity
 210      * @param limit the limit property
 211      * @param name the name of an entity
 212      */
 213     public void endEntity(Limit limit, String name) {
 214         entityStart = "";
 215         Map<String, Integer> cache = caches[limit.ordinal()];
 216         if (cache != null) {
 217             cache.remove(name);
 218         }
 219     }
 220 
 221     /**
 222      * Resets the current value of the specified limit.
 223      * @param limit The limit to be reset.
 224      */
 225     public void reset(Limit limit) {
 226         if (limit.ordinal() == Limit.TOTAL_ENTITY_SIZE_LIMIT.ordinal()) {
 227             totalValue[limit.ordinal()] = 0;
 228         } else if (limit.ordinal() == Limit.GENERAL_ENTITY_SIZE_LIMIT.ordinal()) {
 229             names[limit.ordinal()] = null;
 230             values[limit.ordinal()] = 0;
 231             caches[limit.ordinal()] = null;
 232             totalValue[limit.ordinal()] = 0;
 233         }
 234     }
 235 
 236     public void debugPrint(XMLSecurityManager securityManager) {
 237         Formatter formatter = new Formatter();
 238         System.out.println(formatter.format("%30s %15s %15s %15s %30s",
 239                 "Property","Limit","Total size","Size","Entity Name"));
 240 
 241         for (Limit limit : Limit.values()) {
 242             formatter = new Formatter();
 243             System.out.println(formatter.format("%30s %15d %15d %15d %30s",
 244                     limit.name(),
 245                     securityManager.getLimit(limit),
 246                     totalValue[limit.ordinal()],
 247                     values[limit.ordinal()],
 248                     names[limit.ordinal()]));
 249         }
 250     }
 251 }