• Request 接收参数乱码原理解析一:服务器端解码原理


             “Server.UrlDecode(Server.UrlEncode("北京")) == “北京””,先用UrlEncode编码然后用UrlDecode解码,这条语句永远为true吗?答案是否定的,结果可能与很多人预想的不大一样。本文主要分析这一问题出现的原理,研究下Server.UrlEncode(),Server.UrlDecode(),Request["xxx"]三个函数与编码方式的关系。

             1. 问题出现的情景

             网站采用了GB2312编码,在Web.config中添加如下配置。

      <system.web>
        <globalization requestEncoding="GB2312" responseEncoding="GB2312"/>
      </system.web>

             测试页面EncodeServerTest.aspx.cs代码。

            protected void Page_Load(object sender, EventArgs e)
            {
                string s = Server.UrlDecode(Server.UrlEncode("北京"));
                bool isEqual = s == "北京";
            }

             测试页面EncodeServerTest.aspx代码。

    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>页面编码测试(服务器端)</title>
        <script type="text/javascript" src="Scripts/jquery-2.1.1.min.js"></script>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <input type="button" name="btnAjaxPost" value="AJax提交" onclick="Ajax()" />
                <div id="divMessage" style="color: red"></div>
            </div>
        </form>
        <script type="text/javascript">
            function Ajax() {
                $.ajax({
                    type: "POST",
                    url: "EncodeServerTest.aspx",
                    data: {name:"name"},
                    success: function (data) {
                        $("#divMessage").html(data);
                    }
                });
            }
    
        </script>
    </body>
    </html>
    View Code

             运行页面,首次执行时,编码解码方式都为GB2312,isEuqal=true;点击页面的button,通过ajax再次请求页面,编码方式仍为GB2312,但解码方式变成了UTF-8,于是s值成了乱码,isEqual=false。下面两个图分别为两次执行的结果:

             实际项目遇到问题的场景比这复杂,但也是因为UrlEncode编码和UrlDecode解码方式不一致造成的,本系列的第三篇会有实际项目场景的说明。要解释这一现象,必须了解UrlEncode()和UrlDecode()的实现。

             2. Server.UrlEncode()函数

             反编译UrlEncode()函数,实现如下:

            public string UrlEncode(string s)
            {
                Encoding e = (this._context != null) ? this._context.Response.ContentEncoding : Encoding.UTF8;
                return HttpUtility.UrlEncode(s, e);
            }

             从源码可以看出,有上下文时用的是Response.ContentEncoding,没有上下文时默认用UTF-8编码。关键是Response.ContentEncoding的实现,继续反编译ContentEncoding的实现:

            public Encoding ContentEncoding
            {
                get
                {
                    if (this._encoding == null)
                    {
                        GlobalizationSection globalization = RuntimeConfig.GetLKGConfig(this._context).Globalization;
                        if (globalization != null)
                        {
                            this._encoding = globalization.ResponseEncoding;
                        }
                        if (this._encoding == null)
                        {
                            this._encoding = Encoding.Default;
                        }
                    }
                    return this._encoding;
                }
            }

             结论:UrlEncode()函数,优先从取配置文件的Globalization结点获取,如果配置文件没有的话用Encoding.Default,最后默认用Encoding.UTF8。

             3. Server.UrlDecode()函数

             反编译UrlEncode()函数,实现如下:

            public string UrlDecode(string s)
            {
                Encoding e = (this._context != null) ? this._context.Request.ContentEncoding : Encoding.UTF8;
                return HttpUtility.UrlDecode(s, e);
            }

             从源码可以看出,有上下文时用的是Request.ContentEncoding,没有上下文时默认用UTF-8编码。关键是Request.ContentEncoding的实现,继续反编译ContentEncoding的实现:

            public Encoding ContentEncoding
            {
                get
                {
                    if (!this._flags[0x20] || (this._encoding == null))
                    {
                        this._encoding = this.GetEncodingFromHeaders();
                        if ((this._encoding is UTF7Encoding) && !AppSettings.AllowUtf7RequestContentEncoding)
                        {
                            this._encoding = null;
                        }
                        if (this._encoding == null)
                        {
                            GlobalizationSection globalization = RuntimeConfig.GetLKGConfig(this._context).Globalization;
                            this._encoding = globalization.RequestEncoding;
                        }
                        this._flags.Set(0x20);
                    }
                    return this._encoding;
                }
                set
                {
                    this._encoding = value;
                    this._flags.Set(0x20);
                }
            }

             从源码可以看出,Request.ContentEncoding先通过函数GetEncodingFromHeaders()获取,如果获取不到,则从配置文件获取,接下来看GetEncodingFromHeaders()的实现:

            private Encoding GetEncodingFromHeaders()
            {
                if ((this.UserAgent != null) && CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this.UserAgent, "UP"))
                {
                    string str = this.Headers["x-up-devcap-post-charset"];
                    if (!string.IsNullOrEmpty(str))
                    {
                        try
                        {
                            return Encoding.GetEncoding(str);
                        }
                        catch
                        {
                        }
                    }
                }
                if (!this._wr.HasEntityBody())
                {
                    return null;
                }
                string contentType = this.ContentType;
                if (contentType == null)
                {
                    return null;
                }
                string attributeFromHeader = GetAttributeFromHeader(contentType, "charset");
                if (attributeFromHeader == null)
                {
                    return null;
                }
                Encoding encoding = null;
                try
                {
                    encoding = Encoding.GetEncoding(attributeFromHeader);
                }
                catch
                {
                }
                return encoding;
            }
    View Code

             从GetEncodingFromHeaders()的源码可以看出,先从HTTP请求头(x-up-devcap-post-charset或者charset)获取编码信息,如果编码合法的话则采用HTTP请求头指定的编码方式解码。

             结论:UrlDecode()函数,优先从HTTP请求头(x-up-devcap-post-charset或者charset)获取编码,如果没指定的话从取配置文件的Globalization结点获取,最后默认Encoding.UTF8。

             通过对UrlEncode()和UrlDecode()源码的分析,可以看出两者在确定编码上并不一致,UrlDecode()和HTTP请求的头有关,而通过Fiddler对比EncodeServerTest.aspx页面的两次请求,发现通过Ajax方式的请求,请求头正好多了“Content-Type:application/x-www-form-urlencoded; charset=UTF-8”一句,文章开始的问题得以解释。

             补充:获取Response.ContentEncoding和Request.ContentEncoding时,还有一个重要的函数”GlobalizationSection globalization = RuntimeConfig.GetLKGConfig(this._context).Globalization“,网上关于这个函数的资料很少,反编译后代码也很复杂,看的云里雾里,下面摘录一部分代码,从总可以猜测这个函数的功能:根据配置文件的继承关系,取配置文件中Globalization结点的Request和Response编码方式,如果没取到的话默认取UTF-8编码,个人感觉获取Request.ContentEncoding时的分支Encoding.Default赋值应该不会被执行。

            internal static RuntimeConfig GetLKGConfig(HttpContext context)
            {
                RuntimeConfig lKGRuntimeConfig = null;
                bool flag = false;
                try
                {
                    lKGRuntimeConfig = GetConfig(context);
                    flag = true;
                }
                catch
                {
                }
                if (!flag)
                {
                    lKGRuntimeConfig = GetLKGRuntimeConfig(context.Request.FilePathObject);
                }
                return lKGRuntimeConfig.RuntimeConfigLKG;
            }
    
            //先取网站的配置文件,然后取本机的配置文件
            private static RuntimeConfig GetLKGRuntimeConfig(VirtualPath path)
            {
                try
                {
                    path = path.Parent;
                }
                catch
                {
                    path = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPathObject;
                }
                while (path != null)
                {
                    try
                    {
                        return GetConfig(path);
                    }
                    catch
                    {
                        path = path.Parent;
                    }
                }
                try
                {
                    return GetRootWebConfig();
                }
                catch
                {
                }
                try
                {
                    return GetMachineConfig();
                }
                catch
                {
                }
                return GetNullRuntimeConfig();
            }
    
            //配置文件有的话,返回配置文件的编码方式;配置文件没有的话返回UTF-8编码方式
            //感觉获取Request.ContentEncoding时的Encoding.Default应该不会被执行
            [ConfigurationProperty("responseEncoding", DefaultValue = "utf-8")]
            public Encoding ResponseEncoding
            {
                get
                {
                    if (this.responseEncodingCache == null)
                    {
                        this.responseEncodingCache = Encoding.UTF8;
                    }
                    return this.responseEncodingCache;
                }
                set
                {
                    if (value != null)
                    {
                        base[_propResponseEncoding] = value.WebName;
                        this.responseEncodingCache = value;
                    }
                    else
                    {
                        base[_propResponseEncoding] = value;
                        this.responseEncodingCache = Encoding.UTF8;
                    }
                }
            }
    
            //配置文件有的话,返回配置文件的编码方式;配置文件没有的话返回UTF-8编码方式
            [ConfigurationProperty("requestEncoding", DefaultValue = "utf-8")]
            public Encoding RequestEncoding
            {
                get
                {
                    if (this.requestEncodingCache == null)
                    {
                        this.requestEncodingCache = Encoding.UTF8;
                    }
                    return this.requestEncodingCache;
                }
                set
                {
                    if (value != null)
                    {
                        base[_propRequestEncoding] = value.WebName;
                        this.requestEncodingCache = value;
                    }
                    else
                    {
                        base[_propRequestEncoding] = value;
                        this.requestEncodingCache = Encoding.UTF8;
                    }
                }
            }
    View Code

             4. Request["xxx"]

             Request[key],根据指定的key,依次访问QueryString,Form,Cookies,ServerVariables这4个集合,如果在任意一个集合中找到了,就立即返回。通常如果请求是用GET方法发出的,那我们一般是访问QueryString去获取用户的数据,如果请求是用POST方法提交的, 我们一般使用Form去访问用户提交的表单数据。

            public string this[string key]
            {
                get
                {
                    string str = this.QueryString[key];
                    if (str != null)
                    {
                        return str;
                    }
                    str = this.Form[key];
                    if (str != null)
                    {
                        return str;
                    }
                    HttpCookie cookie = this.Cookies[key];
                    if (cookie != null)
                    {
                        return cookie.Value;
                    }
                    str = this.ServerVariables[key];
                    if (str != null)
                    {
                        return str;
                    }
                    return null;
                }
            }

             Request.QueryString[key]实现源码如下,从中可以看到经过层层调用,最终调用的是”base.Add(HttpUtility.UrlDecode(str, encoding), HttpUtility.UrlDecode(str2, encoding));“添加到集合中,而是用的解码方式encoding和Server.UrlDecode()函数是一致的,都是Request.ContentEncoding

            //QueryString[key]实现
            public NameValueCollection QueryString
            {
                get
                {
                    this.EnsureQueryString();
                    if (this._flags[1])
                    {
                        this._flags.Clear(1);
                        this.ValidateHttpValueCollection(this._queryString, RequestValidationSource.QueryString);
                    }
                    return this._queryString;
                }
            }
            //QueryString[key]调用EnsureQueryString()初始化数据
            internal HttpValueCollection EnsureQueryString()
            {
                if (this._queryString == null)
                {
                    this._queryString = new HttpValueCollection();
                    if (this._wr != null)
                    {
                        this.FillInQueryStringCollection();
                    }
                    this._queryString.MakeReadOnly();
                }
                return this._queryString;
            }
    
            //FillInQueryStringCollection()函数解码,用的解码方式为QueryStringEncoding
            private void FillInQueryStringCollection()
            {
                byte[] queryStringBytes = this.QueryStringBytes;
                if (queryStringBytes != null)
                {
                    if (queryStringBytes.Length != 0)
                    {
                        this._queryString.FillFromEncodedBytes(queryStringBytes, this.QueryStringEncoding);
                    }
                }
                else if (!string.IsNullOrEmpty(this.QueryStringText))
                {
                    this._queryString.FillFromString(this.QueryStringText, true, this.QueryStringEncoding);
                }
            }
    
            //解码函数
            internal void FillFromString(string s, bool urlencoded, Encoding encoding)
            {
                int num = (s != null) ? s.Length : 0;
                for (int i = 0; i < num; i++)
                {
                    this.ThrowIfMaxHttpCollectionKeysExceeded();
                    int startIndex = i;
                    int num4 = -1;
                    while (i < num)
                    {
                        char ch = s[i];
                        if (ch == '=')
                        {
                            if (num4 < 0)
                            {
                                num4 = i;
                            }
                        }
                        else if (ch == '&')
                        {
                            break;
                        }
                        i++;
                    }
                    string str = null;
                    string str2 = null;
                    if (num4 >= 0)
                    {
                        str = s.Substring(startIndex, num4 - startIndex);
                        str2 = s.Substring(num4 + 1, (i - num4) - 1);
                    }
                    else
                    {
                        str2 = s.Substring(startIndex, i - startIndex);
                    }
                    if (urlencoded)
                    {
                        base.Add(HttpUtility.UrlDecode(str, encoding), HttpUtility.UrlDecode(str2, encoding));
                    }
                    else
                    {
                        base.Add(str, str2);
                    }
                    if ((i == (num - 1)) && (s[i] == '&'))
                    {
                        base.Add(null, string.Empty);
                    }
                }
            }
    
            //QueryString[key]调用的解码方式为ContentEncoding,和Server.UrlDecode()一致
            internal Encoding QueryStringEncoding
            {
                get
                {
                    Encoding contentEncoding = this.ContentEncoding;
                    if (!contentEncoding.Equals(Encoding.Unicode))
                    {
                        return contentEncoding;
                    }
                    return Encoding.UTF8;
                }
            }
    View Code

             Request.Form[key]实现源码如下,从中可以看到经过层层调用,最终调用的是”HttpUtility.UrlDecode(bytes, num4 + 1, (i - num4) - 1, encoding);“添加到集合中,而调用的解码方式encoding和Server.UrlDecode()函数是一致的,都是Request.ContentEncoding

            //Form[key]实现
            public NameValueCollection Form
            {
                get
                {
                    this.EnsureForm();
                    if (this._flags[2])
                    {
                        this._flags.Clear(2);
                        this.ValidateHttpValueCollection(this._form, RequestValidationSource.Form);
                    }
                    return this._form;
                }
            }
            internal HttpValueCollection EnsureForm()
            {
                if (this._form == null)
                {
                    this._form = new HttpValueCollection();
                    if (this._wr != null)
                    {
                        this.FillInFormCollection();
                    }
                    this._form.MakeReadOnly();
                }
                return this._form;
            }
    
            private void FillInFormCollection()
            {
                if ((this._wr != null) && this._wr.HasEntityBody())
                {
                    string contentType = this.ContentType;
                    if ((contentType != null) && (this._readEntityBodyMode != System.Web.ReadEntityBodyMode.Bufferless))
                    {
                        if (StringUtil.StringStartsWithIgnoreCase(contentType, "application/x-www-form-urlencoded"))
                        {
                            byte[] bytes = null;
                            HttpRawUploadedContent entireRawContent = this.GetEntireRawContent();
                            if (entireRawContent != null)
                            {
                                bytes = entireRawContent.GetAsByteArray();
                            }
                            if (bytes == null)
                            {
                                return;
                            }
                            try
                            {
                                this._form.FillFromEncodedBytes(bytes, this.ContentEncoding);
                                return;
                            }
                            catch (Exception exception)
                            {
                                throw new HttpException(System.Web.SR.GetString("Invalid_urlencoded_form_data"), exception);
                            }
                        }
                        if (StringUtil.StringStartsWithIgnoreCase(contentType, "multipart/form-data"))
                        {
                            MultipartContentElement[] multipartContent = this.GetMultipartContent();
                            if (multipartContent != null)
                            {
                                for (int i = 0; i < multipartContent.Length; i++)
                                {
                                    if (multipartContent[i].IsFormItem)
                                    {
                                        this._form.ThrowIfMaxHttpCollectionKeysExceeded();
                                        this._form.Add(multipartContent[i].Name, multipartContent[i].GetAsString(this.ContentEncoding));
                                    }
                                }
                            }
                        }
                    }
                }
            }
    
            internal void FillFromEncodedBytes(byte[] bytes, Encoding encoding)
            {
                int num = (bytes != null) ? bytes.Length : 0;
                for (int i = 0; i < num; i++)
                {
                    string str;
                    string str2;
                    this.ThrowIfMaxHttpCollectionKeysExceeded();
                    int offset = i;
                    int num4 = -1;
                    while (i < num)
                    {
                        byte num5 = bytes[i];
                        if (num5 == 0x3d)
                        {
                            if (num4 < 0)
                            {
                                num4 = i;
                            }
                        }
                        else if (num5 == 0x26)
                        {
                            break;
                        }
                        i++;
                    }
                    if (num4 >= 0)
                    {
                        str = HttpUtility.UrlDecode(bytes, offset, num4 - offset, encoding);
                        str2 = HttpUtility.UrlDecode(bytes, num4 + 1, (i - num4) - 1, encoding);
                    }
                    else
                    {
                        str = null;
                        str2 = HttpUtility.UrlDecode(bytes, offset, i - offset, encoding);
                    }
                    base.Add(str, str2);
                    if ((i == (num - 1)) && (bytes[i] == 0x26))
                    {
                        base.Add(null, string.Empty);
                    }
                }
            }
    View Code

             Request.Cookies[key],最终没有调用解码函数,只是把HTTP请求中Cookie值取出来了,如果存储Cookie时,对数据进行了编码处理,通过Request.Cookies[key]获取到Cookie值,需要调用对应的解码函数进行解码。最好调用函数HttpUtility.UrlDecode(str, encoding)解码,以免因为HTTP请求不同造成解码方式不同而出错(对应Server.UrlDecode()函数)。

             5. 本文结论

             Request.QueryString[key]、Request.Form[key]默认都会调用函数HttpUtility.UrlDecode(str, encoding),如果HTTP请求的数据只经过一次编码,无需再调用解码函数;Request.Cookies[key]没用调用解码函数,获取到值后需要调用正确的解码函数才能得到正确的值。

             Request.QueryString[key]、Request.Form[key]、Server.UrlDecode(),解码方式获取是一致的,都是优先从HTTP请求头(x-up-devcap-post-charset或者charset)获取编码,如果没指定的话从取配置文件的Globalization结点获取,最后默认Encoding.UTF8。

             Server.UrlEncode()解码方式,优先从取配置文件的Globalization结点获取,如果配置文件没有的话用Encoding.Default,最后默认用Encoding.UTF8。

             Server.UrlEncode()和Server.UrlDecode(),获取编码方式并不一样,两者成对使用结果并不一定正确,这个和我们通常的认识不一致,需要特别注意。

             参考:Request 接收参数乱码原理解析你不知道的 页面编码,浏览器选择编码,get,post各种乱码由来

  • 相关阅读:
    VIM快捷键(转)
    VIM中文乱码
    vsftpd.conf 联机手册
    keepalived nginx 主备配置
    Keepalived 主备配置
    Linux centos7 安装 keepalived-2.0.6
    Linux centos开机执行JAR Shell脚本
    Nginx负载均衡案例
    Windows虚拟机安装Linux系统
    Linux centos7 redis安装教程
  • 原文地址:https://www.cnblogs.com/freshman0216/p/4165949.html
Copyright © 2020-2023  润新知