Skip to content

Union policy skipIfEmpty value issue #199

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 2, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 5 additions & 18 deletions src/main/java/org/owasp/html/ElementAndAttributePolicies.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,18 @@ final class ElementAndAttributePolicies {
final String elementName;
final ElementPolicy elPolicy;
final ImmutableMap<String, AttributePolicy> attrPolicies;
final boolean skipIfEmpty;
final HtmlTagSkipType htmlTagSkipType;

ElementAndAttributePolicies(
String elementName,
ElementPolicy elPolicy,
Map<? extends String, ? extends AttributePolicy>
attrPolicies,
boolean skipIfEmpty) {
HtmlTagSkipType htmlTagSkipType) {
this.elementName = elementName;
this.elPolicy = elPolicy;
this.attrPolicies = ImmutableMap.copyOf(attrPolicies);
this.skipIfEmpty = skipIfEmpty;
this.htmlTagSkipType = htmlTagSkipType;
}

ElementAndAttributePolicies and(ElementAndAttributePolicies p) {
Expand All @@ -78,24 +78,11 @@ ElementAndAttributePolicies and(ElementAndAttributePolicies p) {
}
}

// HACK: this is attempting to recognize when skipIfEmpty has been
// explicitly set in HtmlPolicyBuilder and can only make a best effort at
// that and is also too tightly coupled with HtmlPolicyBuilder.
// Maybe go tri-state.
boolean combinedSkipIfEmpty;
if (HtmlPolicyBuilder.DEFAULT_SKIP_IF_EMPTY.contains(elementName)) {
// Either policy explicitly opted out of skip if empty.
combinedSkipIfEmpty = skipIfEmpty && p.skipIfEmpty;
} else {
// Either policy explicitly specified skip if empty.
combinedSkipIfEmpty = skipIfEmpty || p.skipIfEmpty;
}

return new ElementAndAttributePolicies(
elementName,
ElementPolicy.Util.join(elPolicy, p.elPolicy),
joinedAttrPolicies.build(),
combinedSkipIfEmpty);
this.htmlTagSkipType.and(p.htmlTagSkipType));
}

ElementAndAttributePolicies andGlobals(
Expand Down Expand Up @@ -130,7 +117,7 @@ ElementAndAttributePolicies andGlobals(
}
if (anded == null) { return this; }
return new ElementAndAttributePolicies(
elementName, elPolicy, anded, skipIfEmpty);
elementName, elPolicy, anded, htmlTagSkipType);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public void openTag(String elementName, List<String> attrs) {
ElementAndAttributePolicies policies = elAndAttrPolicies.get(elementName);
String adjustedElementName = applyPolicies(elementName, attrs, policies);
if (adjustedElementName != null
&& !(attrs.isEmpty() && policies.skipIfEmpty)) {
&& !(attrs.isEmpty() && policies.htmlTagSkipType.skipAvailability())) {
writeOpenTag(policies, adjustedElementName, attrs);
return;
}
Expand Down
42 changes: 31 additions & 11 deletions src/main/java/org/owasp/html/HtmlPolicyBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,19 @@
@NotThreadSafe
public class HtmlPolicyBuilder {
/**
* The default set of elements that are removed if they have no attributes.
* Since {@code <img>} is in this set, by default, a policy will remove
* The default map of elements that are removed if they have no attributes.
* Since {@code <img>} is in this map, by default, a policy will remove
* {@code <img src=javascript:alert(1337)>} because its URL is not allowed
* and it has no other attributes that would warrant it appearing in the
* output.
*/
public static final ImmutableSet<String> DEFAULT_SKIP_IF_EMPTY
= ImmutableSet.of("a", "font", "img", "input", "span");
public static final ImmutableMap<String, HtmlTagSkipType> DEFAULT_SKIP_TAG_MAP_IF_EMPTY_ATTR
= ImmutableMap.of(
"a", HtmlTagSkipType.SKIP_BY_DEFAULT,
"font", HtmlTagSkipType.SKIP_BY_DEFAULT,
"img", HtmlTagSkipType.SKIP_BY_DEFAULT,
"input", HtmlTagSkipType.SKIP_BY_DEFAULT,
"span", HtmlTagSkipType.SKIP_BY_DEFAULT);

/**
* These
Expand All @@ -190,8 +195,7 @@ public class HtmlPolicyBuilder {
private final Map<String, AttributePolicy> globalAttrPolicies
= Maps.newLinkedHashMap();
private final Set<String> allowedProtocols = Sets.newLinkedHashSet();
private final Set<String> skipIfEmpty = Sets.newLinkedHashSet(
DEFAULT_SKIP_IF_EMPTY);
private final Map<String, HtmlTagSkipType> skipIssueTagMap = Maps.newLinkedHashMap(DEFAULT_SKIP_TAG_MAP_IF_EMPTY_ATTR);
private final Map<String, Boolean> textContainers = Maps.newLinkedHashMap();
private HtmlStreamEventProcessor postprocessor =
HtmlStreamEventProcessor.Processors.IDENTITY;
Expand Down Expand Up @@ -307,29 +311,29 @@ public HtmlPolicyBuilder disallowTextIn(String... elementNames) {
* Assuming the given elements are allowed, allows them to appear without
* attributes.
*
* @see #DEFAULT_SKIP_IF_EMPTY
* @see #DEFAULT_SKIP_TAG_MAP_IF_EMPTY_ATTR
* @see #disallowWithoutAttributes
*/
public HtmlPolicyBuilder allowWithoutAttributes(String... elementNames) {
invalidateCompiledState();
for (String elementName : elementNames) {
elementName = HtmlLexer.canonicalName(elementName);
skipIfEmpty.remove(elementName);
skipIssueTagMap.put(elementName, HtmlTagSkipType.DO_NOT_SKIP);
}
return this;
}

/**
* Disallows the given elements from appearing without attributes.
*
* @see #DEFAULT_SKIP_IF_EMPTY
* @see #DEFAULT_SKIP_TAG_MAP_IF_EMPTY_ATTR
* @see #allowWithoutAttributes
*/
public HtmlPolicyBuilder disallowWithoutAttributes(String... elementNames) {
invalidateCompiledState();
for (String elementName : elementNames) {
elementName = HtmlLexer.canonicalName(elementName);
skipIfEmpty.add(elementName);
skipIssueTagMap.put(elementName, HtmlTagSkipType.SKIP);
}
return this;
}
Expand Down Expand Up @@ -835,13 +839,29 @@ private CompiledState compilePolicies() {
elementName,
new ElementAndAttributePolicies(
elementName,
elPolicy, attrs.build(), skipIfEmpty.contains(elementName)));
elPolicy, attrs.build(),
getHtmlTagSkipType(elementName)
)
);
}
compiledState = new CompiledState(
globalAttrPolicies, policiesBuilder.build());
return compiledState;
}

private HtmlTagSkipType getHtmlTagSkipType(String elementName) {
HtmlTagSkipType htmlTagSkipType = skipIssueTagMap.get(elementName);
if (htmlTagSkipType == null) {
if (DEFAULT_SKIP_TAG_MAP_IF_EMPTY_ATTR.containsKey(elementName)) {
return HtmlTagSkipType.SKIP_BY_DEFAULT;
} else {
return HtmlTagSkipType.NONE;
}
}

return htmlTagSkipType;
}

/**
* Builds the relationship between attributes, the values that they may have,
* and the elements on which they may appear.
Expand Down
34 changes: 34 additions & 0 deletions src/main/java/org/owasp/html/HtmlTagSkipType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.owasp.html;

public enum HtmlTagSkipType {
SKIP_BY_DEFAULT(true),
SKIP(true),
DO_NOT_SKIP(false),
NONE(false);

private final boolean skipAvailability;

HtmlTagSkipType(boolean skipAvailability) {
this.skipAvailability = skipAvailability;
}

public HtmlTagSkipType and(HtmlTagSkipType s) {
if (this == s || s == SKIP_BY_DEFAULT) {
return this;
}

if (s == DO_NOT_SKIP) {
return s;
}

if (s == NONE) {
return this;
}

return SKIP;
}

public boolean skipAvailability() {
return this.skipAvailability;
}
}
Loading