package net.i2p.servlet.filters; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; //import org.owasp.esapi.ESAPI; import net.i2p.I2PAppContext; import net.i2p.util.Log; /** * @since 0.9.14 */ public class XSSRequestWrapper extends HttpServletRequestWrapper { // Adapted from https://owasp-esapi-java.googlecode.com/svn/trunk/configuration/esapi/ESAPI.properties private static final Pattern parameterValuePattern = Pattern.compile("^[\\p{L}\\p{Nd}.,:\\-\\/+=~\\[\\]?@_ \r\n]*$"); private static final Pattern headerValuePattern = Pattern.compile("^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$"); private static final String NOFILTER = "nofilter_"; public XSSRequestWrapper(HttpServletRequest servletRequest) { super(servletRequest); } /** * Parameter names starting with "nofilter_" will not be filtered. */ @Override public String[] getParameterValues(String parameter) { String[] values = super.getParameterValues(parameter); if (parameter.startsWith(NOFILTER)) return values; if (values == null) { return null; } int count = values.length; String[] encodedValues = new String[count]; int good = 0; for (int i = 0; i < count; i++) { String value = values[i]; String v2 = stripXSS(value, parameterValuePattern); if (v2 != null) { encodedValues[good++] = v2; } else if (value != null) { Log log = I2PAppContext.getGlobalContext().logManager().getLog(XSSRequestWrapper.class); log.logAlways(Log.WARN, "URL \"" + getServletPath() + "\" Stripped param \"" + parameter + "\" : \"" + value + '"'); } } if (good <= 0) return null; if (good < count) { // shrink array String[] rv = new String[good]; System.arraycopy(encodedValues, 0, rv, 0, good); encodedValues = rv; } return encodedValues; } /** * Parameter names starting with "nofilter_" will not be filtered. */ @Override public String getParameter(String parameter) { String value = super.getParameter(parameter); if (parameter.startsWith(NOFILTER)) return value; String rv = stripXSS(value, parameterValuePattern); if (value != null && rv == null) { Log log = I2PAppContext.getGlobalContext().logManager().getLog(XSSRequestWrapper.class); log.logAlways(Log.WARN, "URL \"" + getServletPath() + "\" Stripped param \"" + parameter + "\" : \"" + value + '"'); } return rv; } /** * Parameter names starting with "nofilter_" will not be filtered. */ @Override public Map<String, String[]> getParameterMap() { Map<String, String[]> rv = new HashMap<String, String[]>(); for (Enumeration<String> keys = getParameterNames(); keys.hasMoreElements(); ) { String k = keys.nextElement(); String[] v = getParameterValues(k); if (v != null) rv.put(k, v); } return Collections.unmodifiableMap(rv); } @Override public String getHeader(String name) { String value = super.getHeader(name); String rv = stripXSS(value, headerValuePattern); if (value != null && rv == null) { Log log = I2PAppContext.getGlobalContext().logManager().getLog(XSSRequestWrapper.class); log.logAlways(Log.WARN, "URL \"" + getServletPath() + "\" Stripped header \"" + name + "\" : \"" + value + '"'); } return rv; } private static String stripXSS(String value, Pattern whitelistPattern) { if (value != null) { // NOTE: It's highly recommended to use the ESAPI library and uncomment the following line to // avoid encoded attacks. //value = ESAPI.encoder().canonicalize(value); // Remove bad parameters entirely. // NOTE: This doesn't consider whether null is acceptable. if (!whitelistPattern.matcher(value).matches()) { value = null; } } return value; } }