1// Copyright (c) 2011, Mike Samuel
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions
6// are met:
7//
8// Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10// Redistributions in binary form must reproduce the above copyright
11// notice, this list of conditions and the following disclaimer in the
12// documentation and/or other materials provided with the distribution.
13// Neither the name of the OWASP nor the names of its contributors may
14// be used to endorse or promote products derived from this software
15// without specific prior written permission.
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
20// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27// POSSIBILITY OF SUCH DAMAGE.
28
29package org.owasp.html;
30
31import com.google.common.collect.ImmutableList;
32import java.util.Collection;
33import java.util.Set;
34import java.util.LinkedHashSet;
35import javax.annotation.CheckReturnValue;
36import javax.annotation.Nullable;
37import javax.annotation.concurrent.Immutable;
38
39/**
40 * A policy that can be applied to an HTML attribute to decide whether or not to
41 * allow it in the output, possibly after transforming its value.
42 *
43 * @author Mike Samuel <mikesamuel@gmail.com>
44 * @see HtmlPolicyBuilder.AttributeBuilder#matching(AttributePolicy)
45 */
46@TCB public interface AttributePolicy {
47
48  /**
49   * @param elementName the lower-case element name.
50   * @param attributeName the lower-case attribute name.
51   * @param value the attribute value without quotes and with HTML entities
52   *     decoded.
53   *
54   * @return {@code null} to disallow the attribute or the adjusted value if
55   *     allowed.
56   */
57  public @Nullable String apply(
58      String elementName, String attributeName, String value);
59
60
61  /** Utilities for working with attribute policies. */
62  public static final class Util {
63
64    /**
65     * An attribute policy equivalent to applying all the given policies in
66     * order, failing early if any of them fails.
67     */
68    @CheckReturnValue
69    public static final AttributePolicy join(AttributePolicy... policies) {
70      Set<AttributePolicy> uniq = new LinkedHashSet<AttributePolicy>();
71      for (AttributePolicy p : policies) {
72        if (p instanceof JoinedAttributePolicy) {
73          uniq.addAll(((JoinedAttributePolicy) p).policies);
74        } else if (p != null) {
75          uniq.add(p);
76        }
77      }
78
79      if (uniq.contains(REJECT_ALL_ATTRIBUTE_POLICY)) {
80        return REJECT_ALL_ATTRIBUTE_POLICY;
81      }
82      uniq.remove(IDENTITY_ATTRIBUTE_POLICY);
83      switch (uniq.size()) {
84        case 0:  return IDENTITY_ATTRIBUTE_POLICY;
85        case 1:  return uniq.iterator().next();
86        default: return new JoinedAttributePolicy(uniq);
87      }
88    }
89  }
90
91
92  public static final AttributePolicy IDENTITY_ATTRIBUTE_POLICY
93      = new AttributePolicy() {
94        public String apply(
95            String elementName, String attributeName, String value) {
96          return value;
97        }
98      };
99
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
111final 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}
138