1/*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright (c) 2013, 2016, 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 */
40package com.sun.org.apache.xerces.internal.utils;
41
42import com.sun.org.apache.xerces.internal.impl.Constants;
43import com.sun.org.apache.xerces.internal.utils.XMLSecurityManager.Limit;
44import java.util.Formatter;
45import java.util.HashMap;
46import java.util.Map;
47
48/**
49 * A helper for analyzing entity expansion limits
50 *
51 * @author Joe Wang Oracle Corp.
52 *
53 */
54public 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[] caches;
97
98    private String entityStart, entityEnd;
99    /**
100     * Default constructor. Establishes default values for known security
101     * vulnerabilities.
102     */
103    public XMLLimitAnalyzer() {
104        values = new int[Limit.values().length];
105        totalValue = new int[Limit.values().length];
106        names = new String[Limit.values().length];
107        caches = new Map[Limit.values().length];
108    }
109
110    /**
111     * Add the value to the current max count for the specified property
112     * To find the max value of all entities, set no limit
113     *
114     * @param limit the type of the property
115     * @param entityName the name of the entity
116     * @param value the value of the entity
117     */
118    public void addValue(Limit limit, String entityName, int value) {
119        addValue(limit.ordinal(), entityName, value);
120    }
121
122    /**
123     * Add the value to the current count by the index of the property
124     * @param index the index of the property
125     * @param entityName the name of the entity
126     * @param value the value of the entity
127     */
128    public void addValue(int index, String entityName, int value) {
129        if (index == Limit.ENTITY_EXPANSION_LIMIT.ordinal() ||
130                index == Limit.MAX_OCCUR_NODE_LIMIT.ordinal() ||
131                index == Limit.ELEMENT_ATTRIBUTE_LIMIT.ordinal() ||
132                index == Limit.TOTAL_ENTITY_SIZE_LIMIT.ordinal() ||
133                index == Limit.ENTITY_REPLACEMENT_LIMIT.ordinal()
134                ) {
135            totalValue[index] += value;
136            return;
137        }
138        if (index == Limit.MAX_ELEMENT_DEPTH_LIMIT.ordinal() ||
139                index == Limit.MAX_NAME_LIMIT.ordinal()) {
140            values[index] = value;
141            totalValue[index] = value;
142            return;
143        }
144
145        Map<String, Integer> cache;
146        if (caches[index] == null) {
147            cache = new HashMap<>(10);
148            caches[index] = cache;
149        } else {
150            cache = caches[index];
151        }
152
153        int accumulatedValue = value;
154        if (cache.containsKey(entityName)) {
155            accumulatedValue += cache.get(entityName);
156            cache.put(entityName, accumulatedValue);
157        } else {
158            cache.put(entityName, value);
159        }
160
161        if (accumulatedValue > values[index]) {
162            values[index] = accumulatedValue;
163            names[index] = entityName;
164        }
165
166
167        if (index == Limit.GENERAL_ENTITY_SIZE_LIMIT.ordinal() ||
168                index == Limit.PARAMETER_ENTITY_SIZE_LIMIT.ordinal()) {
169            totalValue[Limit.TOTAL_ENTITY_SIZE_LIMIT.ordinal()] += value;
170        }
171    }
172
173    /**
174     * Return the value of the current max count for the specified property
175     *
176     * @param limit the property
177     * @return the value of the property
178     */
179    public int getValue(Limit limit) {
180        return getValue(limit.ordinal());
181    }
182
183    public int getValue(int index) {
184        if (index == Limit.ENTITY_REPLACEMENT_LIMIT.ordinal()) {
185            return totalValue[index];
186        }
187        return values[index];
188    }
189    /**
190     * Return the total value accumulated so far
191     *
192     * @param limit the property
193     * @return the accumulated value of the property
194     */
195    public int getTotalValue(Limit limit) {
196        return totalValue[limit.ordinal()];
197    }
198
199    public int getTotalValue(int index) {
200        return totalValue[index];
201    }
202    /**
203     * Return the current max value (count or length) by the index of a property
204     * @param index the index of a property
205     * @return count of a property
206     */
207    public int getValueByIndex(int index) {
208        return values[index];
209    }
210
211    public void startEntity(String name) {
212        entityStart = name;
213    }
214
215    public boolean isTracking(String name) {
216        if (entityStart == null) {
217            return false;
218        }
219        return entityStart.equals(name);
220    }
221    /**
222     * Stop tracking the entity
223     * @param limit the limit property
224     * @param name the name of an entity
225     */
226    public void endEntity(Limit limit, String name) {
227        entityStart = "";
228        Map<String, Integer> cache = caches[limit.ordinal()];
229        if (cache != null) {
230            cache.remove(name);
231        }
232    }
233
234    /**
235     * Resets the current value of the specified limit.
236     * @param limit The limit to be reset.
237     */
238    public void reset(Limit limit) {
239        if (limit.ordinal() == Limit.TOTAL_ENTITY_SIZE_LIMIT.ordinal()) {
240            totalValue[limit.ordinal()] = 0;
241        } else if (limit.ordinal() == Limit.GENERAL_ENTITY_SIZE_LIMIT.ordinal()) {
242            names[limit.ordinal()] = null;
243            values[limit.ordinal()] = 0;
244            caches[limit.ordinal()] = null;
245            totalValue[limit.ordinal()] = 0;
246        }
247    }
248
249    public void debugPrint(XMLSecurityManager securityManager) {
250        Formatter formatter = new Formatter();
251        System.out.println(formatter.format("%30s %15s %15s %15s %30s",
252                "Property","Limit","Total size","Size","Entity Name"));
253
254        for (Limit limit : Limit.values()) {
255            formatter = new Formatter();
256            System.out.println(formatter.format("%30s %15d %15d %15d %30s",
257                    limit.name(),
258                    securityManager.getLimit(limit),
259                    totalValue[limit.ordinal()],
260                    values[limit.ordinal()],
261                    names[limit.ordinal()]));
262        }
263    }
264}
265