You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2015/05/13 15:40:08 UTC

svn commit: r1679207 - in /sling/trunk/bundles/extensions/xss/src: main/java/org/apache/sling/xss/ main/java/org/apache/sling/xss/impl/ test/java/org/apache/sling/xss/impl/

Author: radu
Date: Wed May 13 13:40:07 2015
New Revision: 1679207

URL: http://svn.apache.org/r1679207
Log:
SLING-4557 - Add JSON and XML validation to the XSS Protection API

* Added validator methods to the API
* Added JSON validation implementation using Apache Commons JSON
* Added XML validation implementation using SAX
* Added unit tests
(applied slightly modified patch from Vlad Bailescu; closes #81)

Modified:
    sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/XSSAPI.java
    sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/impl/XSSAPIImpl.java
    sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/package-info.java
    sling/trunk/bundles/extensions/xss/src/test/java/org/apache/sling/xss/impl/XSSAPIImplTest.java

Modified: sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/XSSAPI.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/XSSAPI.java?rev=1679207&r1=1679206&r2=1679207&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/XSSAPI.java (original)
+++ sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/XSSAPI.java Wed May 13 13:40:07 2015
@@ -1,4 +1,5 @@
-/*******************************************************************************
+/**
+ * ****************************************************************************
  * Licensed to the Apache Software Foundation (ASF) under one or
  * more contributor license agreements. See the NOTICE file
  * distributed with this work for additional information regarding
@@ -6,14 +7,15 @@
  * Apache License, Version 2.0 (the "License"); you may not use
  * this file except in compliance with the License. You may obtain
  * a copy of the License at
- *
+ * <p/>
  * http://www.apache.org/licenses/LICENSE-2.0 Unless required by
  * applicable law or agreed to in writing, software distributed
  * under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions
  * and limitations under the License.
- ******************************************************************************/
+ * ****************************************************************************
+ */
 package org.apache.sling.xss;
 
 
@@ -52,7 +54,7 @@ public interface XSSAPI {
      * @return a sanitized integer
      */
     @Nullable
-    public Integer getValidInteger(@Nullable String integer, @Nullable int defaultValue);
+    Integer getValidInteger(@Nullable String integer, int defaultValue);
 
     /**
      * Validate a string which should contain a long, returning a default value if the source is
@@ -63,7 +65,7 @@ public interface XSSAPI {
      * @return a sanitized integer
      */
     @Nullable
-    public Long getValidLong(@Nullable String source, @Nullable long defaultValue);
+    Long getValidLong(@Nullable String source,long defaultValue);
 
     /**
      * Validate a string which should contain a dimension, returning a default value if the source is
@@ -74,7 +76,7 @@ public interface XSSAPI {
      * @return a sanitized dimension
      */
     @Nullable
-    public String getValidDimension(@Nullable String dimension, @Nullable String defaultValue);
+    String getValidDimension(@Nullable String dimension, @Nullable String defaultValue);
 
     /**
      * Sanitizes a URL for writing as an HTML href or src attribute value.
@@ -83,7 +85,7 @@ public interface XSSAPI {
      * @return a sanitized URL (possibly empty)
      */
     @Nonnull
-    public String getValidHref(@Nullable String url);
+    String getValidHref(@Nullable String url);
 
     /**
      * Validate a Javascript token.  The value must be either a single identifier, a literal number,
@@ -94,7 +96,7 @@ public interface XSSAPI {
      * @return a string containing a single identifier, a literal number, or a literal string token
      */
     @Nullable
-    public String getValidJSToken(@Nullable String token, @Nullable String defaultValue);
+    String getValidJSToken(@Nullable String token, @Nullable String defaultValue);
 
     /**
      * Validate a style/CSS token. Valid CSS tokens are specified at http://www.w3.org/TR/css3-syntax/
@@ -105,7 +107,7 @@ public interface XSSAPI {
      * @return a string containing sanitized style token
      */
     @Nullable
-    public String getValidStyleToken(@Nullable String token, @Nullable String defaultValue);
+    String getValidStyleToken(@Nullable String token, @Nullable String defaultValue);
 
     /**
      * Validate a CSS color value. Color values as specified at http://www.w3.org/TR/css3-color/#colorunits
@@ -117,17 +119,35 @@ public interface XSSAPI {
      * @return a string a css color value.
      */
     @Nullable
-    public String getValidCSSColor(@Nullable String color, @Nullable String defaultColor);
+    String getValidCSSColor(@Nullable String color, @Nullable String defaultColor);
 
     /**
-     * Validate multiline comment to be used inside a <script>...</script> or <style>...</style> block. Multiline
+     * Validate multi-line comment to be used inside a <script>...</script> or <style>...</style> block. Multi-line
      * comment end block is disallowed
      *
      * @param comment           the comment to be used
      * @param defaultComment    a default value to use if the comment is {@code null} or not valid.
-     * @return a valid multiline comment
+     * @return a valid multi-line comment
+     */
+    String getValidMultiLineComment(@Nullable String comment, @Nullable String defaultComment);
+
+    /**
+     * Validate a JSON string
+     *
+     * @param json          the JSON string to validate
+     * @param defaultJson   the default value to use if {@code json} is {@code null} or not valid
+     * @return a valid JSON string
+     */
+    String getValidJSON(@Nullable String json, @Nullable String defaultJson);
+
+    /**
+     * Validate an XML string
+     *
+     * @param xml           the XML string to validate
+     * @param defaultXml    the default value to use if {@code xml} is {@code null} or not valid
+     * @return a valid XML string
      */
-    public String getValidMultiLineComment(@Nullable String comment, @Nullable String defaultComment);
+    String getValidXML(@Nullable String xml, @Nullable String defaultXml);
 
     // =============================================================================================
     // ENCODERS
@@ -141,7 +161,7 @@ public interface XSSAPI {
      * @return an encoded version of the source
      */
     @Nullable
-    public String encodeForHTML(@Nullable String source);
+    String encodeForHTML(@Nullable String source);
 
     /**
      * Encodes a source string for writing to an HTML attribute value.
@@ -151,7 +171,7 @@ public interface XSSAPI {
      * @return an encoded version of the source
      */
     @Nullable
-    public String encodeForHTMLAttr(@Nullable String source);
+    String encodeForHTMLAttr(@Nullable String source);
 
     /**
      * Encodes a source string for XML element content.
@@ -161,7 +181,7 @@ public interface XSSAPI {
      * @return an encoded version of the source
      */
     @Nullable
-    public String encodeForXML(@Nullable String source);
+    String encodeForXML(@Nullable String source);
 
     /**
      * Encodes a source string for writing to an XML attribute value.
@@ -170,7 +190,7 @@ public interface XSSAPI {
      * @return an encoded version of the source
      */
     @Nullable
-    public String encodeForXMLAttr(@Nullable String source);
+    String encodeForXMLAttr(@Nullable String source);
 
     /**
      * Encodes a source string for writing to JavaScript string content.
@@ -181,7 +201,7 @@ public interface XSSAPI {
      * @return an encoded version of the source
      */
     @Nullable
-    public String encodeForJSString(@Nullable String source);
+    String encodeForJSString(@Nullable String source);
 
     /**
      * Encodes a source string for writing to CSS string content.
@@ -192,7 +212,7 @@ public interface XSSAPI {
      * @return an encoded version of the source
      */
     @Nullable
-    public String encodeForCSSString(@Nullable String source);
+    String encodeForCSSString(@Nullable String source);
 
 
     // =============================================================================================
@@ -207,7 +227,7 @@ public interface XSSAPI {
      * @return a string containing the sanitized HTML which may be an empty string if {@code source} is {@code null} or empty
      */
     @Nonnull
-    public String filterHTML(@Nullable String source);
+    String filterHTML(@Nullable String source);
 
 
     // =============================================================================================
@@ -221,7 +241,7 @@ public interface XSSAPI {
      * @param request the request from which to obtain the {@link org.apache.sling.xss.XSSAPI}
      * @return an XSSAPI service capable of validating hrefs.
      */
-    public XSSAPI getRequestSpecificAPI(SlingHttpServletRequest request);
+    XSSAPI getRequestSpecificAPI(SlingHttpServletRequest request);
 
     /**
      * Returns an XSSAPI instance capable of mapping resource URLs.
@@ -230,6 +250,6 @@ public interface XSSAPI {
      * @param resourceResolver the resolver from which to obtain the {@link org.apache.sling.xss.XSSAPI}
      * @return an XSSAPI service capable of validating hrefs.
      */
-    public XSSAPI getResourceResolverSpecificAPI(ResourceResolver resourceResolver);
+    XSSAPI getResourceResolverSpecificAPI(ResourceResolver resourceResolver);
 
 }

Modified: sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/impl/XSSAPIImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/impl/XSSAPIImpl.java?rev=1679207&r1=1679206&r2=1679207&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/impl/XSSAPIImpl.java (original)
+++ sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/impl/XSSAPIImpl.java Wed May 13 13:40:07 2015
@@ -16,28 +16,39 @@
  ******************************************************************************/
 package org.apache.sling.xss.impl;
 
+import java.io.StringReader;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import javax.annotation.Nonnull;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.Service;
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
 import org.apache.sling.xss.ProtectionContext;
 import org.apache.sling.xss.XSSAPI;
 import org.apache.sling.xss.XSSFilter;
 import org.owasp.encoder.Encode;
 import org.owasp.esapi.ESAPI;
 import org.owasp.esapi.Validator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
 
 @Component
 @Service(value = XSSAPI.class)
 public class XSSAPIImpl implements XSSAPI {
-
-    // =============================================================================================
-    // VALIDATORS
-    //
+    private static final Logger LOGGER = LoggerFactory.getLogger(XSSAPIImpl.class);
 
     @Reference
     private XSSFilter xssFilter = null;
@@ -46,9 +57,30 @@ public class XSSAPIImpl implements XSSAP
 
     private static final Pattern PATTERN_AUTO_DIMENSION = Pattern.compile("['\"]?auto['\"]?");
 
+    private SAXParserFactory factory;
+
+    @Activate
+    @SuppressWarnings("unused")
+    protected void activate() {
+        factory = SAXParserFactory.newInstance();
+        factory.setValidating(false);
+        factory.setNamespaceAware(true);
+    }
+
+    @Deactivate
+    @SuppressWarnings("unused")
+    protected void deactivate() {
+        factory = null;
+    }
+
+    // =============================================================================================
+    // VALIDATORS
+    //
+
     /**
      * @see org.apache.sling.xss.XSSAPI#getValidInteger(String, int)
      */
+    @Override
     public Integer getValidInteger(String integer, int defaultValue) {
         if (integer != null && integer.length() > 0) {
             try {
@@ -65,6 +97,7 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#getValidLong(String, long)
      */
+    @Override
     public Long getValidLong(String source, long defaultValue) {
         if (source != null && source.length() > 0) {
             try {
@@ -83,6 +116,7 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#getValidDimension(String, String)
      */
+    @Override
     public String getValidDimension(String dimension, String defaultValue) {
         if (dimension != null && dimension.length() > 0) {
             if (PATTERN_AUTO_DIMENSION.matcher(dimension).matches()) {
@@ -156,6 +190,8 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#getValidHref(String)
      */
+    @Override
+    @Nonnull
     public String getValidHref(final String url) {
         if (url != null && url.length() > 0) {
             // Percent-encode characters that are not allowed in unquoted
@@ -189,6 +225,7 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#getValidJSToken(String, String)
      */
+    @Override
     public String getValidJSToken(String token, String defaultValue) {
         if (token != null && token.length() > 0) {
             token = token.trim();
@@ -238,6 +275,7 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#getValidStyleToken(String, String)
      */
+    @Override
     public String getValidStyleToken(String token, String defaultValue) {
         if (token != null && token.length() > 0 && token.matches(CSS_TOKEN)) {
             return token;
@@ -249,6 +287,7 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#getValidCSSColor(String, String)
      */
+    @Override
     public String getValidCSSColor(String color, String defaultColor) {
         if (color != null && color.length() > 0) {
             color = color.trim();
@@ -272,6 +311,7 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#getValidMultiLineComment(String, String)
      */
+    @Override
     public String getValidMultiLineComment(String comment, String defaultComment) {
         if (comment != null && !comment.contains("*/")) {
             return comment;
@@ -279,6 +319,62 @@ public class XSSAPIImpl implements XSSAP
         return defaultComment;
     }
 
+    /**
+     * @see org.apache.sling.xss.XSSAPI#getValidJSON(String, String)
+     */
+    @Override
+    public String getValidJSON(String json, String defaultJson) {
+        if (json == null) {
+            return getValidJSON(defaultJson, "");
+        }
+        json = json.trim();
+        if ("".equals(json)) {
+            return "";
+        }
+        int curlyIx = json.indexOf("{");
+        int straightIx = json.indexOf("[");
+        if (curlyIx >= 0 && (curlyIx < straightIx || straightIx < 0)) {
+            try {
+                JSONObject obj = new JSONObject(json);
+                return obj.toString();
+            } catch (JSONException e) {
+                LOGGER.debug("JSON validation failed: " + e.getMessage(), e);
+            }
+        } else {
+            try {
+                JSONArray arr = new JSONArray(json);
+                return arr.toString();
+            } catch (JSONException e) {
+                LOGGER.debug("JSON validation failed: " + e.getMessage(), e);
+            }
+        }
+        return getValidJSON(defaultJson, "");
+    }
+
+    /**
+     * @see org.apache.sling.xss.XSSAPI#getValidXML(String, String)
+     */
+    @Override
+    public String getValidXML(String xml, String defaultXml) {
+        if (xml == null) {
+            return getValidXML(defaultXml, "");
+        }
+        xml = xml.trim();
+        if ("".equals(xml)) {
+            return "";
+        }
+
+        try {
+            SAXParser parser = factory.newSAXParser();
+            XMLReader reader = parser.getXMLReader();
+            reader.parse(new InputSource(new StringReader(xml)));
+            return xml;
+        } catch (Exception e) {
+            LOGGER.debug("XML validation failed: " + e.getMessage(), e);
+        }
+        return getValidXML(defaultXml, "");
+    }
+
     // =============================================================================================
     // ENCODERS
     //
@@ -286,6 +382,7 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#encodeForHTML(String)
      */
+    @Override
     public String encodeForHTML(String source) {
         return source == null ? null : Encode.forHtml(source);
     }
@@ -293,6 +390,7 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#encodeForHTMLAttr(String)
      */
+    @Override
     public String encodeForHTMLAttr(String source) {
         return source == null ? null : Encode.forHtmlAttribute(source);
     }
@@ -300,6 +398,7 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#encodeForXML(String)
      */
+    @Override
     public String encodeForXML(String source) {
         return source == null ? null : Encode.forXml(source);
     }
@@ -307,6 +406,7 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#encodeForXMLAttr(String)
      */
+    @Override
     public String encodeForXMLAttr(String source) {
         return source == null ? null : Encode.forXmlAttribute(source);
     }
@@ -314,6 +414,7 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#encodeForJSString(String)
      */
+    @Override
     public String encodeForJSString(String source) {
         return source == null ? null : Encode.forJavaScript(source);
     }
@@ -321,6 +422,7 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#encodeForCSSString(String)
      */
+    @Override
     public String encodeForCSSString(String source) {
         return source == null ? null : Encode.forCssString(source);
     }
@@ -332,6 +434,8 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#filterHTML(String)
      */
+    @Override
+    @Nonnull
     public String filterHTML(String source) {
         return xssFilter.filter(ProtectionContext.HTML_HTML_CONTENT, source);
     }
@@ -343,6 +447,7 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#getRequestSpecificAPI(org.apache.sling.api.SlingHttpServletRequest)
      */
+    @Override
     public XSSAPI getRequestSpecificAPI(final SlingHttpServletRequest request) {
         return this;
     }
@@ -350,6 +455,7 @@ public class XSSAPIImpl implements XSSAP
     /**
      * @see org.apache.sling.xss.XSSAPI#getResourceResolverSpecificAPI(org.apache.sling.api.resource.ResourceResolver)
      */
+    @Override
     public XSSAPI getResourceResolverSpecificAPI(final ResourceResolver resourceResolver) {
         return this;
     }

Modified: sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/package-info.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/package-info.java?rev=1679207&r1=1679206&r2=1679207&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/package-info.java (original)
+++ sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/package-info.java Wed May 13 13:40:07 2015
@@ -17,9 +17,9 @@
 /**
  * XSS Protection Service
  *
- * @version 1.0.0
+ * @version 1.1.0
  */
-@Version("1.0.0")
+@Version("1.1.0")
 package org.apache.sling.xss;
 
 import aQute.bnd.annotation.Version;

Modified: sling/trunk/bundles/extensions/xss/src/test/java/org/apache/sling/xss/impl/XSSAPIImplTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/xss/src/test/java/org/apache/sling/xss/impl/XSSAPIImplTest.java?rev=1679207&r1=1679206&r2=1679207&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/xss/src/test/java/org/apache/sling/xss/impl/XSSAPIImplTest.java (original)
+++ sling/trunk/bundles/extensions/xss/src/test/java/org/apache/sling/xss/impl/XSSAPIImplTest.java Wed May 13 13:40:07 2015
@@ -41,6 +41,8 @@ import static org.mockito.Mockito.when;
 public class XSSAPIImplTest {
 
     public static final String RUBBISH = "rubbish";
+    public static final String RUBBISH_JSON = "[\"rubbish\"]";
+    public static final String RUBBISH_XML = "<rubbish/>";
 
     private XSSAPI xssAPI;
 
@@ -59,6 +61,7 @@ public class XSSAPIImplTest {
             Whitebox.setInternalState(xssFilter, "defaultHandler", mockPolicyHandler);
 
             xssAPI = new XSSAPIImpl();
+            Whitebox.invokeMethod(xssAPI, "activate");
             Field filterField = XSSAPIImpl.class.getDeclaredField("xssFilter");
             filterField.setAccessible(true);
             filterField.set(xssAPI, xssFilter);
@@ -550,4 +553,92 @@ public class XSSAPIImplTest {
             }
         }
     }
+
+    @Test
+    public void testGetValidJSON() {
+        String[][] testData = {
+                {null,      RUBBISH_JSON},
+                {"",        ""},
+                {"1]",      RUBBISH_JSON},
+                {"{}",      "{}"},
+                {"{1}",     RUBBISH_JSON},
+                {
+                        "{test: 'test'}",
+                        "{\"test\":\"test\"}"
+                },
+                {
+                        "{test:\"test}",
+                        RUBBISH_JSON
+                },
+                {
+                        "{test1:'test1', test2: {test21: 'test21', test22: 'test22'}}",
+                        "{\"test1\":\"test1\",\"test2\":{\"test21\":\"test21\",\"test22\":\"test22\"}}"
+                },
+                {"[]",      "[]"},
+                {"[1,2]",   "[1,2]"},
+                {"[1",      RUBBISH_JSON},
+                {
+                        "[{test: 'test'}]",
+                        "[{\"test\":\"test\"}]"
+                }
+        };
+        for (String[] aTestData : testData) {
+            String source = aTestData[0];
+            String expected = aTestData[1];
+
+            String result = xssAPI.getValidJSON(source, RUBBISH_JSON);
+            if (!result.equals(expected)) {
+                fail("Validating JSON '" + source + "', expecting '" + expected + "', but got '" + result + "'");
+            }
+        }
+    }
+
+    @Test
+    public void testGetValidXML() {
+        String[][] testData = {
+                {null,      RUBBISH_XML},
+                {"",        ""},
+                {
+                        "<t/>",
+                        "<t/>"
+                },
+                {
+                        "<t>",
+                        RUBBISH_XML
+                },
+                {
+                        "<t>test</t>",
+                        "<t>test</t>"
+                },
+                {
+                        "<t>test",
+                        RUBBISH_XML
+                },
+                {
+                        "<t t=\"t\">test</t>",
+                        "<t t=\"t\">test</t>"
+                },
+                {
+                        "<t t=\"t>test</t>",
+                        RUBBISH_XML
+                },
+                {
+                        "<t><w>xyz</w></t>",
+                        "<t><w>xyz</w></t>"
+                },
+                {
+                        "<t><w>xyz</t></w>",
+                        RUBBISH_XML
+                }
+        };
+        for (String[] aTestData : testData) {
+            String source = aTestData[0];
+            String expected = aTestData[1];
+
+            String result = xssAPI.getValidXML(source, RUBBISH_XML);
+            if (!result.equals(expected)) {
+                fail("Validating XML '" + source + "', expecting '" + expected + "', but got '" + result + "'");
+            }
+        }
+    }
 }