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

個人在這里只是提供一個思想,歡迎大家拍磚!


免責聲明!

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



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