001 // Copyright (c) 2011, Mike Samuel
002 // All rights reserved.
003 //
004 // Redistribution and use in source and binary forms, with or without
005 // modification, are permitted provided that the following conditions
006 // are met:
007 //
008 // Redistributions of source code must retain the above copyright
009 // notice, this list of conditions and the following disclaimer.
010 // Redistributions in binary form must reproduce the above copyright
011 // notice, this list of conditions and the following disclaimer in the
012 // documentation and/or other materials provided with the distribution.
013 // Neither the name of the OWASP nor the names of its contributors may
014 // be used to endorse or promote products derived from this software
015 // without specific prior written permission.
016 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
017 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
018 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
019 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
020 // COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
021 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
022 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
024 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
025 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
026 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
027 // POSSIBILITY OF SUCH DAMAGE.
028
029 package org.owasp.html;
030
031 import com.google.common.collect.ImmutableList;
032 import java.util.Collection;
033 import java.util.Set;
034 import java.util.LinkedHashSet;
035 import javax.annotation.CheckReturnValue;
036 import javax.annotation.Nullable;
037 import javax.annotation.concurrent.Immutable;
038
039 /**
040 * A policy that can be applied to an HTML attribute to decide whether or not to
041 * allow it in the output, possibly after transforming its value.
042 *
043 * @author Mike Samuel <mikesamuel@gmail.com>
044 * @see HtmlPolicyBuilder.AttributeBuilder#matching(AttributePolicy)
045 */
046 @TCB public interface AttributePolicy {
047
048 /**
049 * @param elementName the lower-case element name.
050 * @param attributeName the lower-case attribute name.
051 * @param value the attribute value without quotes and with HTML entities
052 * decoded.
053 *
054 * @return {@code null} to disallow the attribute or the adjusted value if
055 * allowed.
056 */
057 public @Nullable String apply(
058 String elementName, String attributeName, String value);
059
060
061 /** Utilities for working with attribute policies. */
062 public static final class Util {
063
064 /**
065 * An attribute policy equivalent to applying all the given policies in
066 * order, failing early if any of them fails.
067 */
068 @CheckReturnValue
069 public static final AttributePolicy join(AttributePolicy... policies) {
070 Set<AttributePolicy> uniq = new LinkedHashSet<AttributePolicy>();
071 for (AttributePolicy p : policies) {
072 if (p instanceof JoinedAttributePolicy) {
073 uniq.addAll(((JoinedAttributePolicy) p).policies);
074 } else if (p != null) {
075 uniq.add(p);
076 }
077 }
078
079 if (uniq.contains(REJECT_ALL_ATTRIBUTE_POLICY)) {
080 return REJECT_ALL_ATTRIBUTE_POLICY;
081 }
082 uniq.remove(IDENTITY_ATTRIBUTE_POLICY);
083 switch (uniq.size()) {
084 case 0: return IDENTITY_ATTRIBUTE_POLICY;
085 case 1: return uniq.iterator().next();
086 default: return new JoinedAttributePolicy(uniq);
087 }
088 }
089 }
090
091
092 public static final AttributePolicy IDENTITY_ATTRIBUTE_POLICY
093 = new AttributePolicy() {
094 public String apply(
095 String elementName, String attributeName, String value) {
096 return value;
097 }
098 };
099
100 public static final AttributePolicy REJECT_ALL_ATTRIBUTE_POLICY
101 = new AttributePolicy() {
102 public @Nullable String apply(
103 String elementName, String attributeName, String value) {
104 return null;
105 }
106 };
107
108 }
109
110 @Immutable
111 final class JoinedAttributePolicy implements AttributePolicy {
112 final ImmutableList<AttributePolicy> policies;
113
114 JoinedAttributePolicy(Collection<? extends AttributePolicy> policies) {
115 this.policies = ImmutableList.copyOf(policies);
116 }
117
118 public @Nullable String apply(
119 String elementName, String attributeName, @Nullable String value) {
120 for (AttributePolicy p : policies) {
121 if (value == null) { break; }
122 value = p.apply(elementName, attributeName, value);
123 }
124 return value;
125 }
126
127 @Override
128 public boolean equals(Object o) {
129 return o != null && this.getClass() == o.getClass()
130 && policies.equals(((JoinedAttributePolicy) o).policies);
131 }
132
133 @Override
134 public int hashCode() {
135 return policies.hashCode();
136 }
137 }