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    }