blob: 35daab0df00c355895c6ab51c3cb14d31efada69 [file] [log] [blame]
/*
* 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.struts2.json;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.WildcardUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.*;
import java.util.regex.Pattern;
/**
* <p>Isolate the process of cleaning JSON data from the Interceptor class
* itself.</p>
*
* <p>The allowed and blocked wildcard patterns, combined with
* defaultBlock, let you filter out values that should not be injected, in
* the same way that ParameterFilterInterceptor does. Note that you can
* only remove values from a Map. Removing values from a List is dangerous
* because it could change the meaning of the data!</p>
*/
public abstract class JSONCleaner {
private static final Logger LOG = LogManager.getLogger(JSONCleaner.class);
public static class Filter
{
public Pattern pattern;
public boolean allow;
public Filter(String pattern, boolean allow)
{
this.pattern = WildcardUtil.compileWildcardPattern(pattern);
this.allow = allow;
}
}
private boolean defaultBlock = false;
private Collection<String> allowed;
private Collection<String> blocked;
private Map<String, Filter> includesExcludesMap;
public Object clean(String ognlPrefix, Object data) throws JSONException {
if (data == null)
return null;
else if (data instanceof List)
return cleanList(ognlPrefix, data);
else if (data instanceof Map)
return cleanMap(ognlPrefix, data);
else
return cleanValue(ognlPrefix, data);
}
protected Object cleanList(String ognlPrefix, Object data) throws JSONException {
List list = (List) data;
int count = list.size();
for (int i = 0; i < count; i++) {
list.set(i, clean(ognlPrefix + "[" + i + "]", list.get(i)));
}
return list;
}
protected Object cleanMap(String ognlPrefix, Object data) throws JSONException {
Map map = (Map) data;
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry e = (Map.Entry) iter.next();
String key = (ognlPrefix.length() > 0 ? ognlPrefix + "." : "") + e.getKey();
if (allow(key)) {
e.setValue(clean(key, e.getValue()));
} else {
LOG.debug("Blocked: {}", key);
iter.remove();
}
}
return map;
}
protected abstract Object cleanValue(String ognlName, Object data) throws JSONException;
private boolean allow(String ognl) {
Map<String, Filter> includesExcludesMap = getIncludesExcludesMap();
boolean allow = !isDefaultBlock();
if (includesExcludesMap != null) {
for (Filter f : includesExcludesMap.values()) {
if (f.pattern.matcher(ognl).matches()) {
allow = f.allow;
}
}
}
return allow;
}
/**
* @return the compiled list of includes and excludes
*/
public Map<String, Filter> getIncludesExcludesMap() {
if (allowed == null && blocked == null) {
return includesExcludesMap;
}
if (includesExcludesMap == null) {
includesExcludesMap = new TreeMap<>();
Map<String, Boolean> existingExpr = new HashMap<>();
Map<String, Map<String, String>> includePatternData = JSONUtil.getIncludePatternData();
String splitPattern = includePatternData.get(JSONUtil.SPLIT_PATTERN).get(JSONUtil.WILDCARD_PATTERN);
String joinString = includePatternData.get(JSONUtil.JOIN_STRING).get(JSONUtil.WILDCARD_PATTERN);
String arrayBegin = includePatternData.get(JSONUtil.ARRAY_BEGIN_STRING).get(JSONUtil.WILDCARD_PATTERN);
String arrayEnd = includePatternData.get(JSONUtil.ARRAY_END_STRING).get(JSONUtil.WILDCARD_PATTERN);
if (allowed != null) {
for (String a : allowed) {
// Compile a pattern for each level of the object hierarchy
// so cleanMap() won't short-circuit too early.
String expr = "";
for (String piece : a.split(splitPattern)) {
if (expr.length() > 0) {
expr += joinString;
}
expr += piece;
if (!existingExpr.containsKey(expr)) {
existingExpr.put(expr, Boolean.TRUE);
String s = expr;
if (piece.endsWith(arrayEnd)) {
s = expr.substring(0, expr.lastIndexOf(arrayBegin));
}
if (s.length() > 0) {
includesExcludesMap.put(s, new Filter(s, true));
LOG.debug("Adding include wildcard expression: {}", s);
}
}
}
}
}
if (blocked != null) {
for (String b : blocked) {
includesExcludesMap.put(b, new Filter(b, false));
}
}
}
return includesExcludesMap;
}
/**
* Allow external caching of the compiled result.
*
* @param map the compiled list of includes and excludes
*/
public void setIncludesExcludesMap(Map<String, Filter> map) {
includesExcludesMap = map;
}
/**
* @return value of defaultBlock
*/
public boolean isDefaultBlock() {
return defaultBlock;
}
/**
* @param defaultExclude The defaultExclude to set.
*/
public void setDefaultBlock(boolean defaultExclude) {
this.defaultBlock = defaultExclude;
}
/**
* @return list of blocked wildcard patterns
*/
public Collection<String> getBlockedCollection() {
return blocked;
}
/**
* @param blocked The blocked to set.
*/
public void setBlockedCollection(Collection<String> blocked) {
this.blocked = blocked;
}
/**
* @param blocked The blocked parameters as comma separated String.
*/
public void setBlocked(String blocked) {
setBlockedCollection(asCollection(blocked));
}
/**
* @return list of allowed wildcard patterns
*/
public Collection<String> getAllowedCollection() {
return allowed;
}
/**
* @param allowed The allowed to set.
*/
public void setAllowedCollection(Collection<String> allowed) {
this.allowed = allowed;
}
/**
* @param allowed The allowed paramters as comma separated String.
*/
public void setAllowed(String allowed) {
setAllowedCollection(asCollection(allowed));
}
/**
* Return a collection from the comma delimited String.
*
* @param commaDelim the comma delimited String.
* @return A collection from the comma delimited String. Returns <tt>null</tt> if the string is empty.
*/
private Collection<String> asCollection(String commaDelim) {
if (commaDelim == null || commaDelim.trim().length() == 0) {
return null;
}
return TextParseUtil.commaDelimitedStringToSet(commaDelim);
}
}