Skip to content

Commit f2d261c

Browse files
committed
reworked code that munges maps when compiling element and attribute
policies to have fewer special cases and allow styling policies to compose.
1 parent badcd4e commit f2d261c

File tree

3 files changed

+79
-19
lines changed

3 files changed

+79
-19
lines changed

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

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -638,22 +638,36 @@ public PolicyFactory toFactory() {
638638
textContainerSet.add(textContainer.getKey());
639639
}
640640
}
641-
return new PolicyFactory(compilePolicies(), textContainerSet.build(),
642-
ImmutableMap.copyOf(globalAttrPolicies),
643-
preprocessor, postprocessor);
641+
CompiledState compiled = compilePolicies();
642+
643+
return new PolicyFactory(
644+
compiled.compiledPolicies, textContainerSet.build(),
645+
ImmutableMap.copyOf(compiled.globalAttrPolicies),
646+
preprocessor, postprocessor);
644647
}
645648

646649
// Speed up subsequent builds by caching the compiled policies.
647-
private transient ImmutableMap<String, ElementAndAttributePolicies>
648-
compiledPolicies;
650+
private transient CompiledState compiledState;
651+
652+
private static final class CompiledState {
653+
final Map<String, AttributePolicy> globalAttrPolicies;
654+
final ImmutableMap<String, ElementAndAttributePolicies> compiledPolicies;
655+
656+
CompiledState(
657+
Map<String, AttributePolicy> globalAttrPolicies,
658+
ImmutableMap<String, ElementAndAttributePolicies> compiledPolicies) {
659+
this.globalAttrPolicies = globalAttrPolicies;
660+
this.compiledPolicies = compiledPolicies;
661+
}
662+
}
649663

650664
/** Called by mutators to signal that any compiled policy is out-of-date. */
651665
private void invalidateCompiledState() {
652-
compiledPolicies = null;
666+
compiledState = null;
653667
}
654668

655-
private ImmutableMap<String, ElementAndAttributePolicies> compilePolicies() {
656-
if (compiledPolicies != null) { return compiledPolicies; }
669+
private CompiledState compilePolicies() {
670+
if (compiledState != null) { return compiledState; }
657671

658672
// Copy maps before normalizing in case builder is reused.
659673
@SuppressWarnings("hiding")
@@ -741,6 +755,13 @@ public String apply(String url) {
741755
}
742756
}
743757
}
758+
if (stylingPolicy != null) {
759+
AttributePolicy old = globalAttrPolicies.get("style");
760+
if (old != null) {
761+
globalAttrPolicies.put(
762+
"style", AttributePolicy.Util.join(old, stylingPolicy));
763+
}
764+
}
744765

745766
ImmutableMap.Builder<String, ElementAndAttributePolicies> policiesBuilder
746767
= ImmutableMap.builder();
@@ -753,18 +774,29 @@ public String apply(String url) {
753774

754775
Map<String, AttributePolicy> elAttrPolicies
755776
= attrPolicies.get(elementName);
756-
if (elAttrPolicies == null) { elAttrPolicies = ImmutableMap.of(); }
777+
if (elAttrPolicies == null) {
778+
elAttrPolicies = ImmutableMap.of();
779+
}
780+
if (stylingPolicy != null) {
781+
AttributePolicy old = elAttrPolicies.get("style");
782+
if (old != null) {
783+
attrPolicies.put(
784+
elementName,
785+
elAttrPolicies = Maps.newLinkedHashMap(elAttrPolicies));
786+
elAttrPolicies.put(
787+
"style", AttributePolicy.Util.join(old, stylingPolicy));
788+
}
789+
}
790+
757791
ImmutableMap.Builder<String, AttributePolicy> attrs
758792
= ImmutableMap.builder();
793+
759794
for (Map.Entry<String, AttributePolicy> ape : elAttrPolicies.entrySet()) {
760795
String attributeName = ape.getKey();
761796
// Handle below so we don't end up putting the same key into the map
762797
// twice. ImmutableMap.Builder hates that.
763798
if (globalAttrPolicies.containsKey(attributeName)) { continue; }
764799
AttributePolicy policy = ape.getValue();
765-
if ("style".equals(attributeName)) {
766-
policy = AttributePolicy.Util.join(policy, stylingPolicy);
767-
}
768800
if (!AttributePolicy.REJECT_ALL_ATTRIBUTE_POLICY.equals(policy)) {
769801
attrs.put(attributeName, policy);
770802
}
@@ -774,9 +806,6 @@ public String apply(String url) {
774806
String attributeName = ape.getKey();
775807
AttributePolicy policy = AttributePolicy.Util.join(
776808
elAttrPolicies.get(attributeName), ape.getValue());
777-
if ("style".equals(attributeName)) {
778-
policy = AttributePolicy.Util.join(policy, stylingPolicy);
779-
}
780809
if (!AttributePolicy.REJECT_ALL_ATTRIBUTE_POLICY.equals(policy)) {
781810
attrs.put(attributeName, policy);
782811
}
@@ -788,7 +817,7 @@ public String apply(String url) {
788817
elementName,
789818
elPolicy, attrs.build(), skipIfEmpty.contains(elementName)));
790819
}
791-
return compiledPolicies = policiesBuilder.build();
820+
return new CompiledState(globalAttrPolicies, policiesBuilder.build());
792821
}
793822

794823
/**

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

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@
3232

3333
import javax.annotation.Nullable;
3434

35+
import org.owasp.html.JoinedAttributePolicy.JoinableAttributePolicy;
36+
3537
import com.google.common.annotations.VisibleForTesting;
3638
import com.google.common.base.Function;
39+
import com.google.common.base.Functions;
3740
import com.google.common.collect.Lists;
3841

3942
/**
@@ -42,7 +45,7 @@
4245
* ones to reduce the attack-surface.
4346
*/
4447
@TCB
45-
final class StylingPolicy implements AttributePolicy {
48+
final class StylingPolicy implements JoinableAttributePolicy {
4649

4750
final CssSchema cssSchema;
4851
final Function<String, String> urlRewriter;
@@ -258,4 +261,32 @@ public boolean equals(Object o) {
258261
public int hashCode() {
259262
return cssSchema.hashCode();
260263
}
264+
265+
public Joinable.JoinStrategy<JoinableAttributePolicy> getJoinStrategy() {
266+
return StylingPolicyJoinStrategy.INSTANCE;
267+
}
268+
269+
static final class StylingPolicyJoinStrategy
270+
implements Joinable.JoinStrategy<JoinableAttributePolicy> {
271+
static final StylingPolicyJoinStrategy INSTANCE =
272+
new StylingPolicyJoinStrategy();
273+
274+
public JoinableAttributePolicy join(
275+
Iterable<? extends JoinableAttributePolicy> toJoin) {
276+
Function<String, String> identity = Functions.<String>identity();
277+
CssSchema cssSchema = null;
278+
Function<String, String> urlRewriter = identity;
279+
for (JoinableAttributePolicy p : toJoin) {
280+
StylingPolicy sp = (StylingPolicy) p;
281+
cssSchema = cssSchema == null
282+
? sp.cssSchema : CssSchema.union(cssSchema, sp.cssSchema);
283+
urlRewriter = urlRewriter.equals(identity)
284+
|| urlRewriter.equals(sp.urlRewriter)
285+
? sp.urlRewriter
286+
: Functions.compose(urlRewriter, sp.urlRewriter);
287+
}
288+
return new StylingPolicy(cssSchema, urlRewriter);
289+
}
290+
291+
}
261292
}

src/test/java/org/owasp/html/SanitizersTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,12 +319,12 @@ public static final void testScriptInTable() {
319319
@Test
320320
public static final void testAndOrdering() {
321321
String input = ""
322-
+ "xss<a href=\"http://www.google.de\" style=\"color:red;\""
322+
+ "xss<a href=\"http://www.google.de\" style=\"color:red\""
323323
+ " onmouseover=alert(1) onmousemove=\"alert(2)\" onclick=alert(3)>"
324324
+ "g"
325325
+ "<img src=\"http://example.org\"/>oogle</a>";
326326
String want = ""
327-
+ "xss<a href=\"http://www.google.de\" style=\"color:red;\""
327+
+ "xss<a href=\"http://www.google.de\" style=\"color:red\""
328328
+ " rel=\"nofollow\">"
329329
+ "g"
330330
+ "<img src=\"http://example.org\" />oogle</a>";

0 commit comments

Comments
 (0)