Skip to content

Commit 1e6b035

Browse files
committed
Allow declaring CssSchemas with custom properties.
Discussion at https://groups.google.com/forum/#!topic/owasp-java-html-sanitizer-support/UkOKjqzxhk0 * adds a `CssSchema.withProperties(Map<String, Propety>)` method to allow creating a CssSchema with custom propety declaration * makes `CssSchema.Property` and its constructor public so client code can create them * changes `CssSchema.union` to fail hard if there's an ambiguity in property declarations Now a client should be able to add custom declarations via ```java CssSchema.union(CssSchema.WHITELIST, CssSchema.withProperties(myPropertyMap)) ```
1 parent d0fd10d commit 1e6b035

File tree

1 file changed

+92
-3
lines changed

1 file changed

+92
-3
lines changed

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

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import javax.annotation.Nullable;
3636

37+
import com.google.common.base.Preconditions;
3738
import com.google.common.collect.ImmutableMap;
3839
import com.google.common.collect.ImmutableSet;
3940
import com.google.common.collect.Maps;
@@ -43,7 +44,13 @@
4344
@TCB
4445
public final class CssSchema {
4546

46-
static final class Property {
47+
/**
48+
* Describes how CSS interprets tokens after the ":" for a property.
49+
* For example, if the property name "color" maps to this, then it
50+
* should record that '#' literals are innocuous colors, and that functions
51+
* like "rgb", "rgba", "hsl", etc. are allowed functions.
52+
*/
53+
public static final class Property {
4754
/** A bitfield of BIT_* constants describing groups of allowed tokens. */
4855
final int bits;
4956
/** Specific allowed values. */
@@ -53,13 +60,60 @@ static final class Property {
5360
*/
5461
final ImmutableMap<String, String> fnKeys;
5562

56-
Property(
63+
/**
64+
* @param bits A bitfield of BIT_* constants describing groups of allowed tokens.
65+
* @param literals Specific allowed values.
66+
* @param fnKeys Maps lower-case function tokens to the schema key for their parameters.
67+
*/
68+
public Property(
5769
int bits, ImmutableSet<String> literals,
5870
ImmutableMap<String, String> fnKeys) {
5971
this.bits = bits;
6072
this.literals = literals;
6173
this.fnKeys = fnKeys;
6274
}
75+
76+
@Override
77+
public int hashCode() {
78+
final int prime = 31;
79+
int result = 1;
80+
result = prime * result + bits;
81+
result = prime * result + ((fnKeys == null) ? 0 : fnKeys.hashCode());
82+
result = prime * result + ((literals == null) ? 0 : literals.hashCode());
83+
return result;
84+
}
85+
86+
@Override
87+
public boolean equals(Object obj) {
88+
if (this == obj) {
89+
return true;
90+
}
91+
if (obj == null) {
92+
return false;
93+
}
94+
if (getClass() != obj.getClass()) {
95+
return false;
96+
}
97+
Property other = (Property) obj;
98+
if (bits != other.bits) {
99+
return false;
100+
}
101+
if (fnKeys == null) {
102+
if (other.fnKeys != null) {
103+
return false;
104+
}
105+
} else if (!fnKeys.equals(other.fnKeys)) {
106+
return false;
107+
}
108+
if (literals == null) {
109+
if (other.literals != null) {
110+
return false;
111+
}
112+
} else if (!literals.equals(other.literals)) {
113+
return false;
114+
}
115+
return true;
116+
}
63117
}
64118

65119
static final int BIT_QUANTITY = 1;
@@ -100,17 +154,52 @@ public static CssSchema withProperties(
100154
return new CssSchema(propertiesBuilder.build());
101155
}
102156

157+
/**
158+
* A schema that includes all and only the named properties.
159+
*
160+
* @param properties maps lower-case CSS property names to property objects.
161+
*/
162+
public static CssSchema withProperties(
163+
Map<? extends String, ? extends Property> properties) {
164+
ImmutableMap<String, Property> propertyMap =
165+
ImmutableMap.copyOf(properties);
166+
// check that all fnKeys are defined in properties.
167+
for (Map.Entry<String, Property> e : propertyMap.entrySet()) {
168+
Property property = e.getValue();
169+
for (String fnKey : property.fnKeys.values()) {
170+
if (!propertyMap.containsKey(fnKey)) {
171+
throw new IllegalArgumentException(
172+
"Property map is not self contained. " + e.getValue()
173+
+ " depends on undefined function key " + fnKey);
174+
}
175+
}
176+
}
177+
return new CssSchema(propertyMap);
178+
}
179+
103180
/**
104181
* A schema that represents the union of the input schemas.
105182
*
106183
* @return A schema that allows all and only CSS properties that are allowed
107184
* by at least one of the inputs.
185+
* @throws IllegalArgumentException if two schemas have properties with the
186+
* same name, but different (per .equals) {@link Property} values.
108187
*/
109188
public static CssSchema union(CssSchema... cssSchemas) {
110189
if (cssSchemas.length == 1) { return cssSchemas[0]; }
111190
Map<String, Property> properties = Maps.newLinkedHashMap();
112191
for (CssSchema cssSchema : cssSchemas) {
113-
properties.putAll(cssSchema.properties);
192+
for (Map.Entry<String, Property> e : cssSchema.properties.entrySet()) {
193+
String name = e.getKey();
194+
Property newProp = e.getValue();
195+
Preconditions.checkNotNull(name);
196+
Preconditions.checkNotNull(newProp);
197+
Property oldProp = properties.put(name, newProp);
198+
if (oldProp != null && !oldProp.equals(newProp)) {
199+
throw new IllegalArgumentException(
200+
"Duplicate irreconcilable definitions for " + name);
201+
}
202+
}
114203
}
115204
return new CssSchema(ImmutableMap.copyOf(properties));
116205
}

0 commit comments

Comments
 (0)