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 java.util.List;
032    
033    import javax.annotation.Nullable;
034    import javax.annotation.concurrent.Immutable;
035    
036    /**
037     * A policy that can be applied to an element to decide whether or not to
038     * allow it in the output, possibly after transforming attributes.
039     * <p>
040     * Element policies are applied <strong>after</strong>
041     * {@link AttributePolicy attribute policies} so
042     * they can be used to add extra attributes.
043     *
044     * @author Mike Samuel <mikesamuel@gmail.com>
045     * @see HtmlPolicyBuilder#allowElements(ElementPolicy, String...)
046     */
047    @TCB public interface ElementPolicy {
048      /**
049       * @param elementName the lower-case element name.
050       * @param attrs a list of alternating attribute names and values.
051       *    The list may be added to or removed from.  When removing, be
052       *    careful to remove both the name and its associated value.
053       *
054       * @return {@code null} to disallow the element, or the adjusted element name.
055       */
056      public @Nullable String apply(String elementName, List<String> attrs);
057    
058    
059      /** Utilities for working with element policies. */
060      public static final class Util {
061        private Util() { /* uninstantiable */ }
062    
063        /**
064         * Given zero or more element policies, returns an element policy equivalent
065         * to applying them in order failing early if any of them fails.
066         */
067        public static final ElementPolicy join(ElementPolicy... policies) {
068    
069          class PolicyJoiner {
070            ElementPolicy last = null;
071            ElementPolicy out = null;
072    
073            void join(ElementPolicy p) {
074              if (p == REJECT_ALL_ELEMENT_POLICY) {
075                out = p;
076              } else if (out != REJECT_ALL_ELEMENT_POLICY) {
077                if (p instanceof JoinedElementPolicy) {
078                  JoinedElementPolicy jep = (JoinedElementPolicy) p;
079                  join(jep.first);
080                  join(jep.second);
081                } else if (p != last) {
082                  last = p;
083                  if (out == null || out == IDENTITY_ELEMENT_POLICY) {
084                    out = p;
085                  } else if (p != IDENTITY_ELEMENT_POLICY) {
086                    out = new JoinedElementPolicy(out, p);
087                  }
088                }
089              }
090            }
091          }
092    
093          PolicyJoiner pu = new PolicyJoiner();
094          for (ElementPolicy policy : policies) {
095            if (policy == null) { continue; }
096            pu.join(policy);
097          }
098          return pu.out != null ? pu.out : IDENTITY_ELEMENT_POLICY;
099        }
100    
101      }
102    
103      public static final ElementPolicy IDENTITY_ELEMENT_POLICY
104          = new ElementPolicy() {
105        public String apply(String elementName, List<String> attrs) {
106          return elementName;
107        }
108      };
109    
110      public static final ElementPolicy REJECT_ALL_ELEMENT_POLICY
111          = new ElementPolicy() {
112        public @Nullable String apply(String elementName, List<String> attrs) {
113          return null;
114        }
115      };
116    
117    }
118    
119    @Immutable
120    final class JoinedElementPolicy implements ElementPolicy {
121      final ElementPolicy first, second;
122    
123      JoinedElementPolicy(ElementPolicy first, ElementPolicy second) {
124        this.first = first;
125        this.second = second;
126      }
127    
128      public @Nullable String apply(String elementName, List<String> attrs) {
129        elementName = first.apply(elementName, attrs);
130        return elementName != null ? second.apply(elementName, attrs) : null;
131      }
132    }