「Server.UrlDecode(Server.UrlEncode("北京")) == 「北京」」,先用UrlEncode編碼而後用UrlDecode解碼,這條語句永遠爲true嗎?答案是否認的,結果可能與不少人預想的不大同樣。本文主要分析這一問題出現的原理,研究下Server.UrlEncode(),Server.UrlDecode(),Request["xxx"]三個函數與編碼方式的關係。web
1. 問題出現的情景ajax
網站採用了GB2312編碼,在Web.config中添加以下配置。cookie
<system.web> <globalization requestEncoding="GB2312" responseEncoding="GB2312"/> </system.web>
測試頁面EncodeServerTest.aspx.cs代碼。app
protected void Page_Load(object sender, EventArgs e) { string s = Server.UrlDecode(Server.UrlEncode("北京")); bool isEqual = s == "北京"; }
測試頁面EncodeServerTest.aspx代碼。less
運行頁面,首次執行時,編碼解碼方式都爲GB2312,isEuqal=true;點擊頁面的button,經過ajax再次請求頁面,編碼方式仍爲GB2312,但解碼方式變成了UTF-8,因而s值成了亂碼,isEqual=false。下面兩個圖分別爲兩次執行的結果:ide
實際項目遇到問題的場景比這複雜,但也是由於UrlEncode編碼和UrlDecode解碼方式不一致形成的,本系列的第三篇會有實際項目場景的說明。要解釋這一現象,必須瞭解UrlEncode()和UrlDecode()的實現。函數
2. Server.UrlEncode()函數post
反編譯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(),獲取編碼方式並不同,二者成對使用結果並不必定正確,這個和咱們一般的認識不一致,須要特別注意。