// 2) Ban content properties, and attr pseudo classes, and any other
// pseudo selectors that don't match the whitelist
t.node.acceptPreOrder(new Visitor() {
public boolean visit(AncestorChain<?> ancestors) {
ParseTreeNode node = ancestors.node;
if (node instanceof CssTree.Pseudo) {
boolean remove = false;
CssTree child = ((CssTree.Pseudo) node).children().get(0);
if (child instanceof CssTree.IdentLiteral) {
Name pseudoName = Name.css(
((CssTree.IdentLiteral) child).getValue());
if (!ALLOWED_PSEUDO_CLASSES.contains(pseudoName)) {
// Allow the visited pseudo selector but not with any styles
// that can be fetched via getComputedStyle in DOMita's
// COMPUTED_STYLE_WHITELIST.
if (!(LINK_PSEUDO_CLASSES.contains(pseudoName)
&& strippedPropertiesBannedInLinkClasses(
ancestors.parent.parent.cast(CssTree.Selector.class)
))) {
mq.addMessage(PluginMessageType.UNSAFE_CSS_PSEUDO_SELECTOR,
invalidNodeMessageLevel, node.getFilePosition(),
node);
remove = true;
}
}
} else {
StringBuilder rendered = new StringBuilder();
TokenConsumer tc = new CssPrettyPrinter(rendered);
node.render(new RenderContext(tc));
tc.noMoreTokens();
mq.addMessage(PluginMessageType.UNSAFE_CSS_PSEUDO_SELECTOR,
invalidNodeMessageLevel, node.getFilePosition(),
MessagePart.Factory.valueOf(rendered.toString()));
remove = true;
}
if (remove) {
// Delete the containing selector, since otherwise we'd broaden
// the rule.
selectorFor(ancestors).getAttributes().set(
CssValidator.INVALID, Boolean.TRUE);
}
}
return true;
}
}, t.parent);
// 3) Remove any properties and attributes that didn't validate
t.node.acceptPreOrder(new Visitor() {
public boolean visit(AncestorChain<?> ancestors) {
ParseTreeNode node = ancestors.node;
if (node instanceof CssTree.Property) {
if (node.getAttributes().is(CssValidator.INVALID)) {
declarationFor(ancestors).getAttributes().set(
CssValidator.INVALID, Boolean.TRUE);
}
} else if (node instanceof CssTree.Attrib) {
if (node.getAttributes().is(CssValidator.INVALID)) {
simpleSelectorFor(ancestors).getAttributes().set(
CssValidator.INVALID, Boolean.TRUE);
}
} else if (node instanceof CssTree.Term
&& (CssPropertyPartType.URI == propertyPartType(node))) {
boolean remove = false;
Message removeMsg = null;
CssTree term = (CssTree.Term) node;
CssTree.CssLiteral content =
(CssTree.CssLiteral) term.children().get(0);
if (content instanceof CssTree.Substitution) {
return true; // Handled by later pass.
}
String uriStr = content.getValue();
try {
URI baseUri = content.getFilePosition().source().getUri();
URI relUri = new URI(uriStr);
URI uri = baseUri.resolve(relUri);
ExternalReference ref = new ExternalReference(
uri, baseUri, relUri, content.getFilePosition());
Name propertyPart = propertyPart(node); // TODO
if (uriPolicy != null) {
String rewritten = UriPolicyNanny.apply(
uriPolicy,
ref, UriEffect.SAME_DOCUMENT, LoaderType.SANDBOXED,
Collections.singletonMap(
UriPolicyHintKey.CSS_PROP.key, propertyPart));
if (rewritten == null) {
removeMsg = new Message(
PluginMessageType.DISALLOWED_URI,
node.getFilePosition(),
MessagePart.Factory.valueOf(uriStr));
remove = true;
}
}
} catch (URISyntaxException ex) {
removeMsg = new Message(
PluginMessageType.DISALLOWED_URI,
node.getFilePosition(), MessagePart.Factory.valueOf(uriStr));
remove = true;
}
if (remove) {
// condemn the containing declaration
CssTree.Declaration decl = declarationFor(ancestors);
if (null != decl) {
if (!decl.getAttributes().is(CssValidator.INVALID)) {
if (null != removeMsg) { mq.getMessages().add(removeMsg); }
decl.getAttributes().set(CssValidator.INVALID, Boolean.TRUE);
}
}
}
}
return true;
}
}, t.parent);
// 4) Remove invalid nodes
removeInvalidNodes(t);
// 5) Cleanup. Remove any rulesets with empty selectors
// Since this is a post order traversal, we will first remove empty
// selectors, and then consider any rulesets that have become empty due to
// a lack of selectors.
t.node.acceptPreOrder(new Visitor() {
public boolean visit(AncestorChain<?> ancestors) {
ParseTreeNode node = ancestors.node;
if ((node instanceof CssTree.Selector && node.children().isEmpty())
|| (node instanceof CssTree.RuleSet
&& (node.children().isEmpty()
|| node.children().get(0) instanceof CssTree.Declaration))
) {
((MutableParseTreeNode) ancestors.parent.node).removeChild(node);
return false;
}
return true;