zoukankan      html  css  js  c++  java
  • 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代码。

     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;
            }
    复制代码

             从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;
                    }
                }
            }
    复制代码

             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;
                }
            }
    复制代码

             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);
                    }
                }
            }
    复制代码

             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(),获取编码方式并不一样,两者成对使用结果并不一定正确,这个和我们通常的认识不一致,需要特别注意。

  • 相关阅读:
    UVA 10462 Is There A Second Way Left?(次小生成树&Prim&Kruskal)题解
    POJ 1679 The Unique MST (次小生成树)题解
    POJ 2373 Dividing the Path (单调队列优化DP)题解
    BZOJ 2709 迷宫花园
    BZOJ 1270 雷涛的小猫
    BZOJ 2834 回家的路
    BZOJ 2506 calc
    BZOJ 3124 直径
    BZOJ 4416 阶乘字符串
    BZOJ 3930 选数
  • 原文地址:https://www.cnblogs.com/littleCode/p/4766557.html
Copyright © 2011-2022 走看看