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 }