Skip to content

Commit 15bdb96

Browse files
author
yangbongsoo
authored
Union policy skipIfEmpty value issue (#199)
.and properly merges skipIfEmpty giving preference to explicit policy choices over defaults. Fixes #197
1 parent 3c1c4e3 commit 15bdb96

File tree

5 files changed

+473
-26
lines changed

5 files changed

+473
-26
lines changed

src/main/java/org/owasp/html/ElementAndAttributePolicies.java

+5-18
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,18 @@ final class ElementAndAttributePolicies {
4343
final String elementName;
4444
final ElementPolicy elPolicy;
4545
final ImmutableMap<String, AttributePolicy> attrPolicies;
46-
final boolean skipIfEmpty;
46+
final HtmlTagSkipType htmlTagSkipType;
4747

4848
ElementAndAttributePolicies(
4949
String elementName,
5050
ElementPolicy elPolicy,
5151
Map<? extends String, ? extends AttributePolicy>
5252
attrPolicies,
53-
boolean skipIfEmpty) {
53+
HtmlTagSkipType htmlTagSkipType) {
5454
this.elementName = elementName;
5555
this.elPolicy = elPolicy;
5656
this.attrPolicies = ImmutableMap.copyOf(attrPolicies);
57-
this.skipIfEmpty = skipIfEmpty;
57+
this.htmlTagSkipType = htmlTagSkipType;
5858
}
5959

6060
ElementAndAttributePolicies and(ElementAndAttributePolicies p) {
@@ -78,24 +78,11 @@ ElementAndAttributePolicies and(ElementAndAttributePolicies p) {
7878
}
7979
}
8080

81-
// HACK: this is attempting to recognize when skipIfEmpty has been
82-
// explicitly set in HtmlPolicyBuilder and can only make a best effort at
83-
// that and is also too tightly coupled with HtmlPolicyBuilder.
84-
// Maybe go tri-state.
85-
boolean combinedSkipIfEmpty;
86-
if (HtmlPolicyBuilder.DEFAULT_SKIP_IF_EMPTY.contains(elementName)) {
87-
// Either policy explicitly opted out of skip if empty.
88-
combinedSkipIfEmpty = skipIfEmpty && p.skipIfEmpty;
89-
} else {
90-
// Either policy explicitly specified skip if empty.
91-
combinedSkipIfEmpty = skipIfEmpty || p.skipIfEmpty;
92-
}
93-
9481
return new ElementAndAttributePolicies(
9582
elementName,
9683
ElementPolicy.Util.join(elPolicy, p.elPolicy),
9784
joinedAttrPolicies.build(),
98-
combinedSkipIfEmpty);
85+
this.htmlTagSkipType.and(p.htmlTagSkipType));
9986
}
10087

10188
ElementAndAttributePolicies andGlobals(
@@ -130,7 +117,7 @@ ElementAndAttributePolicies andGlobals(
130117
}
131118
if (anded == null) { return this; }
132119
return new ElementAndAttributePolicies(
133-
elementName, elPolicy, anded, skipIfEmpty);
120+
elementName, elPolicy, anded, htmlTagSkipType);
134121
}
135122

136123
}

src/main/java/org/owasp/html/ElementAndAttributePolicyBasedSanitizerPolicy.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public void openTag(String elementName, List<String> attrs) {
104104
ElementAndAttributePolicies policies = elAndAttrPolicies.get(elementName);
105105
String adjustedElementName = applyPolicies(elementName, attrs, policies);
106106
if (adjustedElementName != null
107-
&& !(attrs.isEmpty() && policies.skipIfEmpty)) {
107+
&& !(attrs.isEmpty() && policies.htmlTagSkipType.skipAvailability())) {
108108
writeOpenTag(policies, adjustedElementName, attrs);
109109
return;
110110
}

src/main/java/org/owasp/html/HtmlPolicyBuilder.java

+31-7
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,15 @@ public class HtmlPolicyBuilder {
168168
public static final ImmutableSet<String> DEFAULT_SKIP_IF_EMPTY
169169
= ImmutableSet.of("a", "font", "img", "input", "span");
170170

171+
static final ImmutableMap<String, HtmlTagSkipType> DEFAULT_SKIP_TAG_MAP_IF_EMPTY_ATTR;
172+
static {
173+
ImmutableMap.Builder<String, HtmlTagSkipType> b = ImmutableMap.builder();
174+
for (String elementName : DEFAULT_SKIP_IF_EMPTY) {
175+
b.put(elementName, HtmlTagSkipType.SKIP_BY_DEFAULT);
176+
}
177+
DEFAULT_SKIP_TAG_MAP_IF_EMPTY_ATTR = b.build();
178+
}
179+
171180
/**
172181
* These
173182
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types"
@@ -190,8 +199,7 @@ public class HtmlPolicyBuilder {
190199
private final Map<String, AttributePolicy> globalAttrPolicies
191200
= Maps.newLinkedHashMap();
192201
private final Set<String> allowedProtocols = Sets.newLinkedHashSet();
193-
private final Set<String> skipIfEmpty = Sets.newLinkedHashSet(
194-
DEFAULT_SKIP_IF_EMPTY);
202+
private final Map<String, HtmlTagSkipType> skipIssueTagMap = Maps.newLinkedHashMap(DEFAULT_SKIP_TAG_MAP_IF_EMPTY_ATTR);
195203
private final Map<String, Boolean> textContainers = Maps.newLinkedHashMap();
196204
private HtmlStreamEventProcessor postprocessor =
197205
HtmlStreamEventProcessor.Processors.IDENTITY;
@@ -307,29 +315,29 @@ public HtmlPolicyBuilder disallowTextIn(String... elementNames) {
307315
* Assuming the given elements are allowed, allows them to appear without
308316
* attributes.
309317
*
310-
* @see #DEFAULT_SKIP_IF_EMPTY
318+
* @see #DEFAULT_SKIP_TAG_MAP_IF_EMPTY_ATTR
311319
* @see #disallowWithoutAttributes
312320
*/
313321
public HtmlPolicyBuilder allowWithoutAttributes(String... elementNames) {
314322
invalidateCompiledState();
315323
for (String elementName : elementNames) {
316324
elementName = HtmlLexer.canonicalName(elementName);
317-
skipIfEmpty.remove(elementName);
325+
skipIssueTagMap.put(elementName, HtmlTagSkipType.DO_NOT_SKIP);
318326
}
319327
return this;
320328
}
321329

322330
/**
323331
* Disallows the given elements from appearing without attributes.
324332
*
325-
* @see #DEFAULT_SKIP_IF_EMPTY
333+
* @see #DEFAULT_SKIP_TAG_MAP_IF_EMPTY_ATTR
326334
* @see #allowWithoutAttributes
327335
*/
328336
public HtmlPolicyBuilder disallowWithoutAttributes(String... elementNames) {
329337
invalidateCompiledState();
330338
for (String elementName : elementNames) {
331339
elementName = HtmlLexer.canonicalName(elementName);
332-
skipIfEmpty.add(elementName);
340+
skipIssueTagMap.put(elementName, HtmlTagSkipType.SKIP);
333341
}
334342
return this;
335343
}
@@ -835,13 +843,29 @@ private CompiledState compilePolicies() {
835843
elementName,
836844
new ElementAndAttributePolicies(
837845
elementName,
838-
elPolicy, attrs.build(), skipIfEmpty.contains(elementName)));
846+
elPolicy, attrs.build(),
847+
getHtmlTagSkipType(elementName)
848+
)
849+
);
839850
}
840851
compiledState = new CompiledState(
841852
globalAttrPolicies, policiesBuilder.build());
842853
return compiledState;
843854
}
844855

856+
private HtmlTagSkipType getHtmlTagSkipType(String elementName) {
857+
HtmlTagSkipType htmlTagSkipType = skipIssueTagMap.get(elementName);
858+
if (htmlTagSkipType == null) {
859+
if (DEFAULT_SKIP_TAG_MAP_IF_EMPTY_ATTR.containsKey(elementName)) {
860+
return HtmlTagSkipType.SKIP_BY_DEFAULT;
861+
} else {
862+
return HtmlTagSkipType.DO_NOT_SKIP_BY_DEFAULT;
863+
}
864+
}
865+
866+
return htmlTagSkipType;
867+
}
868+
845869
/**
846870
* Builds the relationship between attributes, the values that they may have,
847871
* and the elements on which they may appear.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.owasp.html;
2+
3+
public enum HtmlTagSkipType {
4+
SKIP(true),
5+
SKIP_BY_DEFAULT(true),
6+
DO_NOT_SKIP(false),
7+
DO_NOT_SKIP_BY_DEFAULT(false);
8+
9+
private final boolean skipAvailability;
10+
11+
HtmlTagSkipType(boolean skipAvailability) {
12+
this.skipAvailability = skipAvailability;
13+
}
14+
15+
public HtmlTagSkipType and(HtmlTagSkipType s) {
16+
if (this == s || s == SKIP_BY_DEFAULT) {
17+
return this;
18+
}
19+
20+
if (s == DO_NOT_SKIP) {
21+
return s;
22+
}
23+
24+
if (s == DO_NOT_SKIP_BY_DEFAULT) {
25+
return this;
26+
}
27+
28+
return SKIP;
29+
}
30+
31+
public boolean skipAvailability() {
32+
return this.skipAvailability;
33+
}
34+
}

0 commit comments

Comments
 (0)