Adding basic HTML encoding as per SLING-3665

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1604828 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index d35308e..daf7cdc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,128 +1,149 @@
 <?xml version="1.0" encoding="ISO-8859-1"?>
-<!--
-  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 copyright ownership.  The ASF licenses this file
-  to you under the 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
+<!-- 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 copyright ownership. The ASF licenses this file to 
+	you under the 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 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 
-   http://www.apache.org/licenses/LICENSE-2.0
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.sling</groupId>
+		<artifactId>sling</artifactId>
+		<version>19</version>
+		<relativePath>../../../parent/pom.xml</relativePath>
+	</parent>
 
-  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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<artifactId>org.apache.sling.scripting.jsp.taglib</artifactId>
+	<version>2.2.1-SNAPSHOT</version>
+	<packaging>bundle</packaging>
 
-    <modelVersion>4.0.0</modelVersion>
-    <parent>
-        <groupId>org.apache.sling</groupId>
-        <artifactId>sling</artifactId>
-        <version>19</version>
-        <relativePath>../../../parent/pom.xml</relativePath>
-    </parent>
-
-    <artifactId>org.apache.sling.scripting.jsp.taglib</artifactId>
-    <version>2.2.1-SNAPSHOT</version>
-    <packaging>bundle</packaging>
-
-    <name>Apache Sling JSP Tag Library</name>
-    <description>
+	<name>Apache Sling JSP Tag Library</name>
+	<description>
         Core Tag Library for Apache Sling JSP support
     </description>
+    
+	<scm>
+		<connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/scripting/jsp-taglib</connection>
+		<developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/scripting/jsp-taglib</developerConnection>
+		<url>http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/jsp-taglib</url>
+	</scm>
 
-    <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/scripting/jsp-taglib</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/scripting/jsp-taglib</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/jsp-taglib</url>
-    </scm>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<extensions>true</extensions>
+				<configuration>
+					<instructions>
+						<Export-Package>
+							org.apache.sling.scripting.jsp.taglib.*;version=2.2.0
+						</Export-Package>
+						<Import-Package>
+							javax.jcr;resolution:=optional,
+							javax.servlet.jsp.*;version=2.0,
+                            !bsh.*,
+                            !nu.xom.*,
+                            !org.apache.commons.beanutils.*,
+                            !org.apache.commons.configuration.*,
+                            !org.owasp.validator.html.*,
+                            !org.apache.log4j.*,
+							*
+						</Import-Package>
+						<Embed-Dependency>esapi;inline=true</Embed-Dependency>
+					</instructions>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-surefire-plugin</artifactId>
+				<configuration>
+					<systemPropertyVariables>
+                        <org.owasp.esapi.resources>${project.basedir}/src/main/resources</org.owasp.esapi.resources>
+					</systemPropertyVariables>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
 
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.felix</groupId>
-                <artifactId>maven-bundle-plugin</artifactId>
-                <extensions>true</extensions>
-                <configuration>
-                    <instructions>
-                        <Export-Package>
-                            org.apache.sling.scripting.jsp.taglib.*;version=2.2.0
-                        </Export-Package>
-                        <Import-Package>
-                            javax.jcr;resolution:=optional,
-                            javax.servlet.jsp.*;version=2.0,
-                            *
-                        </Import-Package>
-                    </instructions>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
+	<dependencies>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>servlet-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>jsp-api</artifactId>
+			<version>2.0</version>
+			<!-- using compile scope to help IDEs -->
+			<scope>compile</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.api</artifactId>
+			<version>2.1.0</version>
+			<!-- using compile scope to help IDEs -->
+			<scope>compile</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.scripting.jsp</artifactId>
+			<version>2.0.8</version>
+			<!-- using compile scope to help IDEs -->
+			<scope>compile</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.compendium</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+			<!-- using compile scope to help IDEs -->
+			<scope>compile</scope>
+		</dependency>
+		<dependency>
+			<groupId>commons-lang</groupId>
+			<artifactId>commons-lang</artifactId>
+			<version>2.4</version>
+			<scope>compile</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.owasp.esapi</groupId>
+			<artifactId>esapi</artifactId>
+			<version>2.1.0</version>
+			<scope>compile</scope>
+		</dependency>
 
-    <dependencies>
-        <dependency>
-            <groupId>javax.servlet</groupId>
-            <artifactId>servlet-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>javax.servlet</groupId>
-            <artifactId>jsp-api</artifactId>
-            <version>2.0</version>
-            <!-- using compile scope to help IDEs -->
-            <scope>compile</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.api</artifactId>
-            <version>2.1.0</version>
-            <!-- using compile scope to help IDEs -->
-            <scope>compile</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.scripting.jsp</artifactId>
-            <version>2.0.8</version>
-            <!-- using compile scope to help IDEs -->
-            <scope>compile</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.core</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.compendium</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-            <!-- using compile scope to help IDEs -->
-            <scope>compile</scope>
-        </dependency>
-	
-        <!-- Testing Dependencies -->
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.jmock</groupId>
-            <artifactId>jmock-junit4</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-simple</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.commons.testing</artifactId>
-            <version>2.0.12</version>
-            <scope>test</scope>
-        </dependency>
-    </dependencies>
+		<!-- Testing Dependencies -->
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.jmock</groupId>
+			<artifactId>jmock-junit4</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-simple</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.commons.testing</artifactId>
+			<version>2.0.12</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/scripting/jsp/taglib/EncodeTag.java b/src/main/java/org/apache/sling/scripting/jsp/taglib/EncodeTag.java
new file mode 100644
index 0000000..7dbe506
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/jsp/taglib/EncodeTag.java
@@ -0,0 +1,149 @@
+/*
+ * 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 copyright ownership.
+ * The ASF licenses this file to You under the 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
+ *
+ *      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.scripting.jsp.taglib;
+
+import java.io.IOException;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.BodyTagSupport;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.sling.scripting.jsp.taglib.helpers.XSSSupport;
+import org.apache.sling.scripting.jsp.taglib.helpers.XSSSupport.ENCODING_MODE;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tag for writing properly XSS encoded text to the response using the OWASP
+ * ESAPI for supporting a number of encoding modes.
+ */
+public class EncodeTag extends BodyTagSupport {
+
+	private static final long serialVersionUID = 5673936481350419997L;
+
+	private static final Logger log = LoggerFactory.getLogger(EncodeTag.class);
+	private String value;
+	private String defaultValue;
+	private ENCODING_MODE mode;
+	private boolean readBody = false;
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.jsp.tagext.TagSupport#doEndTag()
+	 */
+	@Override
+	public int doEndTag() throws JspException {
+		log.trace("doEndTag");
+
+		if (readBody) {
+			if (bodyContent != null && bodyContent.getString() != null) {
+				String encoded = XSSSupport.encode(bodyContent.getString(),
+						mode);
+				write(encoded);
+			}
+		}
+		return EVAL_PAGE;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.jsp.tagext.BodyTagSupport#doStartTag()
+	 */
+	@Override
+	public int doStartTag() throws JspException {
+		int res = SKIP_BODY;
+		String unencoded = value;
+		if (unencoded == null) {
+			unencoded = defaultValue;
+		}
+
+		if (unencoded != null) {
+			String encoded = XSSSupport.encode(unencoded, mode);
+			write(encoded);
+		} else {
+			readBody = true;
+			res = EVAL_BODY_BUFFERED;
+		}
+		return res;
+	}
+
+	/**
+	 * @return the default value
+	 */
+	public String getDefault() {
+		return defaultValue;
+	}
+
+	/**
+	 * @return the mode
+	 */
+	public String getMode() {
+		return mode.toString();
+	}
+
+	/**
+	 * @return the value
+	 */
+	public String getValue() {
+		return value;
+	}
+
+	/**
+	 * @param defaultValue
+	 *            the default value to set
+	 */
+	public void setDefault(String defaultValue) {
+		this.defaultValue = defaultValue;
+	}
+
+	/**
+	 * @param mode
+	 *            the mode to set
+	 */
+	public void setMode(String mode) {
+		this.mode = XSSSupport.getEncodingMode(mode);
+	}
+
+	/**
+	 * @param value
+	 *            the value to set
+	 */
+	public void setValue(String value) {
+		this.value = value;
+	}
+
+	/**
+	 * Writes the encoded text to the response.
+	 * 
+	 * @param encoded
+	 *            the encoded text to write to the page
+	 * @throws JspException
+	 */
+	private void write(String encoded) throws JspException {
+		if (!StringUtils.isEmpty(encoded)) {
+			try {
+				pageContext.getOut().write(encoded);
+			} catch (IOException e) {
+				log.error("Exception writing escaped content to page", e);
+				throw new JspException(
+						"Exception writing escaped content to page", e);
+			}
+		}
+	}
+}
diff --git a/src/main/java/org/apache/sling/scripting/jsp/taglib/SlingFunctions.java b/src/main/java/org/apache/sling/scripting/jsp/taglib/SlingFunctions.java
index c51271f..4d30b98 100644
--- a/src/main/java/org/apache/sling/scripting/jsp/taglib/SlingFunctions.java
+++ b/src/main/java/org/apache/sling/scripting/jsp/taglib/SlingFunctions.java
@@ -22,6 +22,7 @@
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.scripting.jsp.taglib.helpers.XSSSupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -65,6 +66,19 @@
 	}
 
 	/**
+	 * Loads the Class for the name from the current thread's classload.
+	 * 
+	 * @param className
+	 *            The name of the class to load
+	 * @return the class
+	 * @throws ClassNotFoundException
+	 *             a class with the specified name could not be found
+	 */
+	public static String escape(String value, String mode) {
+		return XSSSupport.encode(value, XSSSupport.getEncodingMode(mode));
+	}
+
+	/**
 	 * Searches for resources using the given query formulated in the given
 	 * language.
 	 * 
@@ -170,7 +184,6 @@
 		return resource != null ? resource.listChildren().hasNext() : false;
 	}
 
-
 	/**
 	 * Method for allowing the invocation of the Sling Resource listChildren
 	 * method.
@@ -207,4 +220,5 @@
 		return Thread.currentThread().getContextClassLoader()
 				.loadClass(className);
 	}
+
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/scripting/jsp/taglib/helpers/XSSSupport.java b/src/main/java/org/apache/sling/scripting/jsp/taglib/helpers/XSSSupport.java
new file mode 100644
index 0000000..f30c0ed
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/jsp/taglib/helpers/XSSSupport.java
@@ -0,0 +1,85 @@
+/*
+ * 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 copyright ownership.
+ * The ASF licenses this file to You under the 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
+ *
+ *      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.scripting.jsp.taglib.helpers;
+
+import org.apache.commons.lang.StringUtils;
+import org.owasp.esapi.ESAPI;
+
+/**
+ * Support for basic XSS protection as provided by the OWASP ESAPI's escape
+ * methods.
+ */
+public class XSSSupport {
+
+	/**
+	 * The encoding modes supported by this tag.
+	 */
+	public static enum ENCODING_MODE {
+		/**
+		 * Encodes the content as HTML
+		 */
+		HTML, HTML_ATTR, XML, XML_ATTR, JS
+	}
+
+	/**
+	 * Encodes the unencoded string using the specified mode. This will be
+	 * deferred to the corresponding OWASP ESAPI encoding method.
+	 * 
+	 * @param unencoded
+	 *            the unencoded string
+	 * @param mode
+	 *            the mode with which to encode the string
+	 * @return the encoded string
+	 */
+	public static String encode(String unencoded, ENCODING_MODE mode) {
+
+		String encoded = null;
+		switch (mode) {
+		case HTML:
+			encoded = ESAPI.encoder().encodeForHTML(unencoded);
+			break;
+		case HTML_ATTR:
+			encoded = ESAPI.encoder().encodeForHTMLAttribute(unencoded);
+			break;
+		case XML:
+			encoded = ESAPI.encoder().encodeForXML(unencoded);
+			break;
+		case XML_ATTR:
+			encoded = ESAPI.encoder().encodeForXMLAttribute(unencoded);
+			break;
+		case JS:
+			encoded = ESAPI.encoder().encodeForJavaScript(unencoded);
+			break;
+		default:
+			break;
+		}
+		return encoded;
+	}
+
+	/**
+	 * Retrieves the encoding mode associated with the specified string. Will
+	 * throw an IllegalArgumentException if the mode string is not a valid mode
+	 * and will throw a NullPointerException if the mode string is null.
+	 * 
+	 * @param modeStr
+	 *            the mode string
+	 * @return the encoding mode
+	 */
+	public static ENCODING_MODE getEncodingMode(String modeStr) {
+		return ENCODING_MODE.valueOf(StringUtils.upperCase(modeStr));
+	}
+}
diff --git a/src/main/resources/ESAPI.properties b/src/main/resources/ESAPI.properties
new file mode 100644
index 0000000..20c9f5d
--- /dev/null
+++ b/src/main/resources/ESAPI.properties
@@ -0,0 +1,452 @@
+#

+# OWASP Enterprise Security API (ESAPI) Properties file -- PRODUCTION Version

+# 

+# This file is part of the Open Web Application Security Project (OWASP)

+# Enterprise Security API (ESAPI) project. For details, please see

+# http://www.owasp.org/index.php/ESAPI.

+#

+# Copyright (c) 2008,2009 - The OWASP Foundation

+#

+# DISCUSS: This may cause a major backwards compatibility issue, etc. but

+#		   from a name space perspective, we probably should have prefaced

+#		   all the property names with ESAPI or at least OWASP. Otherwise

+#		   there could be problems is someone loads this properties file into

+#		   the System properties.  We could also put this file into the

+#		   esapi.jar file (perhaps as a ResourceBundle) and then allow an external

+#		   ESAPI properties be defined that would overwrite these defaults.

+#		   That keeps the application's properties relatively simple as usually

+#		   they will only want to override a few properties. If looks like we

+#		   already support multiple override levels of this in the

+#		   DefaultSecurityConfiguration class, but I'm suggesting placing the

+#		   defaults in the esapi.jar itself. That way, if the jar is signed,

+#		   we could detect if those properties had been tampered with. (The

+#		   code to check the jar signatures is pretty simple... maybe 70-90 LOC,

+#		   but off course there is an execution penalty (similar to the way

+#		   that the separate sunjce.jar used to be when a class from it was

+#		   first loaded). Thoughts?

+###############################################################################

+#

+# WARNING: Operating system protection should be used to lock down the .esapi

+# resources directory and all the files inside and all the directories all the

+# way up to the root directory of the file system.  Note that if you are using

+# file-based implementations, that some files may need to be read-write as they

+# get updated dynamically.

+#

+# Before using, be sure to update the MasterKey and MasterSalt as described below.

+# N.B.: If you had stored data that you have previously encrypted with ESAPI 1.4,

+#		you *must* FIRST decrypt it using ESAPI 1.4 and then (if so desired)

+#		re-encrypt it with ESAPI 2.0. If you fail to do this, you will NOT be

+#		able to decrypt your data with ESAPI 2.0.

+#

+#		YOU HAVE BEEN WARNED!!! More details are in the ESAPI 2.0 Release Notes.

+#

+#===========================================================================

+# ESAPI Configuration

+#

+# If true, then print all the ESAPI properties set here when they are loaded.

+# If false, they are not printed. Useful to reduce output when running JUnit tests.

+# If you need to troubleshoot a properties related problem, turning this on may help.

+# This is 'false' in the src/test/resources/.esapi version. It is 'true' by

+# default for reasons of backward compatibility with earlier ESAPI versions.

+ESAPI.printProperties=true

+

+# ESAPI is designed to be easily extensible. You can use the reference implementation

+# or implement your own providers to take advantage of your enterprise's security

+# infrastructure. The functions in ESAPI are referenced using the ESAPI locator, like:

+#

+#    String ciphertext =

+#		ESAPI.encryptor().encrypt("Secret message");   // Deprecated in 2.0

+#    CipherText cipherText =

+#		ESAPI.encryptor().encrypt(new PlainText("Secret message")); // Preferred

+#

+# Below you can specify the classname for the provider that you wish to use in your

+# application. The only requirement is that it implement the appropriate ESAPI interface.

+# This allows you to switch security implementations in the future without rewriting the

+# entire application.

+#

+# ExperimentalAccessController requires ESAPI-AccessControlPolicy.xml in .esapi directory

+ESAPI.AccessControl=org.owasp.esapi.reference.DefaultAccessController

+# FileBasedAuthenticator requires users.txt file in .esapi directory

+ESAPI.Authenticator=org.owasp.esapi.reference.FileBasedAuthenticator

+ESAPI.Encoder=org.owasp.esapi.reference.DefaultEncoder

+ESAPI.Encryptor=org.owasp.esapi.reference.crypto.JavaEncryptor

+

+ESAPI.Executor=org.owasp.esapi.reference.DefaultExecutor

+ESAPI.HTTPUtilities=org.owasp.esapi.reference.DefaultHTTPUtilities

+ESAPI.IntrusionDetector=org.owasp.esapi.reference.DefaultIntrusionDetector

+# Log4JFactory Requires log4j.xml or log4j.properties in classpath - http://www.laliluna.de/log4j-tutorial.html

+#ESAPI.Logger=org.owasp.esapi.reference.Log4JLogFactory

+ESAPI.Logger=org.owasp.esapi.reference.JavaLogFactory

+ESAPI.Randomizer=org.owasp.esapi.reference.DefaultRandomizer

+ESAPI.Validator=org.owasp.esapi.reference.DefaultValidator

+

+#===========================================================================

+# ESAPI Authenticator

+#

+Authenticator.AllowedLoginAttempts=3

+Authenticator.MaxOldPasswordHashes=13

+Authenticator.UsernameParameterName=username

+Authenticator.PasswordParameterName=password

+# RememberTokenDuration (in days)

+Authenticator.RememberTokenDuration=14

+# Session Timeouts (in minutes)

+Authenticator.IdleTimeoutDuration=20

+Authenticator.AbsoluteTimeoutDuration=120

+

+#===========================================================================

+# ESAPI Encoder

+#

+# ESAPI canonicalizes input before validation to prevent bypassing filters with encoded attacks.

+# Failure to canonicalize input is a very common mistake when implementing validation schemes.

+# Canonicalization is automatic when using the ESAPI Validator, but you can also use the

+# following code to canonicalize data.

+#

+#      ESAPI.Encoder().canonicalize( "%22hello world&#x22;" );

+#  

+# Multiple encoding is when a single encoding format is applied multiple times. Allowing

+# multiple encoding is strongly discouraged.

+Encoder.AllowMultipleEncoding=false

+

+# Mixed encoding is when multiple different encoding formats are applied, or when 

+# multiple formats are nested. Allowing multiple encoding is strongly discouraged.

+Encoder.AllowMixedEncoding=false

+

+# The default list of codecs to apply when canonicalizing untrusted data. The list should include the codecs

+# for all downstream interpreters or decoders. For example, if the data is likely to end up in a URL, HTML, or

+# inside JavaScript, then the list of codecs below is appropriate. The order of the list is not terribly important.

+Encoder.DefaultCodecList=HTMLEntityCodec,XMLEntityCodec,PercentCodec,JavaScriptCodec

+

+

+#===========================================================================

+# ESAPI Encryption

+#

+# The ESAPI Encryptor provides basic cryptographic functions with a simplified API.

+# To get started, generate a new key using java -classpath esapi.jar org.owasp.esapi.reference.crypto.JavaEncryptor

+# There is not currently any support for key rotation, so be careful when changing your key and salt as it

+# will invalidate all signed, encrypted, and hashed data.

+#

+# WARNING: Not all combinations of algorithms and key lengths are supported.

+# If you choose to use a key length greater than 128, you MUST download the

+# unlimited strength policy files and install in the lib directory of your JRE/JDK.

+# See http://java.sun.com/javase/downloads/index.jsp for more information.

+#

+# Backward compatibility with ESAPI Java 1.4 is supported by the two deprecated API

+# methods, Encryptor.encrypt(String) and Encryptor.decrypt(String). However, whenever

+# possible, these methods should be avoided as they use ECB cipher mode, which in almost

+# all circumstances a poor choice because of it's weakness. CBC cipher mode is the default

+# for the new Encryptor encrypt / decrypt methods for ESAPI Java 2.0.  In general, you

+# should only use this compatibility setting if you have persistent data encrypted with

+# version 1.4 and even then, you should ONLY set this compatibility mode UNTIL

+# you have decrypted all of your old encrypted data and then re-encrypted it with

+# ESAPI 2.0 using CBC mode. If you have some reason to mix the deprecated 1.4 mode

+# with the new 2.0 methods, make sure that you use the same cipher algorithm for both

+# (256-bit AES was the default for 1.4; 128-bit is the default for 2.0; see below for

+# more details.) Otherwise, you will have to use the new 2.0 encrypt / decrypt methods

+# where you can specify a SecretKey. (Note that if you are using the 256-bit AES,

+# that requires downloading the special jurisdiction policy files mentioned above.)

+#

+#		***** IMPORTANT: Do NOT forget to replace these with your own values! *****

+# To calculate these values, you can run:

+#		java -classpath esapi.jar org.owasp.esapi.reference.crypto.JavaEncryptor

+#

+#Encryptor.MasterKey=

+#Encryptor.MasterSalt=

+

+# Provides the default JCE provider that ESAPI will "prefer" for its symmetric

+# encryption and hashing. (That is it will look to this provider first, but it

+# will defer to other providers if the requested algorithm is not implemented

+# by this provider.) If left unset, ESAPI will just use your Java VM's current

+# preferred JCE provider, which is generally set in the file

+# "$JAVA_HOME/jre/lib/security/java.security".

+#

+# The main intent of this is to allow ESAPI symmetric encryption to be

+# used with a FIPS 140-2 compliant crypto-module. For details, see the section

+# "Using ESAPI Symmetric Encryption with FIPS 140-2 Cryptographic Modules" in

+# the ESAPI 2.0 Symmetric Encryption User Guide, at:

+# http://owasp-esapi-java.googlecode.com/svn/trunk/documentation/esapi4java-core-2.0-symmetric-crypto-user-guide.html

+# However, this property also allows you to easily use an alternate JCE provider

+# such as "Bouncy Castle" without having to make changes to "java.security".

+# See Javadoc for SecurityProviderLoader for further details. If you wish to use

+# a provider that is not known to SecurityProviderLoader, you may specify the

+# fully-qualified class name of the JCE provider class that implements

+# java.security.Provider. If the name contains a '.', this is interpreted as

+# a fully-qualified class name that implements java.security.Provider.

+#

+# NOTE: Setting this property has the side-effect of changing it in your application

+#       as well, so if you are using JCE in your application directly rather than

+#       through ESAPI (you wouldn't do that, would you? ;-), it will change the

+#       preferred JCE provider there as well.

+#

+# Default: Keeps the JCE provider set to whatever JVM sets it to.

+Encryptor.PreferredJCEProvider=

+

+# AES is the most widely used and strongest encryption algorithm. This

+# should agree with your Encryptor.CipherTransformation property.

+# By default, ESAPI Java 1.4 uses "PBEWithMD5AndDES" and which is

+# very weak. It is essentially a password-based encryption key, hashed

+# with MD5 around 1K times and then encrypted with the weak DES algorithm

+# (56-bits) using ECB mode and an unspecified padding (it is

+# JCE provider specific, but most likely "NoPadding"). However, 2.0 uses

+# "AES/CBC/PKCSPadding". If you want to change these, change them here.

+# Warning: This property does not control the default reference implementation for

+#		   ESAPI 2.0 using JavaEncryptor. Also, this property will be dropped

+#		   in the future.

+# @deprecated

+Encryptor.EncryptionAlgorithm=AES

+#		For ESAPI Java 2.0 - New encrypt / decrypt methods use this.

+Encryptor.CipherTransformation=AES/CBC/PKCS5Padding

+

+# Applies to ESAPI 2.0 and later only!

+# Comma-separated list of cipher modes that provide *BOTH*

+# confidentiality *AND* message authenticity. (NIST refers to such cipher

+# modes as "combined modes" so that's what we shall call them.) If any of these

+# cipher modes are used then no MAC is calculated and stored

+# in the CipherText upon encryption. Likewise, if one of these

+# cipher modes is used with decryption, no attempt will be made

+# to validate the MAC contained in the CipherText object regardless

+# of whether it contains one or not. Since the expectation is that

+# these cipher modes support support message authenticity already,

+# injecting a MAC in the CipherText object would be at best redundant.

+#

+# Note that as of JDK 1.5, the SunJCE provider does not support *any*

+# of these cipher modes. Of these listed, only GCM and CCM are currently

+# NIST approved. YMMV for other JCE providers. E.g., Bouncy Castle supports

+# GCM and CCM with "NoPadding" mode, but not with "PKCS5Padding" or other

+# padding modes.

+Encryptor.cipher_modes.combined_modes=GCM,CCM,IAPM,EAX,OCB,CWC

+

+# Applies to ESAPI 2.0 and later only!

+# Additional cipher modes allowed for ESAPI 2.0 encryption. These

+# cipher modes are in _addition_ to those specified by the property

+# 'Encryptor.cipher_modes.combined_modes'.

+# Note: We will add support for streaming modes like CFB & OFB once

+# we add support for 'specified' to the property 'Encryptor.ChooseIVMethod'

+# (probably in ESAPI 2.1).

+# DISCUSS: Better name?

+Encryptor.cipher_modes.additional_allowed=CBC

+

+# 128-bit is almost always sufficient and appears to be more resistant to

+# related key attacks than is 256-bit AES. Use '_' to use default key size

+# for cipher algorithms (where it makes sense because the algorithm supports

+# a variable key size). Key length must agree to what's provided as the

+# cipher transformation, otherwise this will be ignored after logging a

+# warning.

+#

+# NOTE: This is what applies BOTH ESAPI 1.4 and 2.0. See warning above about mixing!

+Encryptor.EncryptionKeyLength=128

+

+# Because 2.0 uses CBC mode by default, it requires an initialization vector (IV).

+# (All cipher modes except ECB require an IV.) There are two choices: we can either

+# use a fixed IV known to both parties or allow ESAPI to choose a random IV. While

+# the IV does not need to be hidden from adversaries, it is important that the

+# adversary not be allowed to choose it. Also, random IVs are generally much more

+# secure than fixed IVs. (In fact, it is essential that feed-back cipher modes

+# such as CFB and OFB use a different IV for each encryption with a given key so

+# in such cases, random IVs are much preferred. By default, ESAPI 2.0 uses random

+# IVs. If you wish to use 'fixed' IVs, set 'Encryptor.ChooseIVMethod=fixed' and

+# uncomment the Encryptor.fixedIV.

+#

+# Valid values:		random|fixed|specified		'specified' not yet implemented; planned for 2.1

+Encryptor.ChooseIVMethod=random

+# If you choose to use a fixed IV, then you must place a fixed IV here that

+# is known to all others who are sharing your secret key. The format should

+# be a hex string that is the same length as the cipher block size for the

+# cipher algorithm that you are using. The following is an *example* for AES

+# from an AES test vector for AES-128/CBC as described in:

+# NIST Special Publication 800-38A (2001 Edition)

+# "Recommendation for Block Cipher Modes of Operation".

+# (Note that the block size for AES is 16 bytes == 128 bits.)

+#

+Encryptor.fixedIV=0x000102030405060708090a0b0c0d0e0f

+

+# Whether or not CipherText should use a message authentication code (MAC) with it.

+# This prevents an adversary from altering the IV as well as allowing a more

+# fool-proof way of determining the decryption failed because of an incorrect

+# key being supplied. This refers to the "separate" MAC calculated and stored

+# in CipherText, not part of any MAC that is calculated as a result of a

+# "combined mode" cipher mode.

+#

+# If you are using ESAPI with a FIPS 140-2 cryptographic module, you *must* also

+# set this property to false.

+Encryptor.CipherText.useMAC=true

+

+# Whether or not the PlainText object may be overwritten and then marked

+# eligible for garbage collection. If not set, this is still treated as 'true'.

+Encryptor.PlainText.overwrite=true

+

+# Do not use DES except in a legacy situations. 56-bit is way too small key size.

+#Encryptor.EncryptionKeyLength=56

+#Encryptor.EncryptionAlgorithm=DES

+

+# TripleDES is considered strong enough for most purposes.

+#	Note:	There is also a 112-bit version of DESede. Using the 168-bit version

+#			requires downloading the special jurisdiction policy from Sun.

+#Encryptor.EncryptionKeyLength=168

+#Encryptor.EncryptionAlgorithm=DESede

+

+Encryptor.HashAlgorithm=SHA-512

+Encryptor.HashIterations=1024

+Encryptor.DigitalSignatureAlgorithm=SHA1withDSA

+Encryptor.DigitalSignatureKeyLength=1024

+Encryptor.RandomAlgorithm=SHA1PRNG

+Encryptor.CharacterEncoding=UTF-8

+

+# This is the Pseudo Random Function (PRF) that ESAPI's Key Derivation Function

+# (KDF) normally uses. Note this is *only* the PRF used for ESAPI's KDF and

+# *not* what is used for ESAPI's MAC. (Currently, HmacSHA1 is always used for

+# the MAC, mostly to keep the overall size at a minimum.)

+#

+# Currently supported choices for JDK 1.5 and 1.6 are:

+#	HmacSHA1 (160 bits), HmacSHA256 (256 bits), HmacSHA384 (384 bits), and

+#	HmacSHA512 (512 bits).

+# Note that HmacMD5 is *not* supported for the PRF used by the KDF even though

+# the JDKs support it.  See the ESAPI 2.0 Symmetric Encryption User Guide

+# further details.

+Encryptor.KDF.PRF=HmacSHA256

+#===========================================================================

+# ESAPI HttpUtilties

+#

+# The HttpUtilities provide basic protections to HTTP requests and responses. Primarily these methods 

+# protect against malicious data from attackers, such as unprintable characters, escaped characters,

+# and other simple attacks. The HttpUtilities also provides utility methods for dealing with cookies,

+# headers, and CSRF tokens.

+#

+# Default file upload location (remember to escape backslashes with \\)

+HttpUtilities.UploadDir=C:\\ESAPI\\testUpload

+HttpUtilities.UploadTempDir=C:\\temp

+# Force flags on cookies, if you use HttpUtilities to set cookies

+HttpUtilities.ForceHttpOnlySession=false

+HttpUtilities.ForceSecureSession=false

+HttpUtilities.ForceHttpOnlyCookies=true

+HttpUtilities.ForceSecureCookies=true

+# Maximum size of HTTP headers

+HttpUtilities.MaxHeaderSize=4096

+# File upload configuration

+HttpUtilities.ApprovedUploadExtensions=.zip,.pdf,.doc,.docx,.ppt,.pptx,.tar,.gz,.tgz,.rar,.war,.jar,.ear,.xls,.rtf,.properties,.java,.class,.txt,.xml,.jsp,.jsf,.exe,.dll

+HttpUtilities.MaxUploadFileBytes=500000000

+# Using UTF-8 throughout your stack is highly recommended. That includes your database driver,

+# container, and any other technologies you may be using. Failure to do this may expose you

+# to Unicode transcoding injection attacks. Use of UTF-8 does not hinder internationalization.

+HttpUtilities.ResponseContentType=text/html; charset=UTF-8

+# This is the name of the cookie used to represent the HTTP session

+# Typically this will be the default "JSESSIONID" 

+HttpUtilities.HttpSessionIdName=JSESSIONID

+

+

+

+#===========================================================================

+# ESAPI Executor

+# CHECKME - Not sure what this is used for, but surely it should be made OS independent.

+Executor.WorkingDirectory=C:\\Windows\\Temp

+Executor.ApprovedExecutables=C:\\Windows\\System32\\cmd.exe,C:\\Windows\\System32\\runas.exe

+

+

+#===========================================================================

+# ESAPI Logging

+# Set the application name if these logs are combined with other applications

+Logger.ApplicationName=ExampleApplication

+# If you use an HTML log viewer that does not properly HTML escape log data, you can set LogEncodingRequired to true

+Logger.LogEncodingRequired=false

+# Determines whether ESAPI should log the application name. This might be clutter in some single-server/single-app environments.

+Logger.LogApplicationName=true

+# Determines whether ESAPI should log the server IP and port. This might be clutter in some single-server environments.

+Logger.LogServerIP=true

+# LogFileName, the name of the logging file. Provide a full directory path (e.g., C:\\ESAPI\\ESAPI_logging_file) if you

+# want to place it in a specific directory.

+Logger.LogFileName=ESAPI_logging_file

+# MaxLogFileSize, the max size (in bytes) of a single log file before it cuts over to a new one (default is 10,000,000)

+Logger.MaxLogFileSize=10000000

+

+

+#===========================================================================

+# ESAPI Intrusion Detection

+#

+# Each event has a base to which .count, .interval, and .action are added

+# The IntrusionException will fire if we receive "count" events within "interval" seconds

+# The IntrusionDetector is configurable to take the following actions: log, logout, and disable

+#  (multiple actions separated by commas are allowed e.g. event.test.actions=log,disable

+#

+# Custom Events

+# Names must start with "event." as the base

+# Use IntrusionDetector.addEvent( "test" ) in your code to trigger "event.test" here

+# You can also disable intrusion detection completely by changing

+# the following parameter to true

+#

+IntrusionDetector.Disable=false

+#

+IntrusionDetector.event.test.count=2

+IntrusionDetector.event.test.interval=10

+IntrusionDetector.event.test.actions=disable,log

+

+# Exception Events

+# All EnterpriseSecurityExceptions are registered automatically

+# Call IntrusionDetector.getInstance().addException(e) for Exceptions that do not extend EnterpriseSecurityException

+# Use the fully qualified classname of the exception as the base

+

+# any intrusion is an attack

+IntrusionDetector.org.owasp.esapi.errors.IntrusionException.count=1

+IntrusionDetector.org.owasp.esapi.errors.IntrusionException.interval=1

+IntrusionDetector.org.owasp.esapi.errors.IntrusionException.actions=log,disable,logout

+

+# for test purposes

+# CHECKME: Shouldn't there be something in the property name itself that designates

+#		   that these are for testing???

+IntrusionDetector.org.owasp.esapi.errors.IntegrityException.count=10

+IntrusionDetector.org.owasp.esapi.errors.IntegrityException.interval=5

+IntrusionDetector.org.owasp.esapi.errors.IntegrityException.actions=log,disable,logout

+

+# rapid validation errors indicate scans or attacks in progress

+# org.owasp.esapi.errors.ValidationException.count=10

+# org.owasp.esapi.errors.ValidationException.interval=10

+# org.owasp.esapi.errors.ValidationException.actions=log,logout

+

+# sessions jumping between hosts indicates session hijacking

+IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.count=2

+IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.interval=10

+IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.actions=log,logout

+

+

+#===========================================================================

+# ESAPI Validation

+#

+# The ESAPI Validator works on regular expressions with defined names. You can define names

+# either here, or you may define application specific patterns in a separate file defined below.

+# This allows enterprises to specify both organizational standards as well as application specific

+# validation rules.

+#

+Validator.ConfigurationFile=validation.properties

+

+# Validators used by ESAPI

+Validator.AccountName=^[a-zA-Z0-9]{3,20}$

+Validator.SystemCommand=^[a-zA-Z\\-\\/]{1,64}$

+Validator.RoleName=^[a-z]{1,20}$

+

+#the word TEST below should be changed to your application 

+#name - only relative URL's are supported

+Validator.Redirect=^\\/test.*$

+

+# Global HTTP Validation Rules

+# Values with Base64 encoded data (e.g. encrypted state) will need at least [a-zA-Z0-9\/+=]

+Validator.HTTPScheme=^(http|https)$

+Validator.HTTPServerName=^[a-zA-Z0-9_.\\-]*$

+Validator.HTTPParameterName=^[a-zA-Z0-9_]{1,32}$

+Validator.HTTPParameterValue=^[a-zA-Z0-9.\\-\\/+=@_ ]*$

+Validator.HTTPCookieName=^[a-zA-Z0-9\\-_]{1,32}$

+Validator.HTTPCookieValue=^[a-zA-Z0-9\\-\\/+=_ ]*$

+Validator.HTTPHeaderName=^[a-zA-Z0-9\\-_]{1,32}$

+Validator.HTTPHeaderValue=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$

+Validator.HTTPContextPath=^\\/?[a-zA-Z0-9.\\-\\/_]*$

+Validator.HTTPServletPath=^[a-zA-Z0-9.\\-\\/_]*$

+Validator.HTTPPath=^[a-zA-Z0-9.\\-_]*$

+Validator.HTTPQueryString=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ %]*$

+Validator.HTTPURI=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$

+Validator.HTTPURL=^.*$

+Validator.HTTPJSESSIONID=^[A-Z0-9]{10,30}$

+

+# Validation of file related input

+Validator.FileName=^[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$

+Validator.DirectoryName=^[a-zA-Z0-9:/\\\\!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$

+

+# Validation of dates. Controls whether or not 'lenient' dates are accepted.

+# See DataFormat.setLenient(boolean flag) for further details.

+Validator.AcceptLenientDates=false
\ No newline at end of file
diff --git a/src/main/resources/META-INF/sling.tld b/src/main/resources/META-INF/sling.tld
index 82ce0be..1ff7935 100644
--- a/src/main/resources/META-INF/sling.tld
+++ b/src/main/resources/META-INF/sling.tld
@@ -24,6 +24,12 @@
 		<function-signature>java.lang.Object adaptTo(org.apache.sling.api.adapter.Adaptable, java.lang.String)</function-signature>
 	</function>
 
+    <function>
+        <name>encode</name>
+        <function-class>org.apache.sling.scripting.jsp.taglib.SlingFunctions</function-class>
+        <function-signature>java.lang.String encode(java.lang.String, java.lang.String)</function-signature>
+    </function>
+
 	<function>
 		<name>findResources</name>
 		<function-class>org.apache.sling.scripting.jsp.taglib.SlingFunctions</function-class>
@@ -436,6 +442,44 @@
 		</attribute>
 	</tag>
 
+    <tag>
+        <description>
+            Writes properly XSS encoded text to the response using the 
+            OWASP ESAPI for supporting a number of encoding modes.
+        </description>
+        <name>encode</name>
+        <tag-class>
+            org.apache.sling.scripting.jsp.taglib.EncodeTag
+        </tag-class>
+        <body-content>JSP</body-content>
+        <attribute>
+            <description>
+                The value to encode.
+            </description>
+            <name>value</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <description>
+                The default value to be used if the value is either 
+                null or an empty string.
+            </description>
+            <name>default</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <description>
+                The mode to use for encoding.  Must be one of the 
+                valid modes found in XSSSupport.ENCODING_MODE.
+            </description>
+            <name>mode</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+    </tag>
+
 	<tag>
 		<description>
 			Tag for searching for resources using the given query
diff --git a/src/main/resources/validation.properties b/src/main/resources/validation.properties
new file mode 100644
index 0000000..18e037f
--- /dev/null
+++ b/src/main/resources/validation.properties
@@ -0,0 +1,29 @@
+# The ESAPI validator does many security checks on input, such as canonicalization

+# and whitelist validation. Note that all of these validation rules are applied *after*

+# canonicalization. Double-encoded characters (even with different encodings involved,

+# are never allowed.

+#

+# To use:

+#

+# First set up a pattern below. You can choose any name you want, prefixed by the word

+# "Validation." For example:

+#   Validation.Email=^[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+\\.[a-zA-Z]{2,4}$

+# 

+# Then you can validate in your code against the pattern like this:

+#     ESAPI.validator().isValidInput("User Email", input, "Email", maxLength, allowNull);

+# Where maxLength and allowNull are set for you needs, respectively.

+#

+# But note, when you use boolean variants of validation functions, you lose critical 

+# canonicalization. It is preferable to use the "get" methods (which throw exceptions) and 

+# and use the returned user input which is in canonical form. Consider the following:

+#  

+# try {

+#    someObject.setEmail(ESAPI.validator().getValidInput("User Email", input, "Email", maxLength, allowNull));

+#

+Validator.SafeString=^[.\\p{Alnum}\\p{Space}]{0,1024}$

+Validator.Email=^[A-Za-z0-9._%'-]+@[A-Za-z0-9.-]+\\.[a-zA-Z]{2,4}$

+Validator.IPAddress=^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$

+Validator.URL=^(ht|f)tp(s?)\\:\\/\\/[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z])*(:(0-9)*)*(\\/?)([a-zA-Z0-9\\-\\.\\?\\,\\:\\'\\/\\\\\\+=&amp;%\\$#_]*)?$

+Validator.CreditCard=^(\\d{4}[- ]?){3}\\d{4}$

+Validator.SSN=^(?!000)([0-6]\\d{2}|7([0-6]\\d|7[012]))([ -]?)(?!00)\\d\\d\\3(?!0000)\\d{4}$

+

diff --git a/src/test/java/org/apache/sling/scripting/jsp/taglib/TestEncodeTag.java b/src/test/java/org/apache/sling/scripting/jsp/taglib/TestEncodeTag.java
new file mode 100644
index 0000000..a418fb7
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/jsp/taglib/TestEncodeTag.java
@@ -0,0 +1,167 @@
+/*
+ * 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 copyright ownership.
+ * The ASF licenses this file to You under the 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
+ *
+ *      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.scripting.jsp.taglib;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspWriter;
+
+import org.apache.sling.api.resource.ValueMap;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Unit Tests for the Class EscapeTag.
+ * 
+ * @see org.apache.sling.scripting.jsp.taglib.EscapeTag
+ */
+public class TestEncodeTag {
+
+	private static final Logger log = LoggerFactory
+			.getLogger(TestEncodeTag.class);
+	private EncodeTag encodeTag;
+	private MockPageContext pageContext;
+	private StringBuilder sb;
+	private static final String VAR_KEY = "properties";
+
+	/**
+	 * Initializes the fields for this test.
+	 */
+	@SuppressWarnings("serial")
+	@Before
+	public void init() {
+		log.info("init");
+		encodeTag = new EncodeTag();
+
+		sb = new StringBuilder();
+		final JspWriter w = new JspWriter(0, false){
+			public void newLine() throws IOException {
+				throw new UnsupportedOperationException();
+			}
+			public void print(boolean paramBoolean) throws IOException {
+				sb.append(paramBoolean);
+			}
+			public void print(char paramChar) throws IOException {
+				sb.append(paramChar);
+			}
+			public void print(int paramInt) throws IOException {
+				sb.append(paramInt);
+			}
+			public void print(long paramLong) throws IOException {
+				sb.append(paramLong);
+			}
+			public void print(float paramFloat) throws IOException {
+				sb.append(paramFloat);
+			}
+			public void print(double paramDouble) throws IOException {
+				sb.append(paramDouble);
+			}
+			public void print(char[] paramArrayOfChar) throws IOException {
+				sb.append(paramArrayOfChar);
+			}
+			public void print(String paramString) throws IOException {
+				sb.append(paramString);
+			}
+			public void print(Object paramObject) throws IOException {
+				sb.append(paramObject);
+			}
+			public void println() throws IOException {
+				throw new UnsupportedOperationException();
+			}
+			public void println(boolean paramBoolean) throws IOException {
+				throw new UnsupportedOperationException();
+			}
+			public void println(char paramChar) throws IOException {
+				throw new UnsupportedOperationException();
+			}
+			public void println(int paramInt) throws IOException {
+				throw new UnsupportedOperationException();
+			}
+			public void println(long paramLong) throws IOException {
+				throw new UnsupportedOperationException();
+			}
+			public void println(float paramFloat) throws IOException {
+				throw new UnsupportedOperationException();
+			}
+			public void println(double paramDouble) throws IOException {
+				throw new UnsupportedOperationException();
+			}
+			public void println(char[] paramArrayOfChar) throws IOException {
+				throw new UnsupportedOperationException();
+			}
+			public void println(String paramString) throws IOException {
+				throw new UnsupportedOperationException();
+			}
+			public void println(Object paramObject) throws IOException {
+				throw new UnsupportedOperationException();
+			}
+			public void clear() throws IOException {
+				throw new UnsupportedOperationException();
+			}
+			public void clearBuffer() throws IOException {
+				throw new UnsupportedOperationException();
+			}
+			public void flush() throws IOException {
+				throw new UnsupportedOperationException();
+			}
+			public void close() throws IOException {
+				throw new UnsupportedOperationException();
+			}
+			public int getRemaining() {
+				throw new UnsupportedOperationException();
+			}
+			public void write(char[] cbuf, int off, int len) throws IOException {
+				sb.append(cbuf);
+			}
+			
+		};
+		pageContext = new MockPageContext(){
+			public JspWriter getOut() {
+				return w;
+			}
+		};
+		encodeTag.setPageContext(pageContext);
+		log.info("init Complete");
+	}
+
+	/**
+	 * Tests the adapt object Tag functionality.
+	 * @throws JspException 
+	 */
+	@Test
+	public void testEncode() throws JspException {
+		log.info("testAdaptObject");
+
+		log.info("Setting up tests");
+		encodeTag.setValue("&amp;Hello World!");
+		encodeTag.setMode("html");
+		encodeTag.doStartTag();
+		encodeTag.doEndTag();
+
+		log.info("Checking result");
+		assertEquals("&amp;amp&#x3b;Hello World&#x21;",sb.toString().trim());
+
+		log.info("Test successful!");
+	}
+}
diff --git a/src/test/java/org/apache/sling/scripting/jsp/taglib/TestSlingFunctions.java b/src/test/java/org/apache/sling/scripting/jsp/taglib/TestSlingFunctions.java
index ab27229..6c6fe56 100644
--- a/src/test/java/org/apache/sling/scripting/jsp/taglib/TestSlingFunctions.java
+++ b/src/test/java/org/apache/sling/scripting/jsp/taglib/TestSlingFunctions.java
@@ -18,7 +18,9 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.util.ArrayList;
 import java.util.Date;
@@ -85,6 +87,41 @@
 	}
 
 	@Test
+	public void testEscape() {
+		log.info("testEncode");
+
+		log.info("Testing HTML Escaping");
+		assertEquals("&amp;nbsp&#x3b;Here is some text&#x21;",
+				SlingFunctions.escape("&nbsp;Here is some text!", "HTML"));
+
+		log.info("Testing HTML Attr Escaping");
+		assertEquals(
+				"&amp;nbsp&#x3b;Here&#x20;is&#x20;some&#x20;text&#x21;&quot;",
+				SlingFunctions
+						.escape("&nbsp;Here is some text!\"", "HTML_ATTR"));
+
+		log.info("Testing invalid values");
+		try {
+			SlingFunctions.escape(null, null);
+			fail("Expected null pointer exception");
+		} catch (NullPointerException npe) {
+			log.info("Encountered expected exception");
+		}
+		try {
+			SlingFunctions.escape(null, "Invalid");
+			fail("Expected invalid argument exception");
+		} catch (IllegalArgumentException iae) {
+			log.info("Encountered expected exception");
+		}
+
+		log.info("Testing null/empty values");
+		assertNull(SlingFunctions.escape(null, "html"));
+		assertEquals("", SlingFunctions.escape("", "html"));
+
+		log.info("Tests successful!");
+	}
+
+	@Test
 	public void testGetResource() {
 		log.info("testGetResource");
 		Resource resource = SlingFunctions.getResource(resolver, TEST_PATH);
@@ -132,33 +169,37 @@
 	}
 
 	@Test
-	public void testGetValue(){
+	public void testGetValue() {
 		log.info("testGetValue");
 		Resource resource = SlingFunctions.getResource(resolver, TEST_PATH);
 		ValueMap properties = resource.adaptTo(ValueMap.class);
-		
+
 		log.info("Testing using class coersion");
-		Date retrievedDate = SlingFunctions.getValue(properties, "date", Date.class);
-		assertEquals(date,retrievedDate);
+		Date retrievedDate = SlingFunctions.getValue(properties, "date",
+				Date.class);
+		assertEquals(date, retrievedDate);
 		assertTrue(retrievedDate instanceof Date);
-		
+
 		log.info("Testing with default value on existing key");
-		Long retrievedLong = SlingFunctions.getValue(properties, "long", new Long(-123L));
-		assertEquals(new Long(0L),retrievedLong);
+		Long retrievedLong = SlingFunctions.getValue(properties, "long",
+				new Long(-123L));
+		assertEquals(new Long(0L), retrievedLong);
 		assertTrue(retrievedLong instanceof Long);
-		
+
 		log.info("Testing with no value and class coersion");
-		Date fakeDate = SlingFunctions.getValue(properties, "date1", Date.class);
+		Date fakeDate = SlingFunctions
+				.getValue(properties, "date1", Date.class);
 		assertTrue(fakeDate == null);
-		
+
 		log.info("Testing with no value and default specified");
-		Long fakeLong = SlingFunctions.getValue(properties, "long1", new Long(-123L));
-		assertEquals(new Long(-123L),fakeLong);
+		Long fakeLong = SlingFunctions.getValue(properties, "long1", new Long(
+				-123L));
+		assertEquals(new Long(-123L), fakeLong);
 		assertTrue(fakeLong instanceof Long);
 
 		log.info("Tests successful!");
 	}
-	
+
 	@Test
 	public void testListChildResources() {
 		log.info("testListChildResources");