blob: 41f22ba91053604376da5e19b8fb9ca091a182a4 [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.owasp.html;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
/**
* Extends the default policy to support dynamic attributes.
*
* Since we want to support the antisamy config file, we have to make dynamic
* tag attributes possible.
* It is placed in the 'org.owasp.html' package because there are package
* private fields.
*/
public class DynamicAttributesSanitizerPolicy extends ElementAndAttributePolicyBasedSanitizerPolicy {
private Map<String, ElementAndAttributePolicies> elementAndAttrPolicies;
private Map<String, AttributePolicy> dynamicAttributesPolicyMap;
private List<String> onInvalidRemoveTagList;
private int numberOfErrors;
public DynamicAttributesSanitizerPolicy(HtmlStreamEventReceiver out,
Map<String, ElementAndAttributePolicies> elAndAttrPolicies,
Set<String> allowedTextContainers,
Map<String, AttributePolicy> dynamicAttributesPolicyMap, List<String> onInvalidRemoveTagList) {
super(out, elAndAttrPolicies, allowedTextContainers);
this.elementAndAttrPolicies = elAndAttrPolicies;
this.dynamicAttributesPolicyMap = dynamicAttributesPolicyMap;
this.onInvalidRemoveTagList = onInvalidRemoveTagList;
}
@Override
public void openTag(String elementName, List<String> attrs) {
// StylingPolicy repeats some of this code because it is more complicated
// to refactor it into multiple method bodies, so if you change this,
// check the override of it in that class.
if (elementName != null && attrs != null && elementAndAttrPolicies != null) {
ElementAndAttributePolicies policies = elementAndAttrPolicies.get(elementName);
String adjustedElementName = applyPolicies2(elementName, attrs, policies);
if (adjustedElementName != null
&& !(attrs.isEmpty() && policies.htmlTagSkipType.skipAvailability())) {
writeOpenTag(policies, adjustedElementName, attrs);
return;
}
deferOpenTag(elementName);
}
}
final @Nullable String applyPolicies2(
String elementName, List<String> attrs,
ElementAndAttributePolicies policies) {
String adjustedElementName;
Boolean removeTag = false;
if (policies != null) {
for (ListIterator<String> attrsIt = attrs.listIterator(); attrsIt.hasNext();) {
String name = attrsIt.next();
AttributePolicy attrPolicy = null;
// check if the attribute name starts with an dynamic tag, to handle it specially
for (Entry<String, AttributePolicy> dynamicAttributeEntry : dynamicAttributesPolicyMap.entrySet()) {
if (name.startsWith(dynamicAttributeEntry.getKey())) {
attrPolicy = dynamicAttributeEntry.getValue();
break;
}
}
// if it is not an dynamic attr it gets it's normal policy
if (attrPolicy == null) {
attrPolicy = policies.attrPolicies.get(name);
}
// if there is no policy for this attribute, it gets removed
if (attrPolicy == null) {
numberOfErrors++;
attrsIt.remove();
attrsIt.next();
attrsIt.remove();
} else {
String value = attrsIt.next();
String adjustedValue = attrPolicy.apply(elementName, name, value);
if (adjustedValue == null) {
numberOfErrors++;
if (onInvalidRemoveTagList.contains(name)) {
removeTag = true;
}
attrsIt.remove();
attrsIt.previous();
attrsIt.remove();
} else {
attrsIt.set(adjustedValue);
}
}
}
try {
Method removeDuplicateAttributesMethod = ElementAndAttributePolicyBasedSanitizerPolicy.class
.getDeclaredMethod("removeDuplicateAttributes", List.class);
removeDuplicateAttributesMethod.setAccessible(true);
try {
removeDuplicateAttributesMethod.invoke(null, attrs);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException(e);
}
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
// checks if the onInvalid action of the invalid Tag is remove, and then removes
// it
adjustedElementName = Boolean.TRUE.equals(removeTag) ? null : policies.elPolicy.apply(elementName, attrs);
if (adjustedElementName != null) {
adjustedElementName = HtmlLexer.canonicalElementName(adjustedElementName);
}
} else {
numberOfErrors++;
adjustedElementName = null;
}
return adjustedElementName;
}
public int getNumberOfErrors() {
return numberOfErrors;
}
}