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各種亂碼由來


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM