diff options
author | mikesamuel <mikesamuel@ad8eed46-c659-4a31-e19d-951d88f54425> | 2014-05-14 16:33:05 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2014-05-14 16:33:05 +0000 |
commit | ffa4f68d3386549eec92db82bec0296c1b8871a2 (patch) | |
tree | e156af6a89c1da762589079339f4912e3c99c03f | |
parent | a22c8f66e2b141c18783fc675ad9b3231172eec5 (diff) | |
parent | b268f8745b09a77af2e8c77ffd376b6459bf4fec (diff) | |
download | sanitizer-ffa4f68d3386549eec92db82bec0296c1b8871a2.tar.gz |
am b268f874: rewrite the CSS sanitizer to do token-level filtering
* commit 'b268f8745b09a77af2e8c77ffd376b6459bf4fec':
rewrite the CSS sanitizer to do token-level filtering
-rw-r--r-- | src/main/org/owasp/html/StylingPolicy.java | 1038 | ||||
-rw-r--r-- | src/tests/org/owasp/html/AntiSamyTest.java | 4 | ||||
-rw-r--r-- | src/tests/org/owasp/html/HtmlPolicyBuilderTest.java | 2 | ||||
-rw-r--r-- | src/tests/org/owasp/html/SanitizersTest.java | 10 | ||||
-rw-r--r-- | src/tests/org/owasp/html/StylingPolicyTest.java | 138 |
5 files changed, 145 insertions, 1047 deletions
diff --git a/src/main/org/owasp/html/StylingPolicy.java b/src/main/org/owasp/html/StylingPolicy.java index 14c43ae..52a4389 100644 --- a/src/main/org/owasp/html/StylingPolicy.java +++ b/src/main/org/owasp/html/StylingPolicy.java @@ -29,14 +29,10 @@ package org.owasp.html; import java.util.List; -import java.util.Set; -import java.util.regex.Pattern; import javax.annotation.Nullable; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; /** @@ -52,248 +48,6 @@ class StylingPolicy implements AttributePolicy { return value != null ? sanitizeCssProperties(value) : null; } - /** Used to track CSS property names while processing CSS. */ - private enum CssPropertyType { - FONT, - FACE, - SIZE, - BACKGROUND_COLOR, - COLOR, - DIRECTION, - UNICODE_BIDI, - ALIGN, - WEIGHT, - STYLE, - TEXT_DECORATION, - HEIGHT, - WIDTH, - MARGIN, - MARGIN_BOTTOM, - MARGIN_LEFT, - MARGIN_RIGHT, - MARGIN_TOP, - PADDING, - PADDING_BOTTOM, - PADDING_LEFT, - PADDING_RIGHT, - PADDING_TOP, - DIMS, - NONE, - ; - } - - private static final Pattern ALLOWED_CSS_SIZE = Pattern.compile( - "medium|smaller|larger|(?:xx?-)(?:small|large)|[0-9]+(p[tx]|%)"); - - private static final Pattern ALLOWED_CSS_WEIGHT = Pattern.compile( - "normal|bold(?:er)?|lighter|[1-9]00"); - - private static final Set<String> ALLOWED_FONT_STYLE = ImmutableSet.of( - "italic", "oblique", "normal"); - - private static final Set<String> ALLOWED_TEXT_ALIGN = ImmutableSet.of( - "start", "end", "left", "right", "center", "justify"); - - private static final Set<String> ALLOWED_TEXT_DECORATION = ImmutableSet.of( - "underline", "overline", "line-through"); - - private static final Set<String> ALLOWED_UNICODE_BIDI = ImmutableSet.of( - "inherit", "normal", "embed", "bidi-override"); - - private static final Set<String> ALLOWED_DIRECTION = ImmutableSet.of( - "inherit", "ltr", "rtl"); - - private static final Pattern NON_NEGATIVE_LENGTH = Pattern.compile( - "(?:0|[1-9][0-9]*)([.][0-9]+)?(ex|[ecm]m|v[hw]|p[xct]|in|%)?"); - - private static final ImmutableMap<String, CssPropertyType> - BY_CSS_PROPERTY_NAME = ImmutableMap.<String, CssPropertyType>builder() - .put("font", CssPropertyType.FONT) - .put("font-family", CssPropertyType.FACE) - .put("font-size", CssPropertyType.SIZE) - .put("color", CssPropertyType.COLOR) - .put("text-align", CssPropertyType.ALIGN) - .put("direction", CssPropertyType.DIRECTION) - .put("font-weight", CssPropertyType.WEIGHT) - .put("font-style", CssPropertyType.STYLE) - .put("text-decoration", CssPropertyType.TEXT_DECORATION) - .put("unicode-bidi", CssPropertyType.UNICODE_BIDI) - .put("background", CssPropertyType.BACKGROUND_COLOR) - .put("background-color", CssPropertyType.BACKGROUND_COLOR) - .put("height", CssPropertyType.HEIGHT) - .put("width", CssPropertyType.WIDTH) - .put("margin", CssPropertyType.MARGIN) - .put("margin-top", CssPropertyType.MARGIN_TOP) - .put("margin-left", CssPropertyType.MARGIN_LEFT) - .put("margin-bottom", CssPropertyType.MARGIN_BOTTOM) - .put("margin-right", CssPropertyType.MARGIN_RIGHT) - .put("padding", CssPropertyType.PADDING) - .put("padding-top", CssPropertyType.PADDING_TOP) - .put("padding-left", CssPropertyType.PADDING_LEFT) - .put("padding-bottom", CssPropertyType.PADDING_BOTTOM) - .put("padding-right", CssPropertyType.PADDING_RIGHT) - .build(); - - /** - * Serializes a CSS property group based on a series of name and value calls. - */ - private static final class PropertyGroup { - private final StringBuilder buf = new StringBuilder(); - private boolean inPropertyName = true; - - PropertyGroup name(String s) { - assert inPropertyName; - if (buf.length() != 0) { buf.append(';'); } - buf.append(s); - inPropertyName = false; - return this; - } - - PropertyGroup name(String s, String suffix) { - assert inPropertyName; - if (buf.length() != 0) { buf.append(';'); } - buf.append(s).append(suffix); - inPropertyName = false; - return this; - } - - PropertyGroup value(String s) { - assert !inPropertyName; - buf.append(':').append(s); - inPropertyName = true; - return this; - } - - PropertyGroup value(String s0, String s1) { - assert !inPropertyName; - buf.append(':').append(s0).append(' ').append(s1); - inPropertyName = true; - return this; - } - - PropertyGroup value(String s0, String s1, String s2) { - assert !inPropertyName; - buf.append(':').append(s0).append(' ').append(s1).append(' ').append(s2); - inPropertyName = true; - return this; - } - - PropertyGroup value(String s0, String s1, String s2, String s3) { - assert !inPropertyName; - buf.append(':').append(s0).append(' ').append(s1) - .append(' ').append(s2).append(' ').append(s3); - inPropertyName = true; - return this; - } - - boolean isEmpty() { return buf.length() == 0; } - - @Override - public String toString() { - return buf.toString(); - } - } - - /** - * A group of CSS Length properties that define the boundaries of a - * rectangular area. The rectangle may be defined in terms of a delta to - * an inner rectangle as in padding, margin, and border. - * <p> - * TODO: handle the keyword "auto" as a valid length. - */ - private static final class Box { - /** Safe CSS length quantities. */ - String bottom, left, right, top; - /** Aggregates positional parameters as in {@code padding: 4px 2cm}. */ - private List<String> positional = null; - - /** - * Another positional parameter whose meaning cannot be completely - * determined until we see how many follow it. - */ - void positional(String s) { - if (positional == null) { - positional = Lists.newArrayListWithCapacity(4); - } - positional.add(s); - } - - /** - * Called after all positional quantities have been seen to figure out - * how they relate to the edges of a rectangle. - */ - void contextualizePositionalQuantities() { - if (positional != null) { - String explicitTop = top, - explicitRight = right, - explicitLeft = left, - explicitBottom = bottom; - switch (positional.size()) { - case 0: - break; - case 1: - top = right = left = bottom = positional.get(0); - break; - case 2: - top = bottom = positional.get(0); - right = left = positional.get(1); - break; - case 3: - top = positional.get(0); - right = left = positional.get(1); - bottom = positional.get(2); - break; - default: - top = positional.get(0); - right = positional.get(1); - bottom = positional.get(2); - left = positional.get(3); - break; - } - positional = null; - if (explicitTop != null) { top = explicitTop; } - if (explicitRight != null) { top = explicitRight; } - if (explicitBottom != null) { top = explicitBottom; } - if (explicitLeft != null) { top = explicitLeft; } - } - } - - /** - * Given a CSS property name, generates a box definition with as few - * CSS properties as possible. - */ - void toPropertyGroup(String basePropertyName, PropertyGroup out) { - if (bottom != null && left != null && right != null && top != null) { - if (left.equals(right)) { - if (bottom.equals(top)) { - if (bottom.equals(left)) { - out.name(basePropertyName).value(bottom); - } else { - out.name(basePropertyName).value(bottom, left); - } - } else { - out.name(basePropertyName).value(top, left, bottom); - } - } else { - out.name(basePropertyName).value(top, right, bottom, left); - } - } else { - if (top != null) { - out.name(basePropertyName, "-top").value(top); - } - if (right != null) { - out.name(basePropertyName, "-right").value(right); - } - if (bottom!= null) { - out.name(basePropertyName, "-bottom").value(bottom); - } - if (left != null) { - out.name(basePropertyName, "-left").value(left); - } - } - } - } - /** * Lossy filtering of CSS properties that allows textual styling that affects * layout, but does not allow breaking out of a clipping region, absolute @@ -303,733 +57,149 @@ class StylingPolicy implements AttributePolicy { */ @VisibleForTesting static String sanitizeCssProperties(String style) { + final StringBuilder sanitizedCss = new StringBuilder(); + CssGrammar.parsePropertyGroup(style, new CssGrammar.PropertyHandler() { + CssSchema schema = CssSchema.DISALLOWED; + List<CssSchema> schemas = null; + int propertyStart = 0; + boolean hasTokens; + boolean inQuotedIdents; - // We walk over CSS tokens to extract salient bits. - class StyleExtractor implements CssGrammar.PropertyHandler { - CssPropertyType type = CssPropertyType.NONE; + private void emitToken(String token) { + closeQuotedIdents(); + if (hasTokens) { sanitizedCss.append(' '); } + sanitizedCss.append(token); + hasTokens = true; + } - // Depth of fns that we have started but not finished. - int fnDepth = 0; - StringBuilder fn; - // Values that are not-whitelisted are put into font attributes to render - // the innocuous. - List<String> faces; - StringBuilder color, backgroundColor; - String align; - // These values are white-listed so we know they can't affect anything - // other than font-face appearance, and layout. - String cssSize, cssWeight, cssFontStyle, cssTextDecoration; - // Bidi support styles. - String cssDir, cssUnicodeBidi; - // Bounding box styles. - String height, width; - Box paddings; - Box margins; + private void closeQuotedIdents() { + if (inQuotedIdents) { + sanitizedCss.append('\''); + inQuotedIdents = false; + } + } public void url(String token) { - // Ignore. - } + closeQuotedIdents(); + // TODO: sanitize the URL. + //if ((schema.bits & CssSchema.BIT_URL) != 0) { - public void startProperty(String propertyName) { - CssPropertyType type = BY_CSS_PROPERTY_NAME.get(propertyName); - this.type = type != null ? type : CssPropertyType.NONE; + //} } - public void quotedString(String token) { - if (fn != null) { - fn.append(token); - return; - } - switch (type) { - case FONT: case FACE: - if (faces == null) { faces = Lists.newArrayList(); } - faces.add(token); - break; - default: break; + public void startProperty(String propertyName) { + if (schemas != null) { schemas.clear(); } + schema = CssSchema.forKey(propertyName); + hasTokens = false; + propertyStart = sanitizedCss.length(); + if (sanitizedCss.length() != 0) { + sanitizedCss.append(';'); } + sanitizedCss.append(propertyName).append(':'); } - public void quantity(String token) { - if (fn != null) { - fn.append(token); - return; - } - switch (type) { - case FONT: - case SIZE: - token = Strings.toLowerCase(token); - if (ALLOWED_CSS_SIZE.matcher(token).matches()) { - cssSize = token; - } - break; - case FACE: - if (faces == null) { faces = Lists.newArrayList(); } - faces.add(token); - break; - case BACKGROUND_COLOR: - if (backgroundColor == null) { - backgroundColor = new StringBuilder(); - } else { - backgroundColor.append(' '); - } - backgroundColor.append(token); - break; - case COLOR: - if (color == null) { - color = new StringBuilder(); - } else { - color.append(' '); - } - color.append(token); - break; - case WEIGHT: - if (ALLOWED_CSS_WEIGHT.matcher(token).matches()) { - cssWeight = token; - } - break; - case WIDTH: - if (NON_NEGATIVE_LENGTH.matcher(token).matches()) { - width = token; - } - break; - case HEIGHT: - if (NON_NEGATIVE_LENGTH.matcher(token).matches()) { - height = token; - } - break; - case MARGIN: - if (NON_NEGATIVE_LENGTH.matcher(token).matches()) { - if (margins == null) { margins = new Box(); } - margins.positional(token); - } - break; - case MARGIN_LEFT: - if (NON_NEGATIVE_LENGTH.matcher(token).matches()) { - if (margins == null) { margins = new Box(); } - margins.left = token; - } - break; - case MARGIN_RIGHT: - if (NON_NEGATIVE_LENGTH.matcher(token).matches()) { - if (margins == null) { margins = new Box(); } - margins.right = token; - } - break; - case MARGIN_TOP: - if (NON_NEGATIVE_LENGTH.matcher(token).matches()) { - if (margins == null) { margins = new Box(); } - margins.top = token; - } - break; - case MARGIN_BOTTOM: - if (NON_NEGATIVE_LENGTH.matcher(token).matches()) { - if (margins == null) { margins = new Box(); } - margins.bottom = token; - } - break; - case PADDING: - if (NON_NEGATIVE_LENGTH.matcher(token).matches()) { - if (paddings == null) { paddings = new Box(); } - paddings.positional(token); - } - break; - case PADDING_LEFT: - if (NON_NEGATIVE_LENGTH.matcher(token).matches()) { - if (paddings == null) { paddings = new Box(); } - paddings.left = token; - } - break; - case PADDING_RIGHT: - if (NON_NEGATIVE_LENGTH.matcher(token).matches()) { - if (paddings == null) { paddings = new Box(); } - paddings.right = token; - } - break; - case PADDING_TOP: - if (NON_NEGATIVE_LENGTH.matcher(token).matches()) { - if (paddings == null) { paddings = new Box(); } - paddings.top = token; - } - break; - case PADDING_BOTTOM: - if (NON_NEGATIVE_LENGTH.matcher(token).matches()) { - if (paddings == null) { paddings = new Box(); } - paddings.bottom = token; - } - break; - default: break; + public void startFunction(String token) { + closeQuotedIdents(); + if (schemas == null) { schemas = Lists.newArrayList(); } + schemas.add(schema); + token = Strings.toLowerCase(token); + String key = schema.fnKeys.get(token); + schema = key != null ? CssSchema.forKey(key) : CssSchema.DISALLOWED; + if (schema != CssSchema.DISALLOWED) { + emitToken(token); } } - public void identifier(String token) { - if (fn != null) { - fn.append(token); - return; - } - switch (type) { - case SIZE: - token = Strings.toLowerCase(token); - if (ALLOWED_CSS_SIZE.matcher(token).matches()) { - cssSize = token; - } - break; - case WEIGHT: - token = Strings.toLowerCase(token); - if (ALLOWED_CSS_WEIGHT.matcher(token).matches()) { - cssWeight = token; - } - break; - case FACE: - if (faces == null) { faces = Lists.newArrayList(); } - faces.add(token); - break; - case FONT: - token = Strings.toLowerCase(token); - if (ALLOWED_CSS_WEIGHT.matcher(token).matches()) { - cssWeight = token; - } else if (ALLOWED_CSS_SIZE.matcher(token).matches()) { - cssSize = token; - } else if (ALLOWED_FONT_STYLE.contains(token)) { - cssFontStyle = token; - } else { - if (faces == null) { faces = Lists.newArrayList(); } - faces.add(token); - } - break; - case BACKGROUND_COLOR: - if (backgroundColor == null) { - backgroundColor = new StringBuilder(); - backgroundColor.append(token); - } - break; - case COLOR: - if (color == null) { - color = new StringBuilder(); - color.append(token); - } - break; - case STYLE: - token = Strings.toLowerCase(token); - if (ALLOWED_FONT_STYLE.contains(token)) { - cssFontStyle = token; - } - break; - case ALIGN: - token = Strings.toLowerCase(token); - if (ALLOWED_TEXT_ALIGN.contains(token)) { - align = token; - } - break; - case DIRECTION: - token = Strings.toLowerCase(token); - if (ALLOWED_DIRECTION.contains(token)) { - cssDir = token; - } - break; - case UNICODE_BIDI: - token = Strings.toLowerCase(token); - if (ALLOWED_UNICODE_BIDI.contains(token)) { - cssUnicodeBidi = token; - } - break; - case TEXT_DECORATION: - token = Strings.toLowerCase(token); - if (ALLOWED_TEXT_DECORATION.contains(token)) { - cssTextDecoration = token; - } - break; - default: break; + public void quotedString(String token) { + closeQuotedIdents(); + int meaning = + schema.bits & (CssSchema.BIT_UNRESERVED_WORD | CssSchema.BIT_URL); + if ((meaning & (meaning - 1)) == 0) { // meaning is unambiguous + if (meaning == CssSchema.BIT_UNRESERVED_WORD + && token.length() > 2 + && isAlphanumericOrSpace(token, 1, token.length() - 1)) { + emitToken(Strings.toLowerCase(token)); + } else if (meaning == CssSchema.BIT_URL) { + // url("url(" + token + ")"); // TODO: %-encode properly + } } } - public void hash(String token) { - if (fn != null) { - fn.append(token); - return; - } - switch (type) { - case BACKGROUND_COLOR: - if (backgroundColor == null) { - backgroundColor = new StringBuilder(); - backgroundColor.append(token); - } - break; - case COLOR: - if (color == null) { - color = new StringBuilder(); - color.append(token); - } - break; - default: - break; + public void quantity(String token) { + int test = token.startsWith("-") + ? CssSchema.BIT_NEGATIVE : CssSchema.BIT_QUANTITY; + if ((schema.bits & test) != 0 + // font-weight uses 100, 200, 300, etc. + || schema.literals.contains(token)) { + emitToken(token); } } public void punctuation(String token) { - if (fn != null) { - fn.append(token); - return; - } - switch (type) { - case FACE: case FONT: - // Commas separate font-families since HTML fonts fall-back to - // simpler forms based on the installed font-set. - if (",".equals(token) && faces != null) { faces.add(","); } - break; - default: break; + closeQuotedIdents(); + if (schema.literals.contains(token)) { + emitToken(token); } } - public void startFunction(String token) { - if (fn == null) { fn = new StringBuilder(); } - fn.append(token); - ++fnDepth; - } - - public void endFunction(String token) { - fn.append(')'); - if (--fnDepth == 0) { - StringBuilder fnContent = fn; - fn = null; - // Use rgb and rgba in color. - switch (type) { - case BACKGROUND_COLOR: - if (backgroundColor == null) { - backgroundColor = fnContent; - } - break; - case COLOR: - if (color == null) { - color = fnContent; - } - break; - default: break; - } - } - } - - public void endProperty() { - type = CssPropertyType.NONE; - } - - @TCB - String toCssProperties() { - PropertyGroup pg = new PropertyGroup(); - String face = sanitizeFontFamilies(faces); - if (face != null) { - pg.name("font-family").value(face); - } - if (align != null) { - pg.name("text-align").value(align); - } - if (cssWeight != null) { - pg.name("font-weight").value(cssWeight); - } - if (cssSize != null) { - pg.name("font-size").value(cssSize); - } - if (cssFontStyle != null) { - pg.name("font-style").value(cssFontStyle); - } - if (cssTextDecoration != null) { - pg.name("text-decoration").value(cssTextDecoration); - } - if (cssDir != null) { - pg.name("direction").value(cssDir); - } - if (cssUnicodeBidi != null) { - pg.name("unicode-bidi").value(cssUnicodeBidi); - } - if (backgroundColor != null) { - String safeColor = sanitizeColor(backgroundColor.toString()); - if (safeColor != null) { - pg.name("background-color").value(safeColor); - } - } - if (color != null) { - String safeColor = sanitizeColor(color.toString()); - if (safeColor != null) { - pg.name("color").value(safeColor); + private static final int IDENT_TO_STRING = + CssSchema.BIT_UNRESERVED_WORD | CssSchema.BIT_STRING; + public void identifier(String token) { + token = Strings.toLowerCase(token); + if (schema.literals.contains(token)) { + emitToken(token); + } else if ((schema.bits & IDENT_TO_STRING) == IDENT_TO_STRING) { + if (!inQuotedIdents) { + inQuotedIdents = true; + if (hasTokens) { sanitizedCss.append(' '); } + sanitizedCss.append('\''); + hasTokens = true; + } else { + sanitizedCss.append(' '); } + sanitizedCss.append(Strings.toLowerCase(token)); } - if (height != null) { - pg.name("height").value(height); - } - if (width != null) { - pg.name("width").value(width); - } - if (margins != null) { - margins.contextualizePositionalQuantities(); - margins.toPropertyGroup("margin", pg); - } - if (paddings != null) { - paddings.contextualizePositionalQuantities(); - paddings.toPropertyGroup("padding", pg); - } - return pg.isEmpty() ? null : pg.toString(); } - } - - StyleExtractor extractor = new StyleExtractor(); - CssGrammar.parsePropertyGroup(style, extractor); - return extractor.toCssProperties(); - } - - /** - * Converts the various CSS syntactic forms for colors to a hex value or null. - * If the input is not a valid CSS color expression, then this method either - * returns null or returns a valid CSS hash color but the particular hash - * color is not well specified (besides being deterministic). - */ - static String sanitizeColor(String s) { - if (s.length() == 0) { return null; } - s = Strings.toLowerCase(s); - String hex = COLOR_TABLE.get(s); - if (hex != null) { return hex; } - int n = s.length(); - if (s.charAt(0) == '#') { - if (n != 4 && n != 7) { return null; } - for (int i = 1; i < n; ++i) { - char ch = s.charAt(i); - if (!(('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f'))) { - return null; + public void hash(String token) { + closeQuotedIdents(); + if ((schema.bits & CssSchema.BIT_HASH_VALUE) != 0) { + emitToken(Strings.toLowerCase(token)); } } - return s; - } - // Handle rgb and rgba - if (!s.startsWith("rgb")) { return null; } - StringBuilder sb = new StringBuilder(7); - sb.append('#'); - if (translateDecimalOrPctByteToHex( - s, translateDecimalOrPctByteToHex( - s, translateDecimalOrPctByteToHex(s, 3, sb), sb), sb) == -1) { - return null; - } - // #aabbcc -> #abc - if (sb.charAt(1) == sb.charAt(2) && sb.charAt(3) == sb.charAt(4) - && sb.charAt(5) == sb.charAt(6)) { - sb.setCharAt(2, sb.charAt(3)); - sb.setCharAt(3, sb.charAt(5)); - sb.setLength(4); - } - return sb.toString(); - } - - private static boolean isDecimalDigit(char ch) { - return '0' <= ch && ch <= '9'; - } - /** - * Looks for a decimal number in the range 0-255 or a percentage into a - * hex pair written to out. Returns the index after the number. - */ - private static int translateDecimalOrPctByteToHex( - String s, int i, StringBuilder out) { - if (i == -1) { return -1; } - int n = s.length(); - for (; i < n; ++i) { - char ch = s.charAt(i); - // Look for the first digit. - if (isDecimalDigit(ch) || ch == '.') { - int value; - if (ch != '.') { - value = ch - '0'; - // Reduce the run of digits to a decimal number. - while (++i < n) { - ch = s.charAt(i); - if (isDecimalDigit(ch)) { - value = value * 10 + (ch - '0'); - } else { - break; - } - } + public void endProperty() { + if (!hasTokens) { + sanitizedCss.setLength(propertyStart); } else { - value = 0; - } - float fraction = 0; - if (s.charAt(i) == '.') { - int numerator = 0; - int denominator = 1; - // Consume any decimal portion. - // TODO: Maybe incorporate into value. - while (++i < n) { - ch = s.charAt(i); - if (!isDecimalDigit(ch)) { break; } - numerator = numerator * 10 + (ch - '0'); - denominator *= 10; - } - fraction = ((float) numerator) / denominator; + closeQuotedIdents(); } - // Convert the decimal number to a percentage if appropriate. - if (i < n && s.charAt(i) == '%') { - // TODO: is this the right rounding mode? - value = (int) Math.round((value + fraction) * 2.55); - ++i; - } else if (value < 0xff && fraction > 0.5) { - ++value; - } - if (0 <= value && value <= 0xff) { - out.append("0123456789abcdef".charAt(value >>> 4)) - .append("0123456789abcdef".charAt(value & 0xf)); - return i; - } - return -1; - } - } - return -1; - } - - /** Maps CSS3 color keywords to unambiguous hash values. */ - private static final ImmutableMap<String, String> COLOR_TABLE - = ImmutableMap.<String, String>builder() - .put("aliceblue", "#f0f8ff") - .put("antiquewhite", "#faebd7") - .put("aqua", "#0ff") - .put("aquamarine", "#7fffd4") - .put("azure", "#f0ffff") - .put("beige", "#f5f5dc") - .put("bisque", "#ffe4c4") - .put("black", "#000") - .put("blanchedalmond", "#ffebcd") - .put("blue", "#00f") - .put("blueviolet", "#8a2be2") - .put("brown", "#a52a2a") - .put("burlywood", "#deb887") - .put("cadetblue", "#5f9ea0") - .put("chartreuse", "#7fff00") - .put("chocolate", "#d2691e") - .put("coral", "#ff7f50") - .put("cornflowerblue", "#6495ed") - .put("cornsilk", "#fff8dc") - .put("crimson", "#dc143c") - .put("cyan", "#0ff") - .put("darkblue", "#00008b") - .put("darkcyan", "#008b8b") - .put("darkgoldenrod", "#b8860b") - .put("darkgray", "#a9a9a9") - .put("darkgreen", "#006400") - .put("darkgrey", "#a9a9a9") - .put("darkkhaki", "#bdb76b") - .put("darkmagenta", "#8b008b") - .put("darkolivegreen", "#556b2f") - .put("darkorange", "#ff8c00") - .put("darkorchid", "#9932cc") - .put("darkred", "#8b0000") - .put("darksalmon", "#e9967a") - .put("darkseagreen", "#8fbc8f") - .put("darkslateblue", "#483d8b") - .put("darkslategray", "#2f4f4f") - .put("darkslategrey", "#2f4f4f") - .put("darkturquoise", "#00ced1") - .put("darkviolet", "#9400d3") - .put("deeppink", "#ff1493") - .put("deepskyblue", "#00bfff") - .put("dimgray", "#696969") - .put("dimgrey", "#696969") - .put("dodgerblue", "#1e90ff") - .put("firebrick", "#b22222") - .put("floralwhite", "#fffaf0") - .put("forestgreen", "#228b22") - .put("fuchsia", "#f0f") - .put("gainsboro", "#dcdcdc") - .put("ghostwhite", "#f8f8ff") - .put("gold", "#ffd700") - .put("goldenrod", "#daa520") - .put("gray", "#808080") - .put("green", "#008000") - .put("greenyellow", "#adff2f") - .put("grey", "#808080") - .put("honeydew", "#f0fff0") - .put("hotpink", "#ff69b4") - .put("indianred", "#cd5c5c") - .put("indigo", "#4b0082") - .put("ivory", "#fffff0") - .put("khaki", "#f0e68c") - .put("lavender", "#e6e6fa") - .put("lavenderblush", "#fff0f5") - .put("lawngreen", "#7cfc00") - .put("lemonchiffon", "#fffacd") - .put("lightblue", "#add8e6") - .put("lightcoral", "#f08080") - .put("lightcyan", "#e0ffff") - .put("lightgoldenrodyellow", "#fafad2") - .put("lightgray", "#d3d3d3") - .put("lightgreen", "#90ee90") - .put("lightgrey", "#d3d3d3") - .put("lightpink", "#ffb6c1") - .put("lightsalmon", "#ffa07a") - .put("lightseagreen", "#20b2aa") - .put("lightskyblue", "#87cefa") - .put("lightslategray", "#789") - .put("lightslategrey", "#789") - .put("lightsteelblue", "#b0c4de") - .put("lightyellow", "#ffffe0") - .put("lime", "#0f0") - .put("limegreen", "#32cd32") - .put("linen", "#faf0e6") - .put("magenta", "#f0f") - .put("maroon", "#800000") - .put("mediumaquamarine", "#66cdaa") - .put("mediumblue", "#0000cd") - .put("mediumorchid", "#ba55d3") - .put("mediumpurple", "#9370db") - .put("mediumseagreen", "#3cb371") - .put("mediumslateblue", "#7b68ee") - .put("mediumspringgreen", "#00fa9a") - .put("mediumturquoise", "#48d1cc") - .put("mediumvioletred", "#c71585") - .put("midnightblue", "#191970") - .put("mintcream", "#f5fffa") - .put("mistyrose", "#ffe4e1") - .put("moccasin", "#ffe4b5") - .put("navajowhite", "#ffdead") - .put("navy", "#000080") - .put("oldlace", "#fdf5e6") - .put("olive", "#808000") - .put("olivedrab", "#6b8e23") - .put("orange", "#ffa500") - .put("orangered", "#ff4500") - .put("orchid", "#da70d6") - .put("palegoldenrod", "#eee8aa") - .put("palegreen", "#98fb98") - .put("paleturquoise", "#afeeee") - .put("palevioletred", "#db7093") - .put("papayawhip", "#ffefd5") - .put("peachpuff", "#ffdab9") - .put("peru", "#cd853f") - .put("pink", "#ffc0cb") - .put("plum", "#dda0dd") - .put("powderblue", "#b0e0e6") - .put("purple", "#800080") - .put("red", "#f00") - .put("rosybrown", "#bc8f8f") - .put("royalblue", "#4169e1") - .put("saddlebrown", "#8b4513") - .put("salmon", "#fa8072") - .put("sandybrown", "#f4a460") - .put("seagreen", "#2e8b57") - .put("seashell", "#fff5ee") - .put("sienna", "#a0522d") - .put("silver", "#c0c0c0") - .put("skyblue", "#87ceeb") - .put("slateblue", "#6a5acd") - .put("slategray", "#708090") - .put("slategrey", "#708090") - .put("snow", "#fffafa") - .put("springgreen", "#00ff7f") - .put("steelblue", "#4682b4") - .put("tan", "#d2b48c") - .put("teal", "#008080") - .put("thistle", "#d8bfd8") - .put("tomato", "#ff6347") - .put("turquoise", "#40e0d0") - .put("violet", "#ee82ee") - .put("wheat", "#f5deb3") - .put("white", "#fff") - .put("whitesmoke", "#f5f5f5") - .put("yellow", "#ff0") - .put("yellowgreen", "#9acd32") - .build(); - - static @Nullable String sanitizeFontFamilies( - @Nullable List<String> families) { - if (families == null) { return null; } - StringBuilder css = new StringBuilder(); - int nFamilies = families.size(); - for (int i = 0; i < nFamilies; ++i) { - String token = families.get(i); - if (",".equals(token)) { continue; } - int familyEnd = i + 1; - while (familyEnd < nFamilies && !",".equals(families.get(familyEnd))) { - ++familyEnd; - } - int cssFamilyStart = css.length(); - if (!sanitizeFontFamilyOnto(families.subList(i, familyEnd), css)) { - css.setLength(cssFamilyStart); - } - i = familyEnd; - } - return css.length() == 0 ? null : css.toString(); - } - - private static boolean sanitizeFontFamilyOnto( - List<String> tokens, StringBuilder out) { - int n = tokens.size(); - if (n == 0) { return false; } - if (out.length() != 0) { out.append(','); } - if (n == 1) { - String token = tokens.get(0); - if (token.length() != 0 - && (token.charAt(0) == '"' || token.charAt(0) == '\'')) { - token = CssGrammar.cssContent(token).trim(); - if (!isNonEmptyAsciiAlnumSpaceSeparated(token)) { return false; } - out.append('"').append(token).append('"'); - return true; } - token = Strings.toLowerCase(token); - if (GENERIC_FONT_FAMILIES.contains(token)) { - out.append(token); - return true; - } - } - // Quote space separated words so that they are not confused with user-agent - // extensions like expression(...) or -webkit-small-control. - out.append('"'); - for (int i = 0; i < n; ++i) { - String token = tokens.get(i); - if (!isNonEmptyAsciiAlnum(token)) { return false; } - if (i != 0) { out.append(' '); } - out.append(token); - } - out.append('"'); - return true; - } - - // Intentionally excludes -webkit-small-control an similar user-agent - // extensions since allowing skinning oF OS controls is a potential trusted - // path violation. - private static Set<String> GENERIC_FONT_FAMILIES = ImmutableSet.of( - "serif", "sans-serif", "cursive", "fantasy", "monospace"); - static boolean isNonEmptyAsciiAlnumSpaceSeparated(String s) { - int i = 0; - int n = s.length(); - while (i < n && s.charAt(i) == ' ') { ++i; } - while (n > i && s.charAt(n - 1) == ' ') { --n; } - if (i == n) { return false; } - while (i < n) { - int e = i + 1; - while (e < n && s.charAt(e) != ' ') { - ++e; - } - if (!isNonEmptyAsciiAlnum(s.substring(i, e))) { - return false; + public void endFunction(String token) { + if (schema != CssSchema.DISALLOWED) { emitToken(")"); } + schema = schemas.remove(schemas.size() - 1); } - i = e; - while (i < n && s.charAt(i) == ' ') { ++i; } - } - return true; + }); + return sanitizedCss.length() == 0 ? null : sanitizedCss.toString(); } - private static final boolean[] ASCII_ALNUM = new boolean['z' + 1]; - static { - for (int i = '0'; i <= '9'; ++i) { ASCII_ALNUM[i] = true; } - for (int i = 'A'; i <= 'Z'; ++i) { ASCII_ALNUM[i] = true; } - for (int i = 'a'; i <= 'z'; ++i) { ASCII_ALNUM[i] = true; } - } - - private static boolean isNonEmptyAsciiAlnum(String s) { - int n = s.length(); - for (int i = 0; i < n; ++i) { - char ch = s.charAt(i); - if (ch < ASCII_ALNUM.length && ASCII_ALNUM[ch]) { - continue; + private static boolean isAlphanumericOrSpace( + String token, int start, int end) { + for (int i = start; i < end; ++i) { + char ch = token.charAt(i); + if (ch <= 0x20) { + if (ch != '\t' && ch != ' ') { + return false; + } } else { - return false; + int chLower = ch | 32; + if (!(('0' <= chLower && chLower <= '9') + || ('a' <= chLower && chLower <= 'z'))) { + return false; + } } } - return n != 0; + return true; } } diff --git a/src/tests/org/owasp/html/AntiSamyTest.java b/src/tests/org/owasp/html/AntiSamyTest.java index 09c2645..1b6983f 100644 --- a/src/tests/org/owasp/html/AntiSamyTest.java +++ b/src/tests/org/owasp/html/AntiSamyTest.java @@ -49,7 +49,7 @@ import junit.framework.TestSuite; public class AntiSamyTest extends TestCase {
static final boolean RUN_KNOWN_FAILURES = false;
- static final boolean DISABLE_INTERNETS = false;
+ static final boolean DISABLE_INTERNETS = true;
private static HtmlSanitizer.Policy makePolicy(Appendable buffer) {
final HtmlStreamRenderer renderer = HtmlStreamRenderer.create(
@@ -445,7 +445,7 @@ public class AntiSamyTest extends TestCase { /* issue #28 */
assertSanitizedDoesContain(
"<div style=\"font-family: Geneva, Arial, courier new, sans-serif\">Test</div>",
- "font-family:"Geneva","Arial","courier new",sans-serif");
+ "font-family:'geneva' , 'arial' , 'courier new' , sans-serif");
/* issue #29 - missing quotes around properties with spaces */
if (RUN_KNOWN_FAILURES) {
diff --git a/src/tests/org/owasp/html/HtmlPolicyBuilderTest.java b/src/tests/org/owasp/html/HtmlPolicyBuilderTest.java index 3d9f995..9c2ce0e 100644 --- a/src/tests/org/owasp/html/HtmlPolicyBuilderTest.java +++ b/src/tests/org/owasp/html/HtmlPolicyBuilderTest.java @@ -204,7 +204,7 @@ public class HtmlPolicyBuilderTest extends TestCase { "<p><b>Fancy</b> with <i><b>soupy</b></i><b> tags</b>.", ("</p><p style=\"text-align:center;font-weight:bold\">" + "Stylish Para 1</p>"), - ("<p style=\"font-weight:bold;direction:rtl;color:#f00\">" + ("<p style=\"color:red;direction:rtl;font-weight:bold\">" + "Stylish Para 2</p>"), ""), apply(new HtmlPolicyBuilder() diff --git a/src/tests/org/owasp/html/SanitizersTest.java b/src/tests/org/owasp/html/SanitizersTest.java index 705cda4..50c0353 100644 --- a/src/tests/org/owasp/html/SanitizersTest.java +++ b/src/tests/org/owasp/html/SanitizersTest.java @@ -203,16 +203,18 @@ public class SanitizersTest extends TestCase { .allowAttributes("size").onElements("font", "img") .toFactory(); String sanitized = "" - + "<table style=\"font-family:"Arial","Geneva"," - + "sans-serif;color:#000\">" + + "<table style=\"color:rgb( 0 , 0 , 0 );" + + "font-family:'arial' , 'geneva' , sans-serif\">" + "<tbody>" + "<tr>" + "<th>Column One</th><th>Column Two</th>" + "</tr>" + "<tr>" - + "<td align=\"center\" style=\"background-color:#fffffe\">" + + "<td align=\"center\"" + + " style=\"background-color:rgb( 255 , 255 , 254 )\">" + "<font size=\"2\">Size 2</font></td>" - + "<td align=\"center\" style=\"background-color:#fffffe\">" + + "<td align=\"center\"" + + " style=\"background-color:rgb( 255 , 255 , 254 )\">" + "<font size=\"7\">Size 7</font></td>" + "</tr>" + "</tbody>" diff --git a/src/tests/org/owasp/html/StylingPolicyTest.java b/src/tests/org/owasp/html/StylingPolicyTest.java index fb23a6b..358d108 100644 --- a/src/tests/org/owasp/html/StylingPolicyTest.java +++ b/src/tests/org/owasp/html/StylingPolicyTest.java @@ -47,18 +47,17 @@ public class StylingPolicyTest extends TestCase { @Test public static final void testColors() { - assertSanitizedCss("color:#f00", "color: red"); - assertSanitizedCss( - "background-color:#f00", "background: #f00"); + assertSanitizedCss("color:red", "color: red"); + assertSanitizedCss("background-color:#f00", "background-color: #f00"); + assertSanitizedCss("background:#f00", "background: #f00"); assertSanitizedCss("color:#f00", "color: #F00"); assertSanitizedCss(null, "color: #F000"); assertSanitizedCss("color:#ff0000", "color: #ff0000"); + assertSanitizedCss("color:rgb( 255 , 0 , 0 )", "color: rgb(255, 0, 0)"); + assertSanitizedCss("background:rgb( 100% , 0 , 0 )", + "background: rgb(100%, 0, 0)"); assertSanitizedCss( - "color:#f00", "color: rgb(255, 0, 0)"); - assertSanitizedCss( - "background-color:#f00", "background: rgb(100%, 0, 0)"); - assertSanitizedCss( - "color:#f00", "color: rgba(100%, 0, 0, 100%)"); + "color:rgba( 100% , 0 , 0 , 100% )", "color: RGBA(100%, 0, 0, 100%)"); assertSanitizedCss(null, "color: transparent"); assertSanitizedCss(null, "color: bogus"); assertSanitizedCss(null, "color: expression(alert(1337))"); @@ -76,15 +75,15 @@ public class StylingPolicyTest extends TestCase { assertSanitizedCss( "font-weight:bold", "font-weight: bold"); assertSanitizedCss( - "font-weight:bold", "font: bold"); + "font:bold", "font: bold"); assertSanitizedCss( - "font-weight:bolder", "font: Bolder"); + "font:bolder", "font: Bolder"); assertSanitizedCss( "font-weight:800", "font-weight: 800"); assertSanitizedCss( null, "font-weight: expression(alert(1337))"); assertSanitizedCss( - "font-family:\"evil\"", + "font:'evil'", "font: 3execute evil"); } @@ -93,9 +92,9 @@ public class StylingPolicyTest extends TestCase { assertSanitizedCss( "font-style:italic", "font-style: Italic"); assertSanitizedCss( - "font-style:italic", "font: italic"); + "font:italic", "font: italic"); assertSanitizedCss( - "font-style:oblique", "font: Oblique"); + "font:oblique", "font: Oblique"); assertSanitizedCss( null, "font-style: expression(alert(1337))"); } @@ -103,57 +102,56 @@ public class StylingPolicyTest extends TestCase { @Test public static final void testFontFace() { assertSanitizedCss( - "font-family:\"arial\",\"helvetica\"", "font: Arial, Helvetica"); + "font:'arial' , 'helvetica'", "font: Arial, Helvetica"); assertSanitizedCss( - "font-family:\"Arial\",\"Helvetica\",sans-serif", + "font-family:'arial' , 'helvetica' , sans-serif", "Font-family: Arial, Helvetica, sans-serif"); assertSanitizedCss( - "font-family:\"Monospace\",sans-serif", + "font-family:'monospace' , sans-serif", "Font-family: \"Monospace\", Sans-serif"); assertSanitizedCss( - "font-family:\"Arial Bold\",\"helvetica\",monospace", + "font:'arial bold' , 'helvetica' , monospace", "FONT: \"Arial Bold\", Helvetica, monospace"); assertSanitizedCss( - "font-family:\"Arial Bold\",\"Helvetica\"", + "font-family:'arial bold' , 'helvetica'", "font-family: \"Arial Bold\", Helvetica"); assertSanitizedCss( - "font-family:\"Arial Bold\",\"Helvetica\"", + "font-family:'arial bold' , 'helvetica'", "font-family: 'Arial Bold', Helvetica"); assertSanitizedCss( - "font-family:\"3execute evil\"", + "font-family:'evil'", "font-family: 3execute evil"); assertSanitizedCss( - "font-family:\"Arial Bold\",\"Helvetica\",sans-serif", + "font-family:'arial bold' , , , 'helvetica' , sans-serif", "font-family: 'Arial Bold',,\"\",Helvetica,sans-serif"); } @Test public static final void testFont() { assertSanitizedCss( - "font-family:\"arial\";" - + "font-weight:bold;font-size:12pt;font-style:oblique", + "font:'arial' 12pt bold oblique", "font: Arial 12pt bold oblique"); assertSanitizedCss( - "font-family:\"Times New Roman\";font-weight:bolder;font-size:24px", + "font:'times new roman' 24px bolder", "font: \"Times New Roman\" 24px bolder"); - assertSanitizedCss("font-size:24px", "font: 24px"); + assertSanitizedCss("font:24px", "font: 24px"); // Non-ascii characters discarded. assertSanitizedCss(null, "font: 24ex\\pression"); // Harmless garbage. assertSanitizedCss( - "font-family:\"pression\"", "font: 24ex\0pression"); + "font:24ex 'pression'", "font: 24ex\0pression"); assertSanitizedCss( null, "font: expression(arial)"); assertSanitizedCss( null, "font: rgb(\"expression(alert(1337))//\")"); assertSanitizedCss("font-size:smaller", "font-size: smaller"); - assertSanitizedCss("font-size:smaller", "font: smaller"); + assertSanitizedCss("font:smaller", "font: smaller"); } @Test public static final void testBidiAndAlignmentAttributes() { assertSanitizedCss( - "text-align:left;direction:ltr;unicode-bidi:embed", + "text-align:left;unicode-bidi:embed;direction:ltr", "Text-align: left; Unicode-bidi: Embed; Direction: LTR;"); assertSanitizedCss( null, "text-align:expression(left())"); @@ -181,78 +179,6 @@ public class StylingPolicyTest extends TestCase { } @Test - public static final void testSanitizeColor() { - assertEquals(null, StylingPolicy.sanitizeColor("")); - assertEquals(null, StylingPolicy.sanitizeColor("bogus")); - assertEquals(null, StylingPolicy.sanitizeColor("javascript:evil")); - assertEquals(null, StylingPolicy.sanitizeColor("expression(evil)")); - assertEquals(null, StylingPolicy.sanitizeColor("moz-binding")); - assertEquals(null, StylingPolicy.sanitizeColor("rgb()")); - assertEquals(null, StylingPolicy.sanitizeColor("rgba()")); - assertEquals(null, StylingPolicy.sanitizeColor("rgb(255, 255)")); - assertEquals(null, StylingPolicy.sanitizeColor("rgb(256, 0, 0)")); - assertEquals(null, StylingPolicy.sanitizeColor("rgb(0, 120%, 0)")); - assertEquals("#fff", StylingPolicy.sanitizeColor("white")); - assertEquals("#000", StylingPolicy.sanitizeColor("black")); - assertEquals("#f00", StylingPolicy.sanitizeColor("red")); - assertEquals("#f00", StylingPolicy.sanitizeColor("red")); - assertEquals("#fa8072", StylingPolicy.sanitizeColor("salmon")); - assertEquals("#ff0080", StylingPolicy.sanitizeColor("rgb(255, 0, 128)")); - assertEquals("#ff0080", StylingPolicy.sanitizeColor("rgb(255,0,128)")); - assertEquals("#ff007f", StylingPolicy.sanitizeColor("rgb(100%,0,50%)")); - assertEquals( - "#ff0080", StylingPolicy.sanitizeColor("rgba(100%,0,128,255)")); - assertEquals("#ff0080", StylingPolicy.sanitizeColor("RGB(255, 0, 128)")); - assertEquals( - "#550102", StylingPolicy.sanitizeColor("Rgb( 33.333% , .9 , .9% )")); - assertEquals( - "#540000", StylingPolicy.sanitizeColor("Rgb( 33.03% , .09 , .09% )")); - } - - private static void assertIsNotNonEmptyAsciiAlnumSpaceSeparated(String s) { - assertFalse(s, StylingPolicy.isNonEmptyAsciiAlnumSpaceSeparated(s)); - } - private static void assertIsNonEmptyAsciiAlnumSpaceSeparated(String s) { - assertTrue(s, StylingPolicy.isNonEmptyAsciiAlnumSpaceSeparated(s)); - } - - @Test - public static final void testIsNonEmptyAsciiAlnumSpaceSeparated() { - assertIsNotNonEmptyAsciiAlnumSpaceSeparated(""); - assertIsNotNonEmptyAsciiAlnumSpaceSeparated(" "); - - assertIsNotNonEmptyAsciiAlnumSpaceSeparated("\u002f"); - assertIsNonEmptyAsciiAlnumSpaceSeparated("0"); - assertIsNonEmptyAsciiAlnumSpaceSeparated("9"); - assertIsNotNonEmptyAsciiAlnumSpaceSeparated("\u003a"); - assertIsNotNonEmptyAsciiAlnumSpaceSeparated("\u0040"); - assertIsNonEmptyAsciiAlnumSpaceSeparated("A"); - assertIsNonEmptyAsciiAlnumSpaceSeparated("Z"); - assertIsNotNonEmptyAsciiAlnumSpaceSeparated("\u005b"); - assertIsNotNonEmptyAsciiAlnumSpaceSeparated("\u0060"); - assertIsNonEmptyAsciiAlnumSpaceSeparated("a"); - assertIsNonEmptyAsciiAlnumSpaceSeparated("z"); - assertIsNotNonEmptyAsciiAlnumSpaceSeparated("\u007b"); - - assertIsNotNonEmptyAsciiAlnumSpaceSeparated("Arial/Helvetica"); - assertIsNotNonEmptyAsciiAlnumSpaceSeparated("Arial#Helvetica"); - assertIsNotNonEmptyAsciiAlnumSpaceSeparated("!Arial Helvetica"); - assertIsNotNonEmptyAsciiAlnumSpaceSeparated("Arial Helvetica!"); - assertIsNotNonEmptyAsciiAlnumSpaceSeparated("Arial Helve!tica"); - assertIsNotNonEmptyAsciiAlnumSpaceSeparated("Arial\u0000Helvetica"); - assertIsNotNonEmptyAsciiAlnumSpaceSeparated("<script>evil()</script>"); - assertIsNotNonEmptyAsciiAlnumSpaceSeparated("Arial\uFF26elvetica"); - assertIsNonEmptyAsciiAlnumSpaceSeparated("x y"); - assertIsNonEmptyAsciiAlnumSpaceSeparated(" x y "); - assertIsNonEmptyAsciiAlnumSpaceSeparated("foo"); - assertIsNonEmptyAsciiAlnumSpaceSeparated(" foo "); - assertIsNonEmptyAsciiAlnumSpaceSeparated("foo bar"); - assertIsNonEmptyAsciiAlnumSpaceSeparated(" foo 92 "); - assertIsNonEmptyAsciiAlnumSpaceSeparated("Foo Bar"); - assertIsNonEmptyAsciiAlnumSpaceSeparated("Arial Helvetica"); - } - - @Test public static final void testBoxProperties() { // http://www.w3.org/TR/CSS2/box.html assertSanitizedCss("height:0", "height:0"); @@ -264,22 +190,22 @@ public class StylingPolicyTest extends TestCase { assertSanitizedCss(null, "width:-20"); assertSanitizedCss(null, "width:url('foo')"); assertSanitizedCss(null, "height:6fixed"); - assertSanitizedCss("margin:2", "margin:2 2 2 2"); - assertSanitizedCss("margin:2", "margin:2 2 2"); - assertSanitizedCss("padding:2", "padding:2 2"); + assertSanitizedCss("margin:2 2 2 2", "margin:2 2 2 2"); + assertSanitizedCss("margin:2 2 2", "margin:2 2 2"); + assertSanitizedCss("padding:2 2", "padding:2 2"); assertSanitizedCss("margin:2", "margin:2"); assertSanitizedCss("margin:2px 4px 6px 8px", "margin:2px 4px 6px 8px"); assertSanitizedCss("padding:0 4px 6px", "padding:0 4px 6px"); - assertSanitizedCss("margin:2px 4px 6px", "margin:2px 4px 6px 4px"); + assertSanitizedCss("margin:2px 4px 6px 4px", "margin:2px 4px 6px 4px"); assertSanitizedCss("margin:0 4px", "margin:0 4px"); assertSanitizedCss("margin:0 4px", "margin:0 4 px"); assertSanitizedCss("padding-left:4px", "padding-left:4px"); - assertSanitizedCss("margin-bottom:3px;padding-top:2px;padding-left:0.4em", + assertSanitizedCss("padding-left:0.4em;padding-top:2px;margin-bottom:3px", "padding-left:0.4em;padding-top:2px;margin-bottom:3px"); assertSanitizedCss("padding:0 1em 0.5in 1.5cm", "padding:00. 1EM +00.5 In 1.50cm"); // Mixed. - assertSanitizedCss("margin:0.25em 1em 1em", // in [top horizontal btm] + assertSanitizedCss("margin:1em;margin-top:0.25em", "margin:1em; margin-top:.25em"); } |