跨站脚本攻击过滤器
This commit is contained in:
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
.gradle
|
||||
build/
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||
!gradle-wrapper.jar
|
||||
|
||||
# Cache of project
|
||||
.gradletasknamecache
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
/bin/
|
||||
|
||||
122
build.gradle
Normal file
122
build.gradle
Normal file
@@ -0,0 +1,122 @@
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
group="com.ai.opt.xss.filter"
|
||||
|
||||
ext{
|
||||
builtBy="gradle 2.2"
|
||||
publishUserName = "runnerdev"
|
||||
publishUserPassword = "runnerdev"
|
||||
// publishURL="http://223.202.119.155:18081/nexus/content/repositories/thirdparty/"
|
||||
//测试分支与生产分支发布URL
|
||||
publishURL = "http://10.1.228.199:18081/nexus/content/repositories/snapshots/"
|
||||
//jar包的版本信息
|
||||
appVersion="1.0-SNAPSHOT"
|
||||
appName="opt-xss-firewall"
|
||||
}
|
||||
|
||||
sourceCompatibility = 1.7
|
||||
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java { srcDirs = ['src/main/java']}
|
||||
resources {
|
||||
srcDirs = ['src/main/resources']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
repositories {
|
||||
maven{ url "http://10.1.228.199:18081/nexus/content/groups/public/"}
|
||||
}
|
||||
|
||||
|
||||
jar{
|
||||
baseName appName
|
||||
version appVersion
|
||||
manifest {
|
||||
attributes 'packageName': appName, 'Built-By': builtBy,'create-date': new Date().format('yyyy-MM-dd HH:mm:ss')
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {archives jar}
|
||||
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
publishing.publications.create("appJarPublish", MavenPublication) {
|
||||
groupId group
|
||||
artifactId appName
|
||||
version appVersion
|
||||
artifact jar
|
||||
pom.withXml {
|
||||
asNode().children().last() + {
|
||||
delegate.dependencies {
|
||||
delegate.dependency {
|
||||
delegate.groupId("org.slf4j")
|
||||
delegate.artifactId("slf4j-api")
|
||||
delegate.version("1.7.12")
|
||||
}
|
||||
delegate.dependency {
|
||||
delegate.groupId("commons-logging")
|
||||
delegate.artifactId("commons-logging")
|
||||
delegate.version("1.2")
|
||||
}
|
||||
delegate.dependency {
|
||||
delegate.groupId("commons-lang")
|
||||
delegate.artifactId("commons-lang")
|
||||
delegate.version("2.6")
|
||||
}
|
||||
delegate.dependency {
|
||||
delegate.groupId("javax.servlet.jsp")
|
||||
delegate.artifactId("javax.servlet.jsp-api")
|
||||
delegate.version("2.3.1")
|
||||
}
|
||||
delegate.dependency {
|
||||
delegate.groupId("javax.servlet")
|
||||
delegate.artifactId("javax.servlet-api")
|
||||
delegate.version("3.1.0")
|
||||
}
|
||||
|
||||
delegate.dependency {
|
||||
delegate.groupId("org.owasp.antisamy")
|
||||
delegate.artifactId("antisamy")
|
||||
delegate.version("1.5.3")
|
||||
}
|
||||
delegate.dependency {
|
||||
delegate.groupId("com.alibaba")
|
||||
delegate.artifactId("fastjson")
|
||||
delegate.version("1.2.6")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
url publishURL
|
||||
credentials {
|
||||
username = publishUserName
|
||||
password = publishUserPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.12'
|
||||
compile 'commons-logging:commons-logging:1.2'
|
||||
compile 'commons-lang:commons-lang:2.6'
|
||||
compile "javax.servlet:javax.servlet-api:3.1.0"
|
||||
compile "javax.servlet.jsp:javax.servlet.jsp-api:2.3.1"
|
||||
compile 'org.owasp.antisamy:antisamy:1.5.3'
|
||||
compile 'com.alibaba:fastjson:1.2.6'
|
||||
}
|
||||
|
||||
|
||||
|
||||
105
src/main/java/com/ai/net/xss/filter/XSSFilter.java
Normal file
105
src/main/java/com/ai/net/xss/filter/XSSFilter.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package com.ai.net.xss.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.ai.net.xss.util.CollectionUtil;
|
||||
import com.ai.net.xss.util.StringUtil;
|
||||
import com.ai.net.xss.wrapper.XssRequestWrapper;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
public class XSSFilter implements Filter {
|
||||
|
||||
private static Logger log=LoggerFactory.getLogger(XSSFilter.class);
|
||||
|
||||
private static final String IGNORE_PATH="ignorePath"; //可放行的请求路径
|
||||
private static final String IGNORE_PARAM_VALUE="ignoreParamValue";//可放行的参数值
|
||||
|
||||
private List<String> ignorePathList;//可放行的请求路径列表
|
||||
private List<String> ignoreParamValueList;//可放行的参数值列表
|
||||
|
||||
//默认放行单点登录的登出响应(响应中包含samlp:LogoutRequest标签,直接放行)
|
||||
private static final String CAS_LOGOUT_RESPONSE_TAG="samlp:LogoutRequest";
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
log.info("XSS fiter [XSSFilter] init start ...");
|
||||
String ignorePaths = filterConfig.getInitParameter(IGNORE_PATH);
|
||||
String ignoreParamValues = filterConfig.getInitParameter(IGNORE_PARAM_VALUE);
|
||||
if (!StringUtil.isBlank(ignorePaths)) {
|
||||
String[] ignorePathArr = ignorePaths.split(",");
|
||||
ignorePathList=Arrays.asList(ignorePathArr);
|
||||
}
|
||||
if (!StringUtil.isBlank(ignoreParamValues)) {
|
||||
String[] ignoreParamValueArr = ignoreParamValues.split(",");
|
||||
ignoreParamValueList=Arrays.asList(ignoreParamValueArr);
|
||||
//默认放行单点登录的登出响应(响应中包含samlp:LogoutRequest标签,直接放行)
|
||||
if(!ignoreParamValueList.contains(CAS_LOGOUT_RESPONSE_TAG)){
|
||||
ignoreParamValueList.add(CAS_LOGOUT_RESPONSE_TAG);
|
||||
}
|
||||
}
|
||||
else{
|
||||
//默认放行单点登录的登出响应(响应中包含samlp:LogoutRequest标签,直接放行)
|
||||
ignoreParamValueList=new ArrayList<String>();
|
||||
ignoreParamValueList.add(CAS_LOGOUT_RESPONSE_TAG);
|
||||
}
|
||||
log.info("ignorePathList="+JSON.toJSONString(ignorePathList));
|
||||
log.info("ignoreParamValueList="+JSON.toJSONString(ignoreParamValueList));
|
||||
log.info("XSS fiter [XSSFilter] init end");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
log.info("XSS fiter [XSSFilter] starting");
|
||||
// 判断uri是否包含项目名称
|
||||
String uriPath = ((HttpServletRequest) request).getRequestURI();
|
||||
if (isIgnorePath(uriPath)) {
|
||||
log.info("ignore xssfilter,path["+uriPath+"] no need XssFilter, go ahead...");
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
} else {
|
||||
log.info("has xssfiter path["+uriPath+"] need XssFilter, go to XssRequestWrapper");
|
||||
chain.doFilter(new XssRequestWrapper((HttpServletRequest) request,ignoreParamValueList), response);
|
||||
}
|
||||
log.info("XSS fiter [XSSFilter] stop");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
log.info("XSS fiter [XSSFilter] destroy");
|
||||
}
|
||||
|
||||
private boolean isIgnorePath(String servletPath) {
|
||||
if(StringUtil.isBlank(servletPath)){
|
||||
return true;
|
||||
}
|
||||
if (CollectionUtil.isEmpty(ignorePathList))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else{
|
||||
for(String ignorePath:ignorePathList){
|
||||
if(servletPath.contains(ignorePath)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
57
src/main/java/com/ai/net/xss/util/CollectionUtil.java
Normal file
57
src/main/java/com/ai/net/xss/util/CollectionUtil.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package com.ai.net.xss.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public final class CollectionUtil {
|
||||
private CollectionUtil(){}
|
||||
public static boolean isEmpty(Collection<?> collection) {
|
||||
if (null == collection) {
|
||||
return true;
|
||||
} else {
|
||||
return collection.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isEmpty(Object[] objects) {
|
||||
return (objects == null || objects.length == 0) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组转换为List
|
||||
*
|
||||
* @param arr
|
||||
* @return
|
||||
*/
|
||||
public static List<?> arrayToList(Object[] arr) {
|
||||
List<?> list = new ArrayList<>();
|
||||
if (arr == null)
|
||||
{return list;}
|
||||
list = Arrays.asList(arr);
|
||||
return list;
|
||||
}
|
||||
/**
|
||||
* 集合分割成字符串
|
||||
* @param collections 集合对象
|
||||
* @param separator 分隔符
|
||||
* @return
|
||||
* @author rui
|
||||
*/
|
||||
public static String split(Collection<?> collections, String separator){
|
||||
|
||||
Object[] array = collections.toArray(new Object[0]);
|
||||
int length = array.length;
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
stringBuilder.append(array[i]);
|
||||
if (i != length-1) {
|
||||
stringBuilder.append(separator);
|
||||
}
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
164
src/main/java/com/ai/net/xss/util/StringUtil.java
Normal file
164
src/main/java/com/ai/net/xss/util/StringUtil.java
Normal file
@@ -0,0 +1,164 @@
|
||||
package com.ai.net.xss.util;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class StringUtil {
|
||||
|
||||
private StringUtil() {
|
||||
|
||||
}
|
||||
|
||||
public static boolean isBlank(String str) {
|
||||
if (null == str) {
|
||||
return true;
|
||||
}
|
||||
if ("".equals(str.trim())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String toString(Object obj) {
|
||||
if (obj == null) {
|
||||
return "";
|
||||
}
|
||||
return obj.toString();
|
||||
}
|
||||
|
||||
public static String restrictLength(String strSrc, int iMaxLength) {
|
||||
if (strSrc == null) {
|
||||
return null;
|
||||
}
|
||||
if (iMaxLength <= 0) {
|
||||
return strSrc;
|
||||
}
|
||||
String strResult = strSrc;
|
||||
byte[] b = null;
|
||||
int iLength = strSrc.length();
|
||||
if (iLength > iMaxLength) {
|
||||
strResult = strResult.substring(0, iMaxLength);
|
||||
iLength = iMaxLength;
|
||||
}
|
||||
while (true) {
|
||||
b = strResult.getBytes();
|
||||
if (b.length <= iMaxLength) {
|
||||
break;
|
||||
}
|
||||
iLength--;
|
||||
strResult = strResult.substring(0, iLength);
|
||||
}
|
||||
return strResult;
|
||||
}
|
||||
|
||||
public static String getRandomString(int length) {
|
||||
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
// Random random = new Random();
|
||||
SecureRandom random = new SecureRandom();
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
int num = random.nextInt(str.length());
|
||||
buf.append(str.charAt(num));
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 左补齐
|
||||
*
|
||||
* @param target
|
||||
* 目标字符串
|
||||
* @param fix
|
||||
* 补齐字符
|
||||
* @param length
|
||||
* 目标长度
|
||||
* @return
|
||||
*/
|
||||
public static String lPad(String target, String fix, int length) {
|
||||
if (target == null || fix == null || !(target.length() < length)) {
|
||||
return target;
|
||||
}
|
||||
StringBuilder newStr = new StringBuilder();
|
||||
for (int i = 0; i < length - target.length(); i++) {
|
||||
newStr.append(fix);
|
||||
}
|
||||
return newStr.append(target).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 右补齐
|
||||
*
|
||||
* @param target
|
||||
* 目标字符串
|
||||
* @param fix
|
||||
* 补齐字符
|
||||
* @param length
|
||||
* 目标长度
|
||||
* @return
|
||||
*/
|
||||
public static String rPad(String target, String fix, int length) {
|
||||
if (target == null || fix == null || !(target.length() < length)) {
|
||||
return target;
|
||||
}
|
||||
StringBuilder newStr = new StringBuilder();
|
||||
newStr.append(target);
|
||||
for (int i = 0; i < length - target.length(); i++) {
|
||||
newStr.append(fix);
|
||||
}
|
||||
return newStr.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串数据join操作
|
||||
*
|
||||
* @param strs
|
||||
* @param spi
|
||||
* @return
|
||||
* @author zhoubo
|
||||
*/
|
||||
public static String join(String[] strs, String spi) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
int step = 0;
|
||||
for (String str : strs) {
|
||||
buf.append(str);
|
||||
if (step++ < strs.length - 1) {
|
||||
buf.append(spi);
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
// 默认值为无
|
||||
public static String toString2(Object obj) {
|
||||
if (obj == null) {
|
||||
return "无";
|
||||
} else if ("".equals(obj)) {
|
||||
return "无";
|
||||
}
|
||||
return obj.toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* public static void main(String[] args){ System.out.println(StringUtil.getRandomString(10)); }
|
||||
*/
|
||||
|
||||
/**
|
||||
* 固网号码去除 区号-号码 中间的横杠 010-88018802
|
||||
*
|
||||
* @param str
|
||||
* @return
|
||||
* @author mayt
|
||||
*/
|
||||
public static String replaceServiceNumBar(String str) {
|
||||
String dest = "";
|
||||
if (str != null) {
|
||||
Pattern p = Pattern.compile("-");
|
||||
Matcher m = p.matcher(str);
|
||||
dest = m.replaceAll("");
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
142
src/main/java/com/ai/net/xss/wrapper/XssRequestWrapper.java
Normal file
142
src/main/java/com/ai/net/xss/wrapper/XssRequestWrapper.java
Normal file
@@ -0,0 +1,142 @@
|
||||
package com.ai.net.xss.wrapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
|
||||
import org.apache.commons.lang.StringEscapeUtils;
|
||||
import org.owasp.validator.html.AntiSamy;
|
||||
import org.owasp.validator.html.CleanResults;
|
||||
import org.owasp.validator.html.Policy;
|
||||
import org.owasp.validator.html.PolicyException;
|
||||
import org.owasp.validator.html.ScanException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.ai.net.xss.util.CollectionUtil;
|
||||
import com.ai.net.xss.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
public class XssRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
private static Logger log=LoggerFactory.getLogger(XssRequestWrapper.class);
|
||||
private List<String> ignoreParamValueList;
|
||||
private static final String ANTISAMY_SLASHDOT_XML = "antisamy-slashdot-1.4.4.xml";
|
||||
private static Policy policy = null;
|
||||
|
||||
static {
|
||||
log.info(" start read XSS configfile [" + ANTISAMY_SLASHDOT_XML + "]");
|
||||
InputStream inputStream = XssRequestWrapper.class.getClassLoader().getResourceAsStream(ANTISAMY_SLASHDOT_XML);
|
||||
try {
|
||||
policy = Policy.getInstance(inputStream);
|
||||
log.info("read XSS configfile [" + ANTISAMY_SLASHDOT_XML + "] success");
|
||||
} catch (PolicyException e) {
|
||||
log.error("read XSS configfile [" + ANTISAMY_SLASHDOT_XML + "] fail , reason:", e);
|
||||
}
|
||||
finally{
|
||||
if(inputStream!=null){
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
log.error("close XSS configfile [" + ANTISAMY_SLASHDOT_XML + "] fail , reason:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public XssRequestWrapper(HttpServletRequest request,List<String> ignoreParamValueList) {
|
||||
super(request);
|
||||
this.ignoreParamValueList=ignoreParamValueList;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Map<String, String[]> getParameterMap() {
|
||||
Map<String, String[]> request_map = super.getParameterMap();
|
||||
Iterator iterator = request_map.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry me = (Map.Entry) iterator.next();
|
||||
log.info(me.getKey()+":");
|
||||
String[] values = (String[]) me.getValue();
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
log.info(values[i]);
|
||||
values[i] = xssClean(values[i]);
|
||||
}
|
||||
}
|
||||
return request_map;
|
||||
}
|
||||
|
||||
public String[] getParameterValues(String paramString) {
|
||||
String[] arrayOfString1 = super.getParameterValues(paramString);
|
||||
if (arrayOfString1 == null)
|
||||
return null;
|
||||
int i = arrayOfString1.length;
|
||||
String[] arrayOfString2 = new String[i];
|
||||
for (int j = 0; j < i; j++)
|
||||
arrayOfString2[j] = xssClean(arrayOfString1[j]);
|
||||
return arrayOfString2;
|
||||
}
|
||||
|
||||
public String getParameter(String paramString) {
|
||||
String str = super.getParameter(paramString);
|
||||
if (str == null)
|
||||
return null;
|
||||
return xssClean(str);
|
||||
}
|
||||
|
||||
public String getHeader(String paramString) {
|
||||
String str = super.getHeader(paramString);
|
||||
if (str == null)
|
||||
return null;
|
||||
return xssClean(str);
|
||||
}
|
||||
|
||||
private String xssClean(String paramValue) {
|
||||
AntiSamy antiSamy = new AntiSamy();
|
||||
log.info("ignoreParamValueList="+JSON.toJSONString(ignoreParamValueList));
|
||||
try {
|
||||
log.info("raw value before xssClean: " + paramValue);
|
||||
if(isIgnoreParamValue(paramValue)){
|
||||
log.info("ignore the xssClean,keep the raw paramValue: " + paramValue);
|
||||
return paramValue;
|
||||
}
|
||||
else{
|
||||
final CleanResults cr = antiSamy.scan(paramValue, policy);
|
||||
String str = StringEscapeUtils.escapeHtml(cr.getCleanHTML());
|
||||
str = str.replaceAll((antiSamy.scan(" ", policy)).getCleanHTML(), "");
|
||||
str = StringEscapeUtils.unescapeHtml(str);
|
||||
log.info("xssfilter value after xssClean:" + str);
|
||||
return str;
|
||||
}
|
||||
|
||||
} catch (ScanException e) {
|
||||
log.error("scan failed ,parmter is [" + paramValue + "]", e);
|
||||
} catch (PolicyException e) {
|
||||
log.error("antisamy convert failed ,parmter is [" + paramValue + "]", e);
|
||||
}
|
||||
return paramValue;
|
||||
}
|
||||
|
||||
private boolean isIgnoreParamValue(String paramValue) {
|
||||
if(StringUtil.isBlank(paramValue)){
|
||||
return true;
|
||||
}
|
||||
if (CollectionUtil.isEmpty(ignoreParamValueList))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
for(String ignoreParamValue:ignoreParamValueList){
|
||||
if(paramValue.contains(ignoreParamValue)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
180
src/main/resources/antisamy-slashdot-1.4.4.xml
Normal file
180
src/main/resources/antisamy-slashdot-1.4.4.xml
Normal file
@@ -0,0 +1,180 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
|
||||
<!--
|
||||
W3C rules retrieved from:
|
||||
http://www.w3.org/TR/html401/struct/global.html
|
||||
-->
|
||||
|
||||
<!--
|
||||
Slashdot allowed tags taken from "Reply" page:
|
||||
<b> <i> <p> <br> <a> <ol> <ul> <li> <dl> <dt> <dd> <em> <strong> <tt> <blockquote> <div> <ecode> <quote>
|
||||
-->
|
||||
|
||||
<anti-samy-rules xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="antisamy.xsd">
|
||||
|
||||
<directives>
|
||||
<directive name="omitXmlDeclaration" value="true"/>
|
||||
<directive name="omitDoctypeDeclaration" value="true"/>
|
||||
<directive name="maxInputSize" value="5000"/>
|
||||
<directive name="useXHTML" value="true"/>
|
||||
<directive name="formatOutput" value="true"/>
|
||||
|
||||
<directive name="embedStyleSheets" value="false"/>
|
||||
</directives>
|
||||
|
||||
<common-regexps>
|
||||
|
||||
<!--
|
||||
From W3C:
|
||||
This attribute assigns a class name or set of class names to an
|
||||
element. Any number of elements may be assigned the same class
|
||||
name or names. Multiple class names must be separated by white
|
||||
space characters.
|
||||
-->
|
||||
|
||||
<regexp name="htmlTitle" value="[\p{L}\p{N}\s\-_',:\[\]!\./\\\(\)&]*"/> <!-- force non-empty with a '+' at the end instead of '*' -->
|
||||
<regexp name="onsiteURL" value="([\p{L}\p{N}\\/\.\?=\#&;\-_~]+|\#(\w)+)"/>
|
||||
<regexp name="offsiteURL" value="(\s)*((ht|f)tp(s?)://|mailto:)[\p{L}\p{N}]+[~\p{L}\p{N}\p{Zs}\-_\.@\#\$%&;:,\?=/\+!\(\)]*(\s)*"/>
|
||||
|
||||
</common-regexps>
|
||||
|
||||
<!--
|
||||
|
||||
Tag.name = a, b, div, body, etc.
|
||||
Tag.action = filter: remove tags, but keep content, validate: keep content as long as it passes rules, remove: remove tag and contents
|
||||
Attribute.name = id, class, href, align, width, etc.
|
||||
Attribute.onInvalid = what to do when the attribute is invalid, e.g., remove the tag (removeTag), remove the attribute (removeAttribute), filter the tag (filterTag)
|
||||
Attribute.description = What rules in English you want to tell the users they can have for this attribute. Include helpful things so they'll be able to tune their HTML
|
||||
|
||||
-->
|
||||
|
||||
<!--
|
||||
Some attributes are common to all (or most) HTML tags. There aren't many that qualify for this. You have to make sure there's no
|
||||
collisions between any of these attribute names with attribute names of other tags that are for different purposes.
|
||||
-->
|
||||
|
||||
<common-attributes>
|
||||
|
||||
|
||||
<attribute name="lang" description="The 'lang' attribute tells the browser what language the element's attribute values and content are written in">
|
||||
<regexp-list>
|
||||
<regexp value="[a-zA-Z]{2,20}"/>
|
||||
</regexp-list>
|
||||
</attribute>
|
||||
|
||||
<attribute name="title" description="The 'title' attribute provides text that shows up in a 'tooltip' when a user hovers their mouse over the element">
|
||||
<regexp-list>
|
||||
<regexp name="htmlTitle"/>
|
||||
</regexp-list>
|
||||
</attribute>
|
||||
|
||||
<attribute name="href" onInvalid="filterTag">
|
||||
<regexp-list>
|
||||
<regexp name="onsiteURL"/>
|
||||
<regexp name="offsiteURL"/>
|
||||
</regexp-list>
|
||||
</attribute>
|
||||
|
||||
<attribute name="align" description="The 'align' attribute of an HTML element is a direction word, like 'left', 'right' or 'center'">
|
||||
<literal-list>
|
||||
<literal value="center"/>
|
||||
<literal value="left"/>
|
||||
<literal value="right"/>
|
||||
<literal value="justify"/>
|
||||
<literal value="char"/>
|
||||
</literal-list>
|
||||
</attribute>
|
||||
|
||||
</common-attributes>
|
||||
|
||||
|
||||
<!--
|
||||
This requires normal updates as browsers continue to diverge from the W3C and each other. As long as the browser wars continue
|
||||
this is going to continue. I'm not sure war is the right word for what's going on. Doesn't somebody have to win a war after
|
||||
a while?
|
||||
-->
|
||||
|
||||
<global-tag-attributes>
|
||||
<attribute name="title"/>
|
||||
<attribute name="lang"/>
|
||||
</global-tag-attributes>
|
||||
|
||||
<tags-to-encode>
|
||||
<tag>g</tag>
|
||||
<tag>grin</tag>
|
||||
</tags-to-encode>
|
||||
|
||||
<tag-rules>
|
||||
|
||||
<!-- Tags related to JavaScript -->
|
||||
|
||||
<tag name="script" action="remove"/>
|
||||
<tag name="noscript" action="remove"/>
|
||||
|
||||
<!-- Frame & related tags -->
|
||||
|
||||
<tag name="iframe" action="remove"/>
|
||||
<tag name="frameset" action="remove"/>
|
||||
<tag name="frame" action="remove"/>
|
||||
<tag name="noframes" action="remove"/>
|
||||
|
||||
<!-- CSS related tags -->
|
||||
<tag name="style" action="remove"/>
|
||||
|
||||
<!-- All reasonable formatting tags -->
|
||||
|
||||
<tag name="p" action="validate">
|
||||
<attribute name="align"/>
|
||||
</tag>
|
||||
|
||||
<tag name="div" action="validate"/>
|
||||
<tag name="i" action="validate"/>
|
||||
<tag name="b" action="validate"/>
|
||||
<tag name="em" action="validate"/>
|
||||
<tag name="blockquote" action="validate"/>
|
||||
<tag name="tt" action="validate"/>
|
||||
<tag name="strong" action="validate"/>
|
||||
|
||||
<tag name="br" action="truncate"/>
|
||||
|
||||
<!-- Custom Slashdot tags, though we're trimming the idea of having a possible mismatching end tag with the endtag="" attribute -->
|
||||
|
||||
<tag name="quote" action="validate"/>
|
||||
<tag name="ecode" action="validate"/>
|
||||
|
||||
|
||||
<!-- Anchor and anchor related tags -->
|
||||
|
||||
<tag name="a" action="validate">
|
||||
|
||||
<attribute name="href" onInvalid="filterTag"/>
|
||||
<attribute name="nohref">
|
||||
<literal-list>
|
||||
<literal value="nohref"/>
|
||||
<literal value=""/>
|
||||
</literal-list>
|
||||
</attribute>
|
||||
<attribute name="rel">
|
||||
<literal-list>
|
||||
<literal value="nofollow"/>
|
||||
</literal-list>
|
||||
</attribute>
|
||||
</tag>
|
||||
|
||||
<!-- List tags -->
|
||||
|
||||
<tag name="ul" action="validate"/>
|
||||
<tag name="ol" action="validate"/>
|
||||
<tag name="li" action="validate"/>
|
||||
|
||||
</tag-rules>
|
||||
|
||||
|
||||
|
||||
<!-- No CSS on Slashdot posts -->
|
||||
|
||||
<css-rules>
|
||||
</css-rules>
|
||||
|
||||
</anti-samy-rules>
|
||||
Reference in New Issue
Block a user