|
|
@@ -0,0 +1,134 @@
|
|
|
+package cn.seecoder.fdroidrepository.security;
|
|
|
+
|
|
|
+import org.apache.commons.lang3.CharUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
+import java.io.BufferedReader;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.io.InputStreamReader;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Objects;
|
|
|
+
|
|
|
+@Component
|
|
|
+public class SensitiveFilter {
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(SensitiveFilter.class);
|
|
|
+
|
|
|
+ // sensitive words will be replaced by REPLACE_HOLDER
|
|
|
+ private static final String REPLACE_HOLDER = "***";
|
|
|
+
|
|
|
+ private final TrieNode rootNode = new TrieNode();
|
|
|
+
|
|
|
+ @PostConstruct
|
|
|
+ public void init() {
|
|
|
+ try (
|
|
|
+ InputStream is = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
|
|
|
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
|
|
+ ) {
|
|
|
+ String keyword;
|
|
|
+ while ((keyword = reader.readLine()) != null) {
|
|
|
+ addKeyword(keyword);
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (IOException e) {
|
|
|
+ logger.error("load sensitive words failed: " + e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private void addKeyword(String keyword) {
|
|
|
+ TrieNode ptr = rootNode;
|
|
|
+ for (int i = 0; i < keyword.length(); i++) {
|
|
|
+ char c = keyword.charAt(i);
|
|
|
+ TrieNode subNode = ptr.getSubNode(c);
|
|
|
+ if (Objects.isNull(subNode)) {
|
|
|
+ ptr.addSubNode(c, new TrieNode());
|
|
|
+ subNode = ptr.getSubNode(c);
|
|
|
+ }
|
|
|
+ ptr = subNode;
|
|
|
+ if (i == keyword.length() - 1) {
|
|
|
+ ptr.setKeyWordEnd(true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param text raw words before sensitive filter
|
|
|
+ * @return words after sensitive filter
|
|
|
+ */
|
|
|
+ public String filter(String text) {
|
|
|
+ if (StringUtils.isBlank(text)) {
|
|
|
+ return text;
|
|
|
+ }
|
|
|
+ TrieNode ptr = rootNode;
|
|
|
+ int begin = 0, position = 0;
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+
|
|
|
+ while (begin < text.length()) {
|
|
|
+ char c = text.charAt(position);
|
|
|
+ // handle symbol
|
|
|
+ if (isSymbol(c)) {
|
|
|
+ if (ptr == rootNode) {
|
|
|
+ sb.append(c);
|
|
|
+ begin++;
|
|
|
+ }
|
|
|
+ position++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ ptr = ptr.getSubNode(c);
|
|
|
+ if (ptr == null) {
|
|
|
+ sb.append(text.charAt(begin));
|
|
|
+ position=++begin;
|
|
|
+ ptr=rootNode;
|
|
|
+ }else if(ptr.isKeyWordEnd()){
|
|
|
+ sb.append(REPLACE_HOLDER);
|
|
|
+ begin=++position;
|
|
|
+ ptr=rootNode;
|
|
|
+ }else{
|
|
|
+ position++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private boolean isSymbol(Character c) {
|
|
|
+
|
|
|
+ return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class TrieNode {
|
|
|
+ private boolean isEnd = false;
|
|
|
+
|
|
|
+ private final Map<Character, TrieNode> subNodes = new HashMap<>();
|
|
|
+
|
|
|
+ public boolean isKeyWordEnd() {
|
|
|
+ return isEnd;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setKeyWordEnd(boolean keyWordEnd) {
|
|
|
+ isEnd = keyWordEnd;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * add sub nodes
|
|
|
+ */
|
|
|
+ public void addSubNode(Character c, TrieNode node) {
|
|
|
+ subNodes.put(c, node);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * get sub nodes
|
|
|
+ */
|
|
|
+ public TrieNode getSubNode(Character c) {
|
|
|
+ return subNodes.get(c);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+}
|