HttpRequest的QueryString属性 的一点认识
我们开发asp.net程序获取QueryString时,经常性的遇到一些url编码问题,如:
当然我们一般都是按照提示来把framework版本设置2.0来解决。为什么可以这么解决了,还有没有其它的解决方法了。
先让我们看看QueryString的源代码吧:
public NameValueCollection QueryString { get { if ( this ._queryString == null ) { this ._queryString = new HttpValueCollection(); if ( this ._wr != null ) { this .FillInQueryStringCollection(); } this ._queryString.MakeReadOnly(); } if ( this ._flags[1]) { this ._flags.Clear(1); this .ValidateNameValueCollection( this ._queryString, RequestValidationSource.QueryString); } return this ._queryString; } } 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); } } |
先让我们插入一点 那就是QueryString默认已经做了url解码。 其中HttpValueCollection的 FillFromEncodedBytes方法如下
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); } } } |
从这里我们可以看到QueryString已经为我们做了解码工作,我们不需要写成 HttpUtility.HtmlDecode(Request.QueryString["xxx"])而是直接写成Request.QueryString["xxx"]就ok了。
现在让我们来看看你QueryString的验证,在代码中有
if (this._flags[1])
{
this._flags.Clear(1);
this.ValidateNameValueCollection(this._queryString, RequestValidationSource.QueryString);
}
一看this.ValidateNameValueCollection这个方法名称就知道是干什么的了,验证QueryString数据;那么在什么情况下验证的了?
让我们看看this._flags[1]在什么地方设置的:
public void ValidateInput() { if (! this ._flags[0x8000]) { this ._flags.Set(0x8000); this ._flags.Set(1); this ._flags.Set(2); this ._flags.Set(4); this ._flags.Set(0x40); this ._flags.Set(0x80); this ._flags.Set(0x100); this ._flags.Set(0x200); this ._flags.Set(8); } } |
而该方法在ValidateInputIfRequiredByConfig中调用,调用代码
internal void ValidateInputIfRequiredByConfig()
{
.........
if (httpRuntime.RequestValidationMode >= VersionUtil.Framework40)
{
this.ValidateInput();
}
}
而该方法是在HttpApplication中的private Exception ValidateHelper(HttpContext context)和它的内部类ValidateRequestExecutionStep构造函数中调用的。为什么这里会有两次调用我想大家应该很清楚,在IIS7有一个集成和经典模式吧,因为asp.net在管道处理中也有两套。
我想现在大家都应该明白为什么错题提示让我们把framework改为2.0了吧。应为在4.0后才验证。这种解决问题的方法是关闭验证,那么我们是否可以改变默认的验证规则了?
让我们看看ValidateNameValueCollection
private void ValidateNameValueCollection(NameValueCollection nvc, RequestValidationSource requestCollection) { int count = nvc.Count; for ( int i = 0; i < count; i++) { string key = nvc.GetKey(i); if ((key == null ) || !key.StartsWith( "__" , StringComparison.Ordinal)) { string str2 = nvc.Get(i); if (! string .IsNullOrEmpty(str2)) { this .ValidateString(str2, key, requestCollection); } } } } private void ValidateString( string value, string collectionKey, RequestValidationSource requestCollection) { int num; value = RemoveNullCharacters(value); if (!RequestValidator.Current.IsValidRequestString( this .Context, value, requestCollection, collectionKey, out num)) { string str = collectionKey + "=\"" ; int startIndex = num - 10; if (startIndex <= 0) { startIndex = 0; } else { str = str + "..." ; } int length = num + 20; if (length >= value.Length) { length = value.Length; str = str + value.Substring(startIndex, length - startIndex) + "\"" ; } else { str = str + value.Substring(startIndex, length - startIndex) + "...\"" ; } string requestValidationSourceName = GetRequestValidationSourceName(requestCollection); throw new HttpRequestValidationException(SR.GetString( "Dangerous_input_detected" , new object [] { requestValidationSourceName, str })); } } |
哦?原来一切都明白了,验证是在RequestValidator做的。
public class RequestValidator { // Fields private static RequestValidator _customValidator; private static readonly Lazy<RequestValidator> _customValidatorResolver = new Lazy<RequestValidator>( new Func<RequestValidator>(RequestValidator.GetCustomValidatorFromConfig)); // Methods private static RequestValidator GetCustomValidatorFromConfig() { HttpRuntimeSection httpRuntime = RuntimeConfig.GetAppConfig().HttpRuntime; Type userBaseType = ConfigUtil.GetType(httpRuntime.RequestValidationType, "requestValidationType" , httpRuntime); ConfigUtil.CheckBaseType( typeof (RequestValidator), userBaseType, "requestValidationType" , httpRuntime); return (RequestValidator) HttpRuntime.CreatePublicInstance(userBaseType); } internal static void InitializeOnFirstRequest() { RequestValidator local1 = _customValidatorResolver.Value; } private static bool IsAtoZ( char c) { return (((c >= 'a' ) && (c <= 'z' )) || ((c >= 'A' ) && (c <= 'Z' ))); } protected internal virtual bool IsValidRequestString(HttpContext context, string value, RequestValidationSource requestValidationSource, string collectionKey, out int validationFailureIndex) { if (requestValidationSource == RequestValidationSource.Headers) { validationFailureIndex = 0; return true ; } return !CrossSiteScriptingValidation.IsDangerousString(value, out validationFailureIndex); } // Properties public static RequestValidator Current { get { if (_customValidator == null ) { _customValidator = _customValidatorResolver.Value; } return _customValidator; } set { if (value == null ) { throw new ArgumentNullException( "value" ); } _customValidator = value; } } } |
主要的验证方法还是在CrossSiteScriptingValidation.IsDangerousString(value, out validationFailureIndex);而CrossSiteScriptingValidation是一个内部类,无法修改。
让我们看看CrossSiteScriptingValidation类大代码把
internal static class CrossSiteScriptingValidation { // Fields private static char [] startingChars = new char [] { '<' , '&' }; // Methods private static bool IsAtoZ( char c) { return (((c >= 'a' ) && (c <= 'z' )) || ((c >= 'A' ) && (c <= 'Z' ))); } internal static bool IsDangerousString( string s, out int matchIndex) { matchIndex = 0; int startIndex = 0; while ( true ) { int num2 = s.IndexOfAny(startingChars, startIndex); if (num2 < 0) { return false ; } if (num2 == (s.Length - 1)) { return false ; } matchIndex = num2; char ch = s[num2]; if (ch != '&' ) { if ((ch == '<' ) && ((IsAtoZ(s[num2 + 1]) || (s[num2 + 1] == '!' )) || ((s[num2 + 1] == '/' ) || (s[num2 + 1] == '?' )))) { return true ; } } else if (s[num2 + 1] == '#' ) { return true ; } startIndex = num2 + 1; } } internal static bool IsDangerousUrl( string s) { if ( string .IsNullOrEmpty(s)) { return false ; } s = s.Trim(); int length = s.Length; if (((((length > 4) && ((s[0] == 'h' ) || (s[0] == 'H' ))) && ((s[1] == 't' ) || (s[1] == 'T' ))) && (((s[2] == 't' ) || (s[2] == 'T' )) && ((s[3] == 'p' ) || (s[3] == 'P' )))) && ((s[4] == ':' ) || (((length > 5) && ((s[4] == 's' ) || (s[4] == 'S' ))) && (s[5] == ':' )))) { return false ; } if (s.IndexOf( ':' ) == -1) { return false ; } return true ; } internal static bool IsValidJavascriptId( string id) { if (! string .IsNullOrEmpty(id)) { return CodeGenerator.IsValidLanguageIndependentIdentifier(id); } return true ; } } |
结果我们发现&# <! </ <? <[a-zA-z] 这些情况验证都是通不过的。
所以我们只需要重写RequestValidator就可以了。RequestValidator的默认设置在HttpRuntime类中,调用顺序
ProcessRequest(HttpWorkerRequest wr)->ProcessRequestNoDemand(HttpWorkerRequest wr)
->ProcessRequestNow(HttpWorkerRequest wr)->ProcessRequestInternal(HttpWorkerRequest wr)
->EnsureFirstRequestInit(HttpContext context)->FirstRequestInit(HttpContext context)
private void FirstRequestInit(HttpContext context) { Exception exception = null ; if ((InitializationException == null ) && ( this ._appDomainId != null )) { try { using ( new ApplicationImpersonationContext()) { CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture; CultureInfo currentUICulture = Thread.CurrentThread.CurrentUICulture; try { InitHttpConfiguration(); CheckApplicationEnabled(); this .CheckAccessToTempDirectory(); this .InitializeHealthMonitoring(); this .InitRequestQueue(); this .InitTrace(context); HealthMonitoringManager.StartHealthMonitoringHeartbeat(); RestrictIISFolders(context); this .PreloadAssembliesFromBin(); this .InitHeaderEncoding(); HttpEncoder.InitializeOnFirstRequest(); RequestValidator.InitializeOnFirstRequest(); if (context.WorkerRequest is ISAPIWorkerRequestOutOfProc) { ProcessModelSection processModel = RuntimeConfig.GetMachineConfig().ProcessModel; } } finally { Thread.CurrentThread.CurrentUICulture = currentUICulture; SetCurrentThreadCultureWithAssert(currentCulture); } } } catch (ConfigurationException exception2) { exception = exception2; } catch (Exception exception3) { exception = new HttpException(SR.GetString( "XSP_init_error" , new object [] { exception3.Message }), exception3); } } if (InitializationException != null ) { throw new HttpException(InitializationException.Message, InitializationException); } if (exception != null ) { InitializationException = exception; throw exception; } AddAppDomainTraceMessage( "FirstRequestInit" ); } |
例如我们现在需要处理我们现在需要过滤QueryString中k=&...的情况
public class CustRequestValidator : RequestValidator { protected override bool IsValidRequestString(HttpContext context, string value, RequestValidationSource requestValidationSource, string collectionKey, out int validationFailureIndex) { validationFailureIndex = 0; //我们现在需要过滤QueryString中k=&...的情况 if (requestValidationSource == RequestValidationSource.QueryString&&collectionKey.Equals( "k" )&& value.StartsWith( "&" )) { return true ; } return base .IsValidRequestString(context, value, requestValidationSource, collectionKey, out validationFailureIndex); } } |
<httpRuntime requestValidationType="MvcApp.CustRequestValidator"/>
个人在这里只是提供一个思想,欢迎大家拍砖!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?