using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Web;
namespace Top.Api.Util
{
    /// 
    /// SPI请求校验结果。
    /// 
    public class CheckResult
    {
        public bool Success { get; set; }
        public string Body { get; set; }
    }
    /// 
    /// SPI服务提供方工具类。
    /// 
    public class SpiUtils
    {
        private const string TOP_SIGN_LIST = "top-sign-list";
        private static readonly string[] HEADER_FIELDS_IP = {"X-Real-IP", "X-Forwarded-For", "Proxy-Client-IP",
        "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
        /// 
        /// 校验SPI请求签名,不支持带上传文件的HTTP请求。
        /// 
        /// HttpRequest对象实例
        /// APP密钥
        /// 校验结果
        public static CheckResult CheckSign(HttpRequest request, string secret)
        {
            CheckResult result = new CheckResult();
            string ctype = request.ContentType;
            if (ctype.StartsWith(Constants.CTYPE_APP_JSON) || ctype.StartsWith(Constants.CTYPE_TEXT_XML) || ctype.StartsWith(Constants.CTYPE_TEXT_PLAIN) || ctype.StartsWith(Constants.CTYPE_APPLICATION_XML))
            {
                result.Body = GetStreamAsString(request, GetRequestCharset(ctype));
                result.Success = CheckSignInternal(request, result.Body, secret);
            }
            else if (ctype.StartsWith(Constants.CTYPE_FORM_DATA))
            {
                result.Success = CheckSignInternal(request, null, secret);
            }
            else
            {
                throw new TopException("Unspported SPI request");
            }
            return result;
        }
        /// 
        /// 校验SPI请求签名,适用于Content-Type为application/x-www-form-urlencoded或multipart/form-data的GET或POST请求。
        /// 
        /// 请求对象
        /// app对应的secret
        /// true:校验通过;false:校验不通过
        public static bool CheckSign4FormRequest(HttpRequest request, string secret)
        {
            return CheckSignInternal(request, null, secret);
        }
        /// 
        /// 校验SPI请求签名,适用于Content-Type为text/xml或text/json的POST请求。
        /// 
        /// 请求对象
        /// 请求体的文本内容
        /// app对应的secret
        /// true:校验通过;false:校验不通过
        public static bool CheckSign4TextRequest(HttpRequest request, string body, string secret)
        {
            return CheckSignInternal(request, body, secret);
        }
        private static bool CheckSignInternal(HttpRequest request, string body, string secret)
        {
            IDictionary parameters = new SortedDictionary(StringComparer.Ordinal);
            string charset = GetRequestCharset(request.ContentType);
            // 1. 获取header参数
            AddAll(parameters, GetHeaderMap(request, charset));
            // 2. 获取url参数
            Dictionary queryMap = GetQueryMap(request, charset);
            AddAll(parameters, queryMap);
            // 3. 获取form参数
            AddAll(parameters, GetFormMap(request));
            // 4. 生成签名并校验
            string remoteSign = null;
            if (queryMap.ContainsKey(Constants.SIGN))
            {
                remoteSign = queryMap[Constants.SIGN];
            }
            string localSign = Sign(parameters, body, secret, charset);
            return localSign.Equals(remoteSign);
        }
        private static void AddAll(IDictionary dest, IDictionary from)
        {
            if (from != null && from.Count > 0)
            {
                IEnumerator> em = from.GetEnumerator();
                while (em.MoveNext())
                {
                    KeyValuePair kvp = em.Current;
                    dest.Add(kvp.Key, kvp.Value);
                }
            }
        }
        /// 
        /// 签名规则:hex(md5(secret+sorted(header_params+url_params+form_params)+body)+secret)
        /// 
        private static string Sign(IDictionary parameters, string body, string secret, string charset)
        {
            IEnumerator> em = parameters.GetEnumerator();
            // 第1步:把所有参数名和参数值串在一起
            StringBuilder query = new StringBuilder(secret);
            while (em.MoveNext())
            {
                string key = em.Current.Key;
                if (!Constants.SIGN.Equals(key))
                {
                    string value = em.Current.Value;
                    query.Append(key).Append(value);
                }
            }
            if (body != null)
            {
                query.Append(body);
            }
            query.Append(secret);
            // 第2步:使用MD5加密
            MD5 md5 = MD5.Create();
            byte[] bytes = md5.ComputeHash(Encoding.GetEncoding(charset).GetBytes(query.ToString()));
            // 第3步:把二进制转化为大写的十六进制
            StringBuilder result = new StringBuilder();
            for (int i = 0; i < bytes.Length; i++)
            {
                result.Append(bytes[i].ToString("X2"));
            }
            return result.ToString();
        }
        private static string GetRequestCharset(string ctype)
        {
            string charset = "utf-8";
            if (!string.IsNullOrEmpty(ctype))
            {
                string[] entires = ctype.Split(';');
                foreach (string entry in entires)
                {
                    string _entry = entry.Trim();
                    if (_entry.StartsWith("charset"))
                    {
                        string[] pair = _entry.Split('=');
                        if (pair.Length == 2)
                        {
                            if (!string.IsNullOrEmpty(pair[1]))
                            {
                                charset = pair[1].Trim();
                            }
                        }
                        break;
                    }
                }
            }
            return charset;
        }
        public static Dictionary GetHeaderMap(HttpRequest request, string charset)
        {
            Dictionary headerMap = new Dictionary();
            string signList = request.Headers[TOP_SIGN_LIST];
            if (!string.IsNullOrEmpty(signList))
            {
                string[] keys = signList.Split(',');
                foreach (string key in keys)
                {
                    string value = request.Headers[key];
                    if (string.IsNullOrEmpty(value))
                    {
                        headerMap.Add(key, "");
                    }
                    else
                    {
                        headerMap.Add(key, HttpUtility.UrlDecode(value, Encoding.GetEncoding(charset)));
                    }
                }
            }
            return headerMap;
        }
        public static Dictionary GetQueryMap(HttpRequest request, string charset)
        {
            Dictionary queryMap = new Dictionary();
            string queryString = request.Url.Query;
            if (!string.IsNullOrEmpty(queryString))
            {
                queryString = queryString.Substring(1); // 忽略?号
                string[] parameters = queryString.Split('&');
                foreach (string parameter in parameters)
                {
                    string[] kv = parameter.Split('=');
                    if (kv.Length == 2)
                    {
                        string key = HttpUtility.UrlDecode(kv[0], Encoding.GetEncoding(charset));
                        string value = HttpUtility.UrlDecode(kv[1], Encoding.GetEncoding(charset));
                        queryMap.Add(key, value);
                    }
                    else if (kv.Length == 1)
                    {
                        string key = HttpUtility.UrlDecode(kv[0], Encoding.GetEncoding(charset));
                        queryMap.Add(key, "");
                    }
                }
            }
            return queryMap;
        }
        public static Dictionary GetFormMap(HttpRequest request)
        {
            Dictionary formMap = new Dictionary();
            NameValueCollection form = request.Form;
            string[] keys = form.AllKeys;
            foreach (string key in keys)
            {
                string value = request.Form[key];
                if (string.IsNullOrEmpty(value))
                {
                    formMap.Add(key, "");
                }
                else
                {
                    formMap.Add(key, value);
                }
            }
            return formMap;
        }
        public static string GetStreamAsString(HttpRequest request, string charset)
        {
            Stream stream = null;
            StreamReader reader = null;
            try
            {
                // 以字符流的方式读取HTTP请求体
                stream = request.InputStream;
                reader = new StreamReader(stream, Encoding.GetEncoding(charset));
                return reader.ReadToEnd();
            }
            finally
            {
                // 释放资源
                if (reader != null) reader.Close();
                if (stream != null) stream.Close();
            }
        }
        /// 
        /// 检查SPI请求到达服务器端是否已经超过指定的分钟数,如果超过则拒绝请求。
        /// 
        /// true代表不超过,false代表超过。
        public static bool CheckTimestamp(HttpRequest request, int minutes)
        {
            string ts = request.QueryString[Constants.TIMESTAMP];
            if (!string.IsNullOrEmpty(ts))
            {
                DateTime remote = DateTime.ParseExact(ts, Constants.DATE_TIME_FORMAT, null);
                DateTime local = DateTime.Now;
                return remote.AddMinutes(minutes).CompareTo(local) > 0;
            }
            else
            {
                return false;
            }
        }
        /// 
        /// 检查发起SPI请求的来源IP是否是TOP机房的出口IP。
        /// 
        /// HTTP请求对象
        /// TOP网关IP出口地址段列表,通过taobao.top.ipout.get获得
        /// true表达IP来源合法,false代表IP来源不合法
        public static bool CheckRemoteIp(HttpRequest request, List topIpList)
        {
            string ip = request.UserHostAddress;
            foreach (string ipHeader in HEADER_FIELDS_IP)
            {
                string realIp = request.Headers[ipHeader];
                if (!string.IsNullOrEmpty(realIp) && !"unknown".Equals(realIp))
                {
                    ip = realIp;
                    break;
                }
            }
            if (topIpList != null)
            {
                foreach (string topIp in topIpList)
                {
                    if (StringUtil.IsIpInRange(ip, topIp))
                    {
                        return true;
                    }
                }
            }
            return false;
        }
    }
}