服务器 解码 芯片,服务器端解码原理

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

1. 问题出现的情景

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

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

protected void Page_Load(object sender, EventArgs e)

{

string s = Server.UrlDecode(Server.UrlEncode("北京"));

bool isEqual = s == "北京";

}

测试页面EncodeServerTest.aspx代码。

8f900a89c6347c561fdf2122f13be562.png View Code

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

881c00ff6e881812c9d8e87436050ab1.png

3302f04c928445850aeae1825cc654a2.png

实际项目遇到问题的场景比这复杂,但也是因为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的实现:

48304ba5e6f9fe08f3fa1abda7d326ab.png

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;

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

结论: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的实现:

48304ba5e6f9fe08f3fa1abda7d326ab.png

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);

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

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

8f900a89c6347c561fdf2122f13be562.png 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赋值应该不会被执行。

961ddebeb323a10fe0623af514929fc1.png

48304ba5e6f9fe08f3fa1abda7d326ab.png

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;

}

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

4. Request["xxx"]

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

48304ba5e6f9fe08f3fa1abda7d326ab.png

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;

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

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

961ddebeb323a10fe0623af514929fc1.png

48304ba5e6f9fe08f3fa1abda7d326ab.png

//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;

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

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

961ddebeb323a10fe0623af514929fc1.png

48304ba5e6f9fe08f3fa1abda7d326ab.png

//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);

}

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值