using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Performance.DtoModels;
using Performance.DtoModels.AppSettings;

namespace Performance.Api
{
    /// <summary>
    /// 防止SQL注入
    /// </summary>
    public class AntiSqlInject
    {
        public static AntiSqlInject Instance { get; } = new();

        /// <summary>
        /// 初始化过滤方法
        /// </summary>
        static AntiSqlInject()
        {
            SqlKeywordsArray.AddRange(SqlSeparatKeywords.Split('|'));
            SqlKeywordsArray.AddRange(Array.ConvertAll(SqlCommandKeywords.Split('|'), h => h + " "));
            SqlKeywordsArray.AddRange(Array.ConvertAll(SqlCommandKeywords.Split('|'), h => " " + h));
        }

        //private const string SqlCommandKeywords = "and|exec|execute|insert|select|delete|update|count|chr|mid|master|char|declare|sitename|net user|xp_cmdshell|or|create|drop|table|from|grant|use|group_concat|column_name|information_schema.columns|table_schema|union|where|select|delete|update|orderhaving|having|by|count|*|truncate|like";
        //private const string SqlSeparatKeywords = "'|;|--|\'|\"|/*|%|#";

        //private static string StrKeyWord = "select|insert|delete|from|count(|drop table|update|truncate|asc(|mid(|char(|xp_cmdshell|exec|master|net local group administrators|net user|or|and";
        //private static string StrSymbol = ";|(|)|[|]|{|}|%|@|*|'|!";

        private const string SqlCommandKeywords = "*|and|asc(|by|char|char(|chr|column_name|count|count(|create|declare|delete|drop|drop table|exec|execute|from|grant|group_concat|having|information_schema.columns|insert|like|master|mid|mid(|net local group administrators|net user|or|orderhaving|select|sitename|table|table_schema|truncate|union|update|use|where|xp_cmdshell";
        private const string SqlSeparatKeywords = "--|;|!|'|\"|(|)|[|]|{|}|*|/*|#|%";
        private static readonly List<string> SqlKeywordsArray = new List<string>();

        /// <summary>  
        /// 检查字符串是否安全,是否包含Sql注入关键字  
        /// <param name="input">被检查的字符串</param>  
        /// <returns>如果包含sql注入关键字，返回：false;否则返回：true</returns>  
        ///</summary>  
        public bool IsSafetySql(string input)
        {
            if (string.IsNullOrWhiteSpace(input))
            {
                return true;
            }
            input = HttpUtility.UrlDecode(input).ToLower();

            foreach (var sqlKeyword in SqlKeywordsArray)
            {
                if (input.IndexOf(sqlKeyword, StringComparison.Ordinal) >= 0)
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// 返回安全字符串
        /// </summary>
        /// <param name="input">输入</param>
        /// <returns>返回</returns>
        public string GetSafetySql(string input)
        {
            if (string.IsNullOrEmpty(input))
            {
                return string.Empty;
            }
            if (IsSafetySql(input)) { return input; }
            input = HttpUtility.UrlDecode(input).ToLower();

            foreach (var sqlKeyword in SqlKeywordsArray)
            {
                if (input.IndexOf(sqlKeyword, StringComparison.Ordinal) >= 0)
                {
                    input = input.Replace(sqlKeyword, string.Empty);
                }
            }
            return input;
        }
    }

    /// <summary>
    /// SQL注入过滤器
    /// </summary>
    public class AntiSqlInjectFilter : IAsyncActionFilter
    {
        private readonly Application _application;
        private readonly ILogger<AntiSqlInjectFilter> _logger;

        public AntiSqlInjectFilter(ILogger<AntiSqlInjectFilter> logger, IOptions<Application> options)
        {
            _application = options.Value;
            _logger = logger;
        }

        /// <inheritdoc />
        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            if (_application.OpenAntiSqlInject == true)
            {
                var routePath = context.HttpContext.Request.Path.ToString();
                try
                {
                    if (_application.AntiSqlInjectRouteWhite?.Any(route => Regex.IsMatch(routePath, route, RegexOptions.IgnoreCase)) != true)
                    {
                        foreach (var value in context.ActionArguments.Where(w => w.Value != null).Select(w => w.Value))
                        {
                            //如果不是值类型或接口，不需要过滤
                            var pType = value.GetType();
                            if (!pType.IsClass) continue;

                            //对string类型过滤 
                            if (value is string)
                            {
                                if (!AntiSqlInject.Instance.IsSafetySql(value.ToString()))
                                {
                                    var response = new ApiResponse(ResponseType.Dangerous, "危险操作已拦截");
                                    context.Result = new ObjectResult(response);
                                    return;
                                }
                            }
                            else if (value is not IFormCollection)
                            {
                                if (value is string)
                                {
                                    if (!AntiSqlInject.Instance.IsSafetySql(value.ToString()))
                                    {
                                        var response = new ApiResponse(ResponseType.Dangerous, "危险操作已拦截");
                                        context.Result = new ObjectResult(response);
                                        return;
                                    }
                                }
                                else if (value is IEnumerable objects)
                                {
                                    foreach (var item in objects)
                                    {
                                        //是一个class，对class的属性中，string类型的属性进行过滤
                                        var properties = pType.GetProperties();
                                        foreach (var pp in properties)
                                        {
                                            var temp = pp.GetValue(item);
                                            if (temp == null) continue;

                                            if (temp is string)
                                            {
                                                if (!AntiSqlInject.Instance.IsSafetySql(temp.ToString()))
                                                {
                                                    var response = new ApiResponse(ResponseType.Dangerous, "危险操作已拦截");
                                                    context.Result = new ObjectResult(response);
                                                    return;
                                                }
                                            }
                                        }
                                    }
                                }
                                else
                                {
                                    //是一个class，对class的属性中，string类型的属性进行过滤
                                    var properties = pType.GetProperties();
                                    foreach (var pp in properties)
                                    {
                                        var temp = pp.GetValue(value);
                                        if (temp == null) continue;

                                        if (temp is string)
                                        {
                                            if (!AntiSqlInject.Instance.IsSafetySql(temp.ToString()))
                                            {
                                                var response = new ApiResponse(ResponseType.Dangerous, "危险操作已拦截");
                                                context.Result = new ObjectResult(response);
                                                return;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError($"SQL注入过滤器出现异常：{routePath};{ex}");
                }
            }
            await next();
        }

    }
}
