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.Map;
032    
033    import javax.annotation.Nonnull;
034    import javax.annotation.Nullable;
035    import javax.annotation.concurrent.Immutable;
036    import javax.annotation.concurrent.ThreadSafe;
037    
038    import com.google.common.base.Function;
039    import com.google.common.collect.ImmutableMap;
040    import com.google.common.collect.ImmutableSet;
041    
042    /**
043     * A factory that can be used to link a sanitizer to an output receiver and that
044     * provides a convenient <code>{@link PolicyFactory#sanitize sanitize}</code>
045     * method and a <code>{@link PolicyFactory#and and}</code> method to compose
046     * policies.
047     *
048     * @author Mike Samuel <mikesamuel@gmail.com>
049     */
050    @ThreadSafe
051    @Immutable
052    @TCB
053    public final class PolicyFactory
054        implements Function<HtmlStreamEventReceiver, HtmlSanitizer.Policy> {
055    
056      private final ImmutableMap<String, ElementAndAttributePolicies> policies;
057      private final ImmutableSet<String> textContainers;
058    
059      PolicyFactory(ImmutableMap<String, ElementAndAttributePolicies> policies,
060                    ImmutableSet<String> textContainers) {
061        this.policies = policies;
062        this.textContainers = textContainers;
063      }
064    
065      /** Produces a sanitizer that emits tokens to {@code out}. */
066      public HtmlSanitizer.Policy apply(@Nonnull HtmlStreamEventReceiver out) {
067        return new ElementAndAttributePolicyBasedSanitizerPolicy(
068            out, policies, textContainers);
069      }
070    
071      /**
072       * Produces a sanitizer that emits tokens to {@code out} and that notifies
073       * any {@code listener} of any dropped tags and attributes.
074       * @param out a renderer that receives approved tokens only.
075       * @param listener if non-null, receives notifications of tags and attributes
076       *     that were rejected by the policy.  This may tie into intrusion
077       *     detection systems.
078       * @param context if {@code (listener != null)} then the context value passed
079       *     with notifications.  This can be used to let the listener know from
080       *     which connection or request the questionable HTML was received.
081       */
082      public <CTX> HtmlSanitizer.Policy apply(
083          HtmlStreamEventReceiver out, @Nullable HtmlChangeListener<CTX> listener,
084          @Nullable CTX context) {
085        if (listener == null) {
086          return apply(out);
087        } else {
088          HtmlChangeReporter<CTX> r = new HtmlChangeReporter<CTX>(
089              out, listener, context);
090          r.setPolicy(apply(r.getWrappedRenderer()));
091          return r.getWrappedPolicy();
092        }
093      }
094    
095      /** A convenience function that sanitizes a string of HTML. */
096      public String sanitize(@Nullable String html) {
097        return sanitize(html, null, null);
098      }
099    
100      /**
101       * A convenience function that sanitizes a string of HTML and reports
102       * the names of rejected element and attributes to listener.
103       * @param html the string of HTML to sanitize.
104       * @param listener if non-null, receives notifications of tags and attributes
105       *     that were rejected by the policy.  This may tie into intrusion
106       *     detection systems.
107       * @param context if {@code (listener != null)} then the context value passed
108       *     with notifications.  This can be used to let the listener know from
109       *     which connection or request the questionable HTML was received.
110       * @return a string of HTML that complies with this factory's policy.
111       */
112      public <CTX> String sanitize(
113          @Nullable String html,
114          @Nullable HtmlChangeListener<CTX> listener, @Nullable CTX context) {
115        if (html == null) { return ""; }
116        StringBuilder out = new StringBuilder(html.length());
117        HtmlSanitizer.sanitize(
118            html,
119            apply(HtmlStreamRenderer.create(out, Handler.DO_NOTHING),
120                  listener, context));
121        return out.toString();
122      }
123    
124      /**
125       * Produces a factory that allows the union of the grants, and intersects
126       * policies where they overlap on a particular granted attribute or element
127       * name.
128       */
129      public PolicyFactory and(PolicyFactory f) {
130        ImmutableMap.Builder<String, ElementAndAttributePolicies> b
131            = ImmutableMap.builder();
132        for (Map.Entry<String, ElementAndAttributePolicies> e
133            : policies.entrySet()) {
134          String elName = e.getKey();
135          ElementAndAttributePolicies p = e.getValue();
136          ElementAndAttributePolicies q = f.policies.get(elName);
137          if (q != null) {
138            p = p.and(q);
139          }
140          b.put(elName, p);
141        }
142        for (Map.Entry<String, ElementAndAttributePolicies> e
143            : f.policies.entrySet()) {
144          String elName = e.getKey();
145          if (!policies.containsKey(elName)) {
146            b.put(elName, e.getValue());
147          }
148        }
149        ImmutableSet<String> textContainers;
150        if (this.textContainers.containsAll(f.textContainers)) {
151          textContainers = this.textContainers;
152        } else if (f.textContainers.containsAll(this.textContainers)) {
153          textContainers = f.textContainers;
154        } else {
155          textContainers = ImmutableSet.<String>builder()
156            .addAll(this.textContainers)
157            .addAll(f.textContainers)
158            .build();
159        }
160        return new PolicyFactory(b.build(), textContainers);
161      }
162    }