blob: 6dca669364e17edcd8f1406139f7df4e335a509c [file] [log] [blame]
// 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}.
*
* <pre>
* HtmlChangeReporter&lt;T&gt; hcr = new HtmlChangeReporter&lt;T&gt;(
* renderer, htmlChangeListener, context);
* hcr.setPolicy(policyFactory.apply(hcr.getWrappedRenderer()));
* HtmlSanitizer.sanitize(html, hcr.getWrappedPolicy());
* </pre>
*
* 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 <T> The type of context value passed to the
*/
public final class HtmlChangeReporter<T> {
private final OutputChannel output;
private final InputChannel<T> input;
public HtmlChangeReporter(
HtmlStreamEventReceiver renderer,
HtmlChangeListener<? super T> listener, @Nullable T context) {
this.output = new OutputChannel(renderer);
this.input = new InputChannel<T>(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<T> implements HtmlSanitizer.Policy {
HtmlStreamEventReceiver policy;
final OutputChannel output;
final T context;
final HtmlChangeListener<? super T> listener;
InputChannel(
OutputChannel output, HtmlChangeListener<? super T> 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<String> 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<String> expectedAttrNames = new LinkedHashSet<String>();
OutputChannel(HtmlStreamEventReceiver renderer) {
this.renderer = renderer;
}
public void openDocument() {
renderer.openDocument();
}
public void closeDocument() {
renderer.closeDocument();
}
public void openTag(String elementName, List<String> 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];
}