package com.qianwen.core.boot.request;
|
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentMap;
|
import java.util.logging.Logger;
|
import java.util.regex.Matcher;
|
import java.util.regex.Pattern;
|
|
/* loaded from: blade-core-boot-9.3.0.0-SNAPSHOT.jar:org/springblade/core/boot/request/XssHtmlFilter.class */
|
public final class XssHtmlFilter {
|
private static final int REGEX_FLAGS_SI = 34;
|
private static final Pattern P_COMMENTS;
|
private static final Pattern P_COMMENT;
|
private static final Pattern P_TAGS;
|
private static final Pattern P_END_TAG;
|
private static final Pattern P_START_TAG;
|
private static final Pattern P_QUOTED_ATTRIBUTES;
|
private static final Pattern P_UNQUOTED_ATTRIBUTES;
|
private static final Pattern P_PROTOCOL;
|
private static final Pattern P_ENTITY;
|
private static final Pattern P_ENTITY_UNICODE;
|
private static final Pattern P_ENCODE;
|
private static final Pattern P_VALID_ENTITIES;
|
private static final Pattern P_VALID_QUOTES;
|
private static final Pattern P_END_ARROW;
|
private static final Pattern P_BODY_TO_END;
|
private static final Pattern P_XML_CONTENT;
|
private static final Pattern P_STRAY_LEFT_ARROW;
|
private static final Pattern P_STRAY_RIGHT_ARROW;
|
private static final Pattern P_AMP;
|
private static final Pattern P_QUOTE;
|
private static final Pattern P_LEFT_ARROW;
|
private static final Pattern P_RIGHT_ARROW;
|
private static final Pattern P_BOTH_ARROWS;
|
private static final ConcurrentMap<String, Pattern> P_REMOVE_PAIR_BLANKS;
|
private static final ConcurrentMap<String, Pattern> P_REMOVE_SELF_BLANKS;
|
private final Map<String, List<String>> vAllowed;
|
private final Map<String, Integer> vTagCounts;
|
private final String[] vSelfClosingTags;
|
private final String[] vNeedClosingTags;
|
private final String[] vDisallowed;
|
private final String[] vProtocolAtts;
|
private final String[] vAllowedProtocols;
|
private final String[] vRemoveBlanks;
|
private final String[] vAllowedEntities;
|
private final boolean stripComment;
|
private final boolean encodeQuotes;
|
private boolean vDebug;
|
private final boolean alwaysMakeTags;
|
static final /* synthetic */ boolean $assertionsDisabled;
|
|
static {
|
$assertionsDisabled = !XssHtmlFilter.class.desiredAssertionStatus();
|
P_COMMENTS = Pattern.compile("<!--(.*?)-->", 32);
|
P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI);
|
P_TAGS = Pattern.compile("<(.*?)>", 32);
|
P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI);
|
P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI);
|
P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI);
|
P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI);
|
P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI);
|
P_ENTITY = Pattern.compile("&#(\\d+);?");
|
P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?");
|
P_ENCODE = Pattern.compile("%([0-9a-f]{2});?");
|
P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))");
|
P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", 32);
|
P_END_ARROW = Pattern.compile("^>");
|
P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)");
|
P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)");
|
P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)");
|
P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)");
|
P_AMP = Pattern.compile("&");
|
P_QUOTE = Pattern.compile("<");
|
P_LEFT_ARROW = Pattern.compile("<");
|
P_RIGHT_ARROW = Pattern.compile(">");
|
P_BOTH_ARROWS = Pattern.compile("<>");
|
P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap();
|
P_REMOVE_SELF_BLANKS = new ConcurrentHashMap();
|
}
|
|
public XssHtmlFilter() {
|
this.vTagCounts = new HashMap();
|
this.vDebug = false;
|
this.vAllowed = new HashMap();
|
ArrayList<String> aAtts = new ArrayList<>();
|
aAtts.add("href");
|
aAtts.add("target");
|
this.vAllowed.put("a", aAtts);
|
ArrayList<String> imgAtts = new ArrayList<>();
|
imgAtts.add("src");
|
imgAtts.add("width");
|
imgAtts.add("height");
|
imgAtts.add("alt");
|
this.vAllowed.put("img", imgAtts);
|
ArrayList<String> noAtts = new ArrayList<>();
|
this.vAllowed.put("b", noAtts);
|
this.vAllowed.put("strong", noAtts);
|
this.vAllowed.put("i", noAtts);
|
this.vAllowed.put("em", noAtts);
|
this.vSelfClosingTags = new String[]{"img"};
|
this.vNeedClosingTags = new String[]{"a", "b", "strong", "i", "em"};
|
this.vDisallowed = new String[0];
|
this.vAllowedProtocols = new String[]{"http", "mailto", "https"};
|
this.vProtocolAtts = new String[]{"src", "href"};
|
this.vRemoveBlanks = new String[]{"a", "b", "strong", "i", "em"};
|
this.vAllowedEntities = new String[]{"amp", "gt", "lt", "quot"};
|
this.stripComment = true;
|
this.encodeQuotes = true;
|
this.alwaysMakeTags = false;
|
}
|
|
public XssHtmlFilter(final boolean debug) {
|
this();
|
this.vDebug = debug;
|
}
|
|
public XssHtmlFilter(final Map<String, Object> conf) {
|
this.vTagCounts = new HashMap();
|
this.vDebug = false;
|
if (!$assertionsDisabled && !conf.containsKey("vAllowed")) {
|
throw new AssertionError("configuration requires vAllowed");
|
}
|
if (!$assertionsDisabled && !conf.containsKey("vSelfClosingTags")) {
|
throw new AssertionError("configuration requires vSelfClosingTags");
|
}
|
if (!$assertionsDisabled && !conf.containsKey("vNeedClosingTags")) {
|
throw new AssertionError("configuration requires vNeedClosingTags");
|
}
|
if (!$assertionsDisabled && !conf.containsKey("vDisallowed")) {
|
throw new AssertionError("configuration requires vDisallowed");
|
}
|
if (!$assertionsDisabled && !conf.containsKey("vAllowedProtocols")) {
|
throw new AssertionError("configuration requires vAllowedProtocols");
|
}
|
if (!$assertionsDisabled && !conf.containsKey("vProtocolAtts")) {
|
throw new AssertionError("configuration requires vProtocolAtts");
|
}
|
if (!$assertionsDisabled && !conf.containsKey("vRemoveBlanks")) {
|
throw new AssertionError("configuration requires vRemoveBlanks");
|
}
|
if (!$assertionsDisabled && !conf.containsKey("vAllowedEntities")) {
|
throw new AssertionError("configuration requires vAllowedEntities");
|
}
|
this.vAllowed = Collections.unmodifiableMap((HashMap) conf.get("vAllowed"));
|
this.vSelfClosingTags = (String[]) conf.get("vSelfClosingTags");
|
this.vNeedClosingTags = (String[]) conf.get("vNeedClosingTags");
|
this.vDisallowed = (String[]) conf.get("vDisallowed");
|
this.vAllowedProtocols = (String[]) conf.get("vAllowedProtocols");
|
this.vProtocolAtts = (String[]) conf.get("vProtocolAtts");
|
this.vRemoveBlanks = (String[]) conf.get("vRemoveBlanks");
|
this.vAllowedEntities = (String[]) conf.get("vAllowedEntities");
|
this.stripComment = conf.containsKey("stripComment") ? ((Boolean) conf.get("stripComment")).booleanValue() : true;
|
this.encodeQuotes = conf.containsKey("encodeQuotes") ? ((Boolean) conf.get("encodeQuotes")).booleanValue() : true;
|
this.alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? ((Boolean) conf.get("alwaysMakeTags")).booleanValue() : true;
|
}
|
|
private void reset() {
|
this.vTagCounts.clear();
|
}
|
|
private void debug(final String msg) {
|
if (this.vDebug) {
|
Logger.getAnonymousLogger().info(msg);
|
}
|
}
|
|
public static String chr(final int decimal) {
|
return String.valueOf((char) decimal);
|
}
|
|
public static String htmlSpecialChars(final String s) {
|
String result = regexReplace(P_AMP, "&", s);
|
return regexReplace(P_RIGHT_ARROW, ">", regexReplace(P_LEFT_ARROW, "<", regexReplace(P_QUOTE, """, result)));
|
}
|
|
public String filter(final String input) {
|
reset();
|
debug("************************************************");
|
debug(" INPUT: " + input);
|
String s = escapeComments(input);
|
debug(" escapeComments: " + s);
|
String s2 = balanceHtml(s);
|
debug(" balanceHtml: " + s2);
|
String s3 = checkTags(s2);
|
debug(" checkTags: " + s3);
|
String s4 = processRemoveBlanks(s3);
|
debug("processRemoveBlanks: " + s4);
|
String s5 = validateEntities(s4);
|
debug(" validateEntites: " + s5);
|
debug("************************************************\n\n");
|
return s5;
|
}
|
|
public boolean isAlwaysMakeTags() {
|
return this.alwaysMakeTags;
|
}
|
|
public boolean isStripComments() {
|
return this.stripComment;
|
}
|
|
private String escapeComments(final String s) {
|
Matcher m = P_COMMENTS.matcher(s);
|
StringBuffer buf = new StringBuffer();
|
if (m.find()) {
|
String match = m.group(1);
|
m.appendReplacement(buf, Matcher.quoteReplacement("<!--" + htmlSpecialChars(match) + "-->"));
|
}
|
m.appendTail(buf);
|
return buf.toString();
|
}
|
|
private String balanceHtml(String s) {
|
String s2;
|
if (this.alwaysMakeTags) {
|
s2 = regexReplace(P_XML_CONTENT, "$1<$2", regexReplace(P_BODY_TO_END, "<$1>", regexReplace(P_END_ARROW, "", s)));
|
} else {
|
s2 = regexReplace(P_BOTH_ARROWS, "", regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", regexReplace(P_STRAY_LEFT_ARROW, "<$1", s)));
|
}
|
return s2;
|
}
|
|
private String checkTags(String s) {
|
Matcher m = P_TAGS.matcher(s);
|
StringBuffer buf = new StringBuffer();
|
while (m.find()) {
|
String replaceStr = m.group(1);
|
m.appendReplacement(buf, Matcher.quoteReplacement(processTag(replaceStr)));
|
}
|
m.appendTail(buf);
|
String s2 = buf.toString();
|
for (String key : this.vTagCounts.keySet()) {
|
for (int ii = 0; ii < this.vTagCounts.get(key).intValue(); ii++) {
|
s2 = s2 + "</" + key + ">";
|
}
|
}
|
return s2;
|
}
|
|
private String processRemoveBlanks(final String s) {
|
String[] strArr;
|
String result = s;
|
for (String tag : this.vRemoveBlanks) {
|
if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) {
|
P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?></" + tag + ">"));
|
}
|
String result2 = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result);
|
if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) {
|
P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>"));
|
}
|
result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result2);
|
}
|
return result;
|
}
|
|
private static String regexReplace(final Pattern regexPattern, final String replacement, final String s) {
|
Matcher m = regexPattern.matcher(s);
|
return m.replaceAll(replacement);
|
}
|
|
private String processTag(final String s) {
|
Matcher m = P_END_TAG.matcher(s);
|
if (m.find()) {
|
String name = m.group(1).toLowerCase();
|
if (allowed(name) && !inArray(name, this.vSelfClosingTags) && this.vTagCounts.containsKey(name)) {
|
this.vTagCounts.put(name, Integer.valueOf(this.vTagCounts.get(name).intValue() - 1));
|
return "</" + name + ">";
|
}
|
}
|
Matcher m2 = P_START_TAG.matcher(s);
|
if (m2.find()) {
|
String name2 = m2.group(1).toLowerCase();
|
String body = m2.group(2);
|
String ending = m2.group(3);
|
if (allowed(name2)) {
|
String params = "";
|
Matcher m22 = P_QUOTED_ATTRIBUTES.matcher(body);
|
Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body);
|
List<String> paramNames = new ArrayList<>();
|
List<String> paramValues = new ArrayList<>();
|
while (m22.find()) {
|
paramNames.add(m22.group(1));
|
paramValues.add(m22.group(3));
|
}
|
while (m3.find()) {
|
paramNames.add(m3.group(1));
|
paramValues.add(m3.group(3));
|
}
|
for (int ii = 0; ii < paramNames.size(); ii++) {
|
String paramName = paramNames.get(ii).toLowerCase();
|
String paramValue = paramValues.get(ii);
|
if (allowedAttribute(name2, paramName)) {
|
if (inArray(paramName, this.vProtocolAtts)) {
|
paramValue = processParamProtocol(paramValue);
|
}
|
params = params + " " + paramName + "=\"" + paramValue + "\"";
|
}
|
}
|
if (inArray(name2, this.vSelfClosingTags)) {
|
ending = " /";
|
}
|
if (inArray(name2, this.vNeedClosingTags)) {
|
ending = "";
|
}
|
if (ending == null || ending.length() < 1) {
|
if (this.vTagCounts.containsKey(name2)) {
|
this.vTagCounts.put(name2, Integer.valueOf(this.vTagCounts.get(name2).intValue() + 1));
|
} else {
|
this.vTagCounts.put(name2, 1);
|
}
|
} else {
|
ending = " /";
|
}
|
return "<" + name2 + params + ending + ">";
|
}
|
return "";
|
}
|
Matcher m4 = P_COMMENT.matcher(s);
|
if (!this.stripComment && m4.find()) {
|
return "<" + m4.group() + ">";
|
}
|
return "";
|
}
|
|
private String processParamProtocol(String s) {
|
String s2 = decodeEntities(s);
|
Matcher m = P_PROTOCOL.matcher(s2);
|
if (m.find()) {
|
String protocol = m.group(1);
|
if (!inArray(protocol, this.vAllowedProtocols)) {
|
s2 = "#" + s2.substring(protocol.length() + 1);
|
if (s2.startsWith("#//")) {
|
s2 = "#" + s2.substring(3);
|
}
|
}
|
}
|
return s2;
|
}
|
|
private String decodeEntities(String s) {
|
StringBuffer buf = new StringBuffer();
|
Matcher m = P_ENTITY.matcher(s);
|
while (m.find()) {
|
String match = m.group(1);
|
int decimal = Integer.decode(match).intValue();
|
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
|
}
|
m.appendTail(buf);
|
String s2 = buf.toString();
|
StringBuffer buf2 = new StringBuffer();
|
Matcher m2 = P_ENTITY_UNICODE.matcher(s2);
|
while (m2.find()) {
|
String match2 = m2.group(1);
|
int decimal2 = Integer.valueOf(match2, 16).intValue();
|
m2.appendReplacement(buf2, Matcher.quoteReplacement(chr(decimal2)));
|
}
|
m2.appendTail(buf2);
|
String s3 = buf2.toString();
|
StringBuffer buf3 = new StringBuffer();
|
Matcher m3 = P_ENCODE.matcher(s3);
|
while (m3.find()) {
|
String match3 = m3.group(1);
|
int decimal3 = Integer.valueOf(match3, 16).intValue();
|
m3.appendReplacement(buf3, Matcher.quoteReplacement(chr(decimal3)));
|
}
|
m3.appendTail(buf3);
|
String s4 = buf3.toString();
|
return validateEntities(s4);
|
}
|
|
private String validateEntities(final String s) {
|
StringBuffer buf = new StringBuffer();
|
Matcher m = P_VALID_ENTITIES.matcher(s);
|
while (m.find()) {
|
String one = m.group(1);
|
String two = m.group(2);
|
m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two)));
|
}
|
m.appendTail(buf);
|
return encodeQuotes(buf.toString());
|
}
|
|
private String encodeQuotes(final String s) {
|
if (this.encodeQuotes) {
|
StringBuffer buf = new StringBuffer();
|
Matcher m = P_VALID_QUOTES.matcher(s);
|
while (m.find()) {
|
String one = m.group(1);
|
String two = m.group(2);
|
String three = m.group(3);
|
m.appendReplacement(buf, Matcher.quoteReplacement(one + regexReplace(P_QUOTE, """, two) + three));
|
}
|
m.appendTail(buf);
|
return buf.toString();
|
}
|
return s;
|
}
|
|
private String checkEntity(final String preamble, final String term) {
|
return (";".equals(term) && isValidEntity(preamble)) ? '&' + preamble : "&" + preamble;
|
}
|
|
private boolean isValidEntity(final String entity) {
|
return inArray(entity, this.vAllowedEntities);
|
}
|
|
private static boolean inArray(final String s, final String[] array) {
|
for (String item : array) {
|
if (item != null && item.equals(s)) {
|
return true;
|
}
|
}
|
return false;
|
}
|
|
private boolean allowed(final String name) {
|
return (this.vAllowed.isEmpty() || this.vAllowed.containsKey(name)) && !inArray(name, this.vDisallowed);
|
}
|
|
private boolean allowedAttribute(final String name, final String paramName) {
|
return allowed(name) && (this.vAllowed.isEmpty() || this.vAllowed.get(name).contains(paramName));
|
}
|
}
|