// Copyright (c) 2011, Mike Samuel // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // // Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // Neither the name of the OWASP nor the names of its contributors may // be used to endorse or promote products derived from this software // without specific prior written permission. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE // COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. package org.owasp.html; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.annotation.Nullable; /** * Sits between the HTML parser, and then policy, and the renderer so that it * can report dropped elements and attributes to an {@link HtmlChangeListener}. * *
 * HtmlChangeReporter<T> hcr = new HtmlChangeReporter<T>(
 *   renderer, htmlChangeListener, context);
 * hcr.setPolicy(policyFactory.apply(hcr.getWrappedRenderer()));
 * HtmlSanitizer.sanitize(html, hcr.getWrappedPolicy());
 * 
* * The renderer receives events from the policy unchanged, but the reporter * notices differences between the events from the lexer and those from the * policy. * * @param The type of context value passed to the */ public final class HtmlChangeReporter { private final OutputChannel output; private final InputChannel input; public HtmlChangeReporter( HtmlStreamEventReceiver renderer, HtmlChangeListener listener, @Nullable T context) { this.output = new OutputChannel(renderer); this.input = new InputChannel(output, listener, context); } /** * Associates an input channel. {@code this} receives events and forwards * them to input. */ public void setPolicy(HtmlSanitizer.Policy policy) { this.input.policy = policy; } public HtmlStreamEventReceiver getWrappedRenderer() { return output; } public HtmlSanitizer.Policy getWrappedPolicy() { return input; } private static final class InputChannel implements HtmlSanitizer.Policy { HtmlStreamEventReceiver policy; final OutputChannel output; final T context; final HtmlChangeListener listener; InputChannel( OutputChannel output, HtmlChangeListener listener, @Nullable T context) { this.output = output; this.context = context; this.listener = listener; } public void openDocument() { policy.openDocument(); } public void closeDocument() { policy.closeDocument(); } public void openTag(String elementName, List attrs) { output.expectedElementName = elementName; output.expectedAttrNames.clear(); for (int i = 0, n = attrs.size(); i < n; i += 2) { output.expectedAttrNames.add(attrs.get(i)); } policy.openTag(elementName, attrs); { // Gather the notification details to avoid any problems with the // listener re-entering the stream event receiver. This shouldn't // occur, but if it does it will be a source of subtle confusing bugs. String discardedElementName = output.expectedElementName; output.expectedElementName = null; int nExpected = output.expectedAttrNames.size(); String[] discardedAttrNames = nExpected != 0 && discardedElementName == null ? output.expectedAttrNames.toArray(new String[nExpected]) : ZERO_STRINGS; output.expectedAttrNames.clear(); // Dispatch notifications to the listener. if (discardedElementName != null) { listener.discardedTag(context, discardedElementName); } if (discardedAttrNames.length != 0) { listener.discardedAttributes( context, elementName, discardedAttrNames); } } } public void closeTag(String elementName) { policy.closeTag(elementName); } public void text(String textChunk) { policy.text(textChunk); } } private static final class OutputChannel implements HtmlStreamEventReceiver { private final HtmlStreamEventReceiver renderer; String expectedElementName; Set expectedAttrNames = new LinkedHashSet(); OutputChannel(HtmlStreamEventReceiver renderer) { this.renderer = renderer; } public void openDocument() { renderer.openDocument(); } public void closeDocument() { renderer.closeDocument(); } public void openTag(String elementName, List attrs) { if (elementName.equals(expectedElementName)) { expectedElementName = null; } for (int i = 0, n = attrs.size(); i < n; i += 2) { expectedAttrNames.remove(attrs.get(i)); } renderer.openTag(elementName, attrs); } public void closeTag(String elementName) { renderer.closeTag(elementName); } public void text(String text) { renderer.text(text); } } private static final String[] ZERO_STRINGS = new String[0]; }