package com.hdkj.lt.utils.security;

import cn.hutool.crypto.SecureUtil;
import com.hdkj.lt.utils.reflect.ReflectUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
public final class SqlInjectionUtil {

    /**
     * sign 用于sql加签的盐值【SQL漏洞】
     * （上线修改值 TABLE_DICT_SIGN_SALT，同步修改前端的盐值）
     */
    private static final String TABLE_DICT_SIGN_SALT = "20200626";

    private static final String XSS_STR = "'|and |exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+";

    private static final String REG = "(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|"
            + "(\\b(select|update|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|into|drop|execute)\\b)";

    private final static Pattern SQL_XSS_PATTERN = Pattern.compile("[_%/\\\\]", Pattern.CASE_INSENSITIVE);

    private final static Pattern SQL_XSS_PATTERN_ALL = Pattern.compile(".*[_%/\\\\].*", Pattern.CASE_INSENSITIVE);


    /**
     * 表示忽略大小写
     */
    private static final Pattern SQL_PATTERN = Pattern.compile(REG, Pattern.CASE_INSENSITIVE);

    /**
     * 针对sql进行额外的sign签名校验（增加安全机制）
     *
     * @param dictCode:
     * @param sign:
     * @param request:
     * @Return: void
     */
    public static void checkDictTableSign(String dictCode, String sign, HttpServletRequest request) {
        //表字典SQL注入漏洞,签名校验
        String accessToken = request.getHeader("X-Access-Token");
        String signStr = dictCode + SqlInjectionUtil.TABLE_DICT_SIGN_SALT + accessToken;
        String javaSign = SecureUtil.md5(signStr);
        if (!javaSign.equals(sign)) {
            log.info("SQL注入漏洞签名校验失败 ：" + sign + "!=" + javaSign + ",dictCode=" + dictCode);
            throw new RuntimeException("无权限访问！");
        }
        log.info("SQL注入漏洞签名校验成功！sign=" + sign + ",dictCode=" + dictCode);
    }


    /**
     * sql注入过滤处理，遇到注入关键字抛异常
     *
     * @param value
     * @return
     */
    public static void filterContent(String value) {
        if (value == null || "".equals(value)) {
            return;
        }
        // 统一转为小写
        value = value.toLowerCase();
        String[] xssArr = XSS_STR.split("\\|");
        for (int i = 0; i < xssArr.length; i++) {
            if (value.indexOf(xssArr[i]) > -1) {
                log.warn("请注意，存在SQL注入关键词---> {}", xssArr[i]);
                log.warn("请注意，值可能存在SQL注入风险!---> {}", value);
                throw new RuntimeException("请注意，值可能存在SQL注入风险!--->" + value);
            }
        }
        return;
    }

    /**
     * sql注入过滤处理，遇到注入关键字抛异常
     *
     * @param values
     * @return
     */
    public static void filterContent(String[] values) {
        String[] xssArr = XSS_STR.split("\\|");
        for (String value : values) {
            if (value == null || "".equals(value)) {
                return;
            }
            // 统一转为小写
            value = value.toLowerCase();
            for (int i = 0; i < xssArr.length; i++) {
                if (value.indexOf(xssArr[i]) > -1) {
                    log.info("请注意，存在SQL注入关键词---> {}", xssArr[i]);
                    log.info("请注意，值可能存在SQL注入风险!---> {}", value);
                    throw new RuntimeException("请注意，值可能存在SQL注入风险!--->" + value);
                }
            }
        }
        return;
    }

    /**
     * 注入过滤
     *
     * @param value
     * @return
     */
    @Deprecated
    public static void specialFilterContent(String value) {
        String specialXssStr = " exec | insert | select | delete | update | drop | count | chr | mid | master | truncate | char | declare |;|+|";
        String[] xssArr = specialXssStr.split("\\|");
        if (value == null || "".equals(value)) {
            return;
        }
        // 统一转为小写
        value = value.toLowerCase();
        for (int i = 0; i < xssArr.length; i++) {
            if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
                log.info("请注意，存在SQL注入关键词---> {}", xssArr[i]);
                log.info("请注意，值可能存在SQL注入风险!---> {}", value);
                throw new RuntimeException("请注意，值可能存在SQL注入风险!--->" + value);
            }
        }
        return;
    }


    /**
     * 注入过滤
     *
     * @param value
     * @return
     */
    @Deprecated
    public static void specialFilterContentForOnlineReport(String value) {
        String specialXssStr = " exec | insert | delete | update | drop | chr | mid | master | truncate | char | declare |";
        String[] xssArr = specialXssStr.split("\\|");
        if (value == null || "".equals(value)) {
            return;
        }
        // 统一转为小写
        value = value.toLowerCase();
        for (int i = 0; i < xssArr.length; i++) {
            if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
                log.info("请注意，存在SQL注入关键词---> {}", xssArr[i]);
                log.info("请注意，值可能存在SQL注入风险!---> {}", value);
                throw new RuntimeException("请注意，值可能存在SQL注入风险!--->" + value);
            }
        }
        return;
    }


    /**
     * 参数校验
     *
     * @param str ep: "or 1=1"
     */
    public static boolean isSqlValid(String str) {
        Matcher matcher = SQL_PATTERN.matcher(str);
        if (matcher.find()) {
            //获取非法字符：or
            log.info("参数存在非法字符，请确认：" + matcher.group());
            return false;
        }
        return true;
    }


    /**
     * sql注入过滤检查，遇到注入关键字抛异常
     */
    @SneakyThrows
    public static <T> void checkSqlInjection(T data) {
        Class clazz = data.getClass();
        List<Method> methodList = Lists.newArrayList();
        ReflectUtils.getClassMethods(clazz, methodList);
        for (Method method : methodList) {
            if (StringUtils.startsWith(method.getName(),"get") && !StringUtils.equals(method.getName(), "getClass") &&
                    method.getParameters().length == 0) {
                Object object = method.invoke(data);
                if (ObjectUtils.isNotEmpty(object)) {
                    checkDataInjection(object);
                }
            }
        }
    }

    protected static  <T> void checkDataInjection(T object) {
        if (object instanceof String ||
                object instanceof Boolean ||
                object instanceof Integer ||
                object instanceof Float ||
                object instanceof Double ||
                object instanceof Long) {
            filterContent(String.valueOf(object));
        } else if (object instanceof List) {
            ((List) object).forEach(e -> checkDataInjection(e));
        } else if (object instanceof Map) {
            ((Map) object).forEach((key, value) -> checkDataInjection(value));
        } else {
            checkSqlInjection(object);
        }
    }

    /**
     * mysql字段模糊查询，在页面输入%查询全部问题处理
     * mysql查询条件后面需要添加 escape '/'
     * like concat('%' ,#{param.zxbName}, '%') escape '/'
     */
    public static String escapeChar(String s) {
        if (StringUtils.isNotEmpty(s)) {
            s = s.replace("/","//").replaceAll("_", "/_").replaceAll("-", "/-").replaceAll("%", "/%");
        }
        return StringUtils.trim(s);
    }

    /**
     * mysql字段模糊查询，在页面输入%查询全部问题处理
     * mysql查询条件后面不能添加 escape '/'
     * like concat('%' ,#{param.zxbName}, '%')
     */
    public static String xxsEscape(String s) {
        if (StringUtils.isNotBlank(s) && s.matches(SQL_XSS_PATTERN_ALL.pattern())) {
            s = SQL_XSS_PATTERN.matcher(s).replaceAll("\\\\$0");
        }
        return s;
    }

}
