org.owasp.validator.html.model.Attribute Maven / Gradle / Ivy
Show all versions of antisamy Show documentation
/*
* Copyright (c) 2007-2022, Arshan Dabirsiaghi, Jason Li, Kristian Rosenvold
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of OWASP nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.owasp.validator.html.model;
import static org.owasp.validator.html.model.Tag.ANY_NORMAL_WHITESPACES;
import static org.owasp.validator.html.model.Tag.ATTRIBUTE_DIVIDER;
import static org.owasp.validator.html.model.Tag.CLOSE_ATTRIBUTE;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
/**
* A model for HTML attributes and the "rules" they must follow (either literals or regular
* expressions) in order to be considered valid.
*
* @author Arshan Dabirsiaghi
* @author Kristian Rosenvold
*/
public class Attribute {
private final String name;
private final String description;
private final String onInvalid;
private final List allowedValues;
private final Pattern[] allowedRegExps;
private final Set allowedValuesLower;
public Attribute(
String name,
List allowedRegexps,
List allowedValues,
String onInvalidStr,
String description) {
this.name = name;
this.allowedRegExps = allowedRegexps.toArray(new Pattern[allowedRegexps.size()]);
this.allowedValues = Collections.unmodifiableList(allowedValues);
Set allowedValuesLower = new HashSet();
for (String allowedValue : allowedValues) {
allowedValuesLower.add(allowedValue.toLowerCase());
}
this.allowedValuesLower = allowedValuesLower;
this.onInvalid = onInvalidStr;
this.description = description;
}
public boolean matchesAllowedExpression(String value) {
String input = value.toLowerCase();
for (Pattern pattern : allowedRegExps) {
if (pattern != null && pattern.matcher(input).matches()) {
return true;
}
}
return false;
}
public boolean containsAllowedValue(String valueInLowerCase) {
return allowedValuesLower.contains(valueInLowerCase);
}
public String getName() {
return name;
}
/**
* @return The onInvalid
value a tag could have, from the list of "filterTag",
* "removeTag" and "removeAttribute"
*/
public String getOnInvalid() {
return onInvalid;
}
public Attribute mutate(String onInvalid, String description) {
return new Attribute(
name,
Arrays.asList(allowedRegExps),
allowedValues,
onInvalid != null && onInvalid.length() != 0 ? onInvalid : this.onInvalid,
description != null && description.length() != 0 ? description : this.description);
}
public String matcherRegEx(boolean hasNext) {
//
StringBuilder regExp = new StringBuilder();
regExp
.append(this.getName())
.append(ANY_NORMAL_WHITESPACES)
.append("=")
.append(ANY_NORMAL_WHITESPACES)
.append("\"")
.append(Tag.OPEN_ATTRIBUTE);
boolean hasRegExps = allowedRegExps.length > 0;
if (allowedRegExps.length + allowedValues.size() > 0) {
/*
* Go through and add static values to the regular expression.
*/
Iterator allowedValues = this.allowedValues.iterator();
while (allowedValues.hasNext()) {
String allowedValue = allowedValues.next();
regExp.append(Tag.escapeRegularExpressionCharacters(allowedValue));
if (allowedValues.hasNext() || hasRegExps) {
regExp.append(ATTRIBUTE_DIVIDER);
}
}
/*
* Add the regular expressions for this attribute value to the mother regular expression.
*/
Iterator allowedRegExps = Arrays.asList(this.allowedRegExps).iterator();
while (allowedRegExps.hasNext()) {
Pattern allowedRegExp = allowedRegExps.next();
regExp.append(allowedRegExp.pattern());
if (allowedRegExps.hasNext()) {
regExp.append(ATTRIBUTE_DIVIDER);
}
}
if (this.allowedRegExps.length + this.allowedValues.size() > 0) {
regExp.append(CLOSE_ATTRIBUTE);
}
regExp.append("\"" + ANY_NORMAL_WHITESPACES);
if (hasNext) {
regExp.append(ATTRIBUTE_DIVIDER);
}
}
return regExp.toString();
}
/**
* This method takes the current rel
attribute values and, depending on which ones to
* add, appends the corresponding values if they are not already present. It is meant to be used
* with anchor tags.
*
* @param addNofollow Specifies if "nofollow"
value should be added in case it is not
* present.
* @param addNoopenerAndNoreferrer Specifies if "noopener noreferrer"
value should be
* added in case it is not present.
* @param currentRelValue Current rel
attribute value, it will be merged with the
* values specified from the previous parameters.
* @return The new rel
attribute value to replace in an anchor tag.
*/
public static String mergeRelValuesInAnchor(
boolean addNofollow, boolean addNoopenerAndNoreferrer, String currentRelValue) {
String newRelValue = "";
if (currentRelValue == null || currentRelValue.isEmpty()) {
if (addNofollow) newRelValue = "nofollow";
if (addNoopenerAndNoreferrer) newRelValue += " noopener noreferrer";
} else {
ArrayList relTokens = new ArrayList<>();
newRelValue = currentRelValue;
for (String value : currentRelValue.split(" ")) {
relTokens.add(value.toLowerCase());
}
if (addNofollow && !relTokens.contains("nofollow")) {
newRelValue += " nofollow";
}
if (addNoopenerAndNoreferrer) {
if (!relTokens.contains("noopener")) {
newRelValue += " noopener";
}
if (!relTokens.contains("noreferrer")) {
newRelValue += " noreferrer";
}
}
}
return newRelValue.trim();
}
}