diff --git a/Java/antisamy-sample-configs/src/main/resources/antisamy.xml b/Java/antisamy-sample-configs/src/main/resources/antisamy.xml index 88ad723..d751af6 100644 --- a/Java/antisamy-sample-configs/src/main/resources/antisamy.xml +++ b/Java/antisamy-sample-configs/src/main/resources/antisamy.xml @@ -27,6 +27,8 @@ http://www.w3.org/TR/html401/struct/global.html + + @@ -183,7 +185,12 @@ http://www.w3.org/TR/html401/struct/global.html - + + + + + + @@ -500,7 +507,12 @@ http://www.w3.org/TR/html401/struct/global.html - + + + + + + g grin diff --git a/Java/antisamy/src/main/java/org/owasp/validator/html/InternalPolicy.java b/Java/antisamy/src/main/java/org/owasp/validator/html/InternalPolicy.java index 1520006..e1b6e0a 100644 --- a/Java/antisamy/src/main/java/org/owasp/validator/html/InternalPolicy.java +++ b/Java/antisamy/src/main/java/org/owasp/validator/html/InternalPolicy.java @@ -28,6 +28,7 @@ private final boolean preserveComments; private final boolean embedStyleSheets; private final boolean isEncodeUnknownTag; + private final boolean allowDynamicAttributes; protected InternalPolicy(URL baseUrl, ParseContext parseContext) throws PolicyException { @@ -47,6 +48,7 @@ protected InternalPolicy(URL baseUrl, ParseContext parseContext) throws PolicyEx this.preserveComments = isTrue(Policy.PRESERVE_COMMENTS); this.styleTag = getTagByLowercaseName("style"); this.embedStyleSheets = isTrue(Policy.EMBED_STYLESHEETS); + this.allowDynamicAttributes = isTrue(Policy.ALLOW_DYNAMIC_ATTRIBUTES); } protected InternalPolicy(Policy old, Map directives, Map tagRules) { @@ -66,6 +68,7 @@ protected InternalPolicy(Policy old, Map directives, MapDEFAULT_MAX_INPUT_SIZE is used. diff --git a/Java/antisamy/src/main/java/org/owasp/validator/html/Policy.java b/Java/antisamy/src/main/java/org/owasp/validator/html/Policy.java index 839d766..fb3a258 100644 --- a/Java/antisamy/src/main/java/org/owasp/validator/html/Policy.java +++ b/Java/antisamy/src/main/java/org/owasp/validator/html/Policy.java @@ -80,6 +80,7 @@ public static final String PRESERVE_SPACE = "preserveSpace"; public static final String PRESERVE_COMMENTS = "preserveComments"; public static final String ENTITY_ENCODE_INTL_CHARS = "entityEncodeIntlChars"; + public static final String ALLOW_DYNAMIC_ATTRIBUTES = "allowDynamicAttributes"; public static final String ACTION_VALIDATE = "validate"; public static final String ACTION_FILTER = "filter"; @@ -93,6 +94,7 @@ private final Map cssRules; protected final Map directives; private final Map globalAttributes; + private final Map dynamicAttributes; private final TagMatcher allowedEmptyTagsMatcher; private final TagMatcher requiresClosingTagsMatcher; @@ -111,6 +113,7 @@ public Tag getTagByLowercaseName(String tagName) { Map cssRules = new HashMap(); Map directives = new HashMap(); Map globalAttributes = new HashMap(); + Map dynamicAttributes = new HashMap(); List allowedEmptyTags = new ArrayList(); List requireClosingTags = new ArrayList(); @@ -207,6 +210,7 @@ protected Policy(ParseContext parseContext) throws PolicyException { this.cssRules = Collections.unmodifiableMap(parseContext.cssRules); this.directives = Collections.unmodifiableMap(parseContext.directives); this.globalAttributes = Collections.unmodifiableMap(parseContext.globalAttributes); + this.dynamicAttributes = Collections.unmodifiableMap(parseContext.dynamicAttributes); } protected Policy(Policy old, Map directives, Map tagRules) { @@ -217,6 +221,7 @@ protected Policy(Policy old, Map directives, Map ta this.cssRules = old.cssRules; this.directives = directives; this.globalAttributes = old.globalAttributes; + this.dynamicAttributes = old.dynamicAttributes; } protected static ParseContext getSimpleParseContext(Element topLevelElement) throws PolicyException { @@ -301,6 +306,7 @@ private static void parsePolicy(Element topLevelElement, ParseContext parseConte parseDirectives(getFirstChild(topLevelElement, "directives"), parseContext.directives); parseCommonAttributes(getFirstChild(topLevelElement, "common-attributes"), parseContext.commonAttributes, parseContext.commonRegularExpressions); parseGlobalAttributes(getFirstChild(topLevelElement, "global-tag-attributes"), parseContext.globalAttributes, parseContext.commonAttributes); + parseDynamicAttributes(getFirstChild(topLevelElement, "dynamic-tag-attributes"), parseContext.dynamicAttributes, parseContext.commonAttributes); parseTagRules(getFirstChild(topLevelElement, "tag-rules"), parseContext.commonAttributes, parseContext.commonRegularExpressions, parseContext.tagRules); parseCSSRules(getFirstChild(topLevelElement, "css-rules"), parseContext.cssRules, parseContext.commonRegularExpressions); @@ -474,6 +480,30 @@ private static void parseGlobalAttributes(Element root, Map g } /** + * Go through section of the policy file. + * + * @param root Top level of + * @param dynamicAttributes A HashMap of dynamic Attributes that need validation for every tag. + * @param commonAttributes The common attributes + * @throws PolicyException + */ + private static void parseDynamicAttributes(Element root, Map dynamicAttributes, Map commonAttributes) throws PolicyException { + for (Element ele : getByTagName(root, "attribute")) { + + String name = getAttributeValue(ele, "name"); + + Attribute toAdd = commonAttributes.get(name.toLowerCase()); + + if (toAdd != null) { + String attrName = name.toLowerCase().substring(0, name.length() - 1); + dynamicAttributes.put(attrName, toAdd); + } else { + throw new PolicyException("Dynamic attribute '" + name + "' was not defined in "); + } + } + } + + /** * Go through the section of the policy file. * * @param root Top level of @@ -715,6 +745,25 @@ public Attribute getGlobalAttributeByName(String name) { } /** + * A method for returning one of the dynamic entries by + * name. + * + * @param name The name of the dynamic global-attribute we want to look up. + * @return An Attribute associated with the global-attribute lookup name specified, + * or null if not found. + */ + public Attribute getDynamicAttributeByName(String name) { + Attribute dynamicAttribute = null; + for (String d : dynamicAttributes.keySet()) { + if (name.startsWith(d)) { + dynamicAttribute = dynamicAttributes.get(d); + break; + } + } + return dynamicAttribute; + } + + /** * Return all the allowed empty tags configured in the Policy. * * @return A String array of all the he allowed empty tags configured in the Policy. diff --git a/Java/antisamy/src/main/java/org/owasp/validator/html/scan/MagicSAXFilter.java b/Java/antisamy/src/main/java/org/owasp/validator/html/scan/MagicSAXFilter.java index 4979a68..e4ed2d8 100644 --- a/Java/antisamy/src/main/java/org/owasp/validator/html/scan/MagicSAXFilter.java +++ b/Java/antisamy/src/main/java/org/owasp/validator/html/scan/MagicSAXFilter.java @@ -276,6 +276,10 @@ public void startElement(QName element, XMLAttributes attributes, Augmentations if (attribute == null) { // no policy defined, perhaps it is a global attribute attribute = policy.getGlobalAttributeByName(nameLower); + if (attribute == null && policy.isAllowDynamicAttributes()) { + // not a global attribute, perhaps it is a dynamic attribute, if allowed + attribute = policy.getDynamicAttributeByName(nameLower); + } } // boolean isAttributeValid = false; if ("style".equalsIgnoreCase(name)) { diff --git a/Java/antisamy/src/test/java/org/owasp/validator/html/test/AntiSamyTest.java b/Java/antisamy/src/test/java/org/owasp/validator/html/test/AntiSamyTest.java index 2396579..9a1a2ff 100644 --- a/Java/antisamy/src/test/java/org/owasp/validator/html/test/AntiSamyTest.java +++ b/Java/antisamy/src/test/java/org/owasp/validator/html/test/AntiSamyTest.java @@ -1229,4 +1229,21 @@ public void testWhitespaceNotBeingMangled() throws ScanException, PolicyExceptio CleanResults preserveSpaceResults = as.scan(test, preserveSpace, AntiSamy.SAX); assertEquals( expected, preserveSpaceResults.getCleanHTML() ); } + + @Test + public void issue159() throws ScanException, PolicyException { + /* issue #159 - allow dynamic HTML5 data-* attribute */ + String good = "

Hello World!

"; + String bad = "

Hello World!

"; + String goodExpected = "

Hello World!

"; + String badExpected = "

Hello World!

"; + // test good attribute "data-" + CleanResults cr = as.scan(good, policy, AntiSamy.SAX); + String s = cr.getCleanHTML(); + assertEquals(goodExpected, s); + // test bad attribute "dat-" + cr = as.scan(bad, policy, AntiSamy.SAX); + s = cr.getCleanHTML(); + assertEquals(badExpected, s); + } } diff --git a/Java/antisamy/src/test/java/org/owasp/validator/html/test/PolicyTest.java b/Java/antisamy/src/test/java/org/owasp/validator/html/test/PolicyTest.java index 5697566..36c1100 100644 --- a/Java/antisamy/src/test/java/org/owasp/validator/html/test/PolicyTest.java +++ b/Java/antisamy/src/test/java/org/owasp/validator/html/test/PolicyTest.java @@ -27,13 +27,14 @@ private static final String DIRECTIVES = "\n\n"; private static final String COMMON_ATTRIBUTES = "\n\n"; private static final String GLOBAL_TAG_ATTRIBUTES = "\n\n"; + private static final String DYNAMIC_TAG_ATTRIBUTES = "\n\n"; private static final String TAG_RULES = "\n"; private static final String CSS_RULES = "\n\n"; private static final String COMMON_REGEXPS = "\n"; private static final String FOOTER = ""; private String assembleFile(String allowedEmptyTagsSection) { - return HEADER + DIRECTIVES + COMMON_REGEXPS + COMMON_ATTRIBUTES + GLOBAL_TAG_ATTRIBUTES + TAG_RULES + CSS_RULES + + return HEADER + DIRECTIVES + COMMON_REGEXPS + COMMON_ATTRIBUTES + GLOBAL_TAG_ATTRIBUTES + DYNAMIC_TAG_ATTRIBUTES + TAG_RULES + CSS_RULES + allowedEmptyTagsSection + FOOTER; }