一個較完整的關鍵字過濾解決方案(中)

作者: Jeffrey Zhao  來源: 博客園  發布時間: 2008-12-24 11:10  閱讀: 2497 次  推薦: 0   原文鏈接   [收藏]  
 
[1] 一個較完整的關鍵字過濾解決方案(中)
[2] 一個較完整的關鍵字過濾解決方案(中)

問題遠沒結束

  上面的問題解決了沒有?哦哦,我是指采取命名約定的方式來改變過濾行為。當然有問題,不過我這里提一下比較重要的兩個:

  首先,就是“改名”這種行為——究竟是否方便?還記得我們的需求嗎(提示一下:方便、通用……)?如果采取上面的命名約定方案,我們可能就需要在頁面的前端和后端都不斷地改名,一會兒加-noffw,一會兒加-json。如果項目只由您來負責這還好辦,只是麻煩一些,但是如果您的團隊中的前臺開發人員性格古怪,固執己見,不愿配合怎么辦(打架我喜歡,可惜不能直接解決問題)?再者,假如您除了一個FilterForbiddenWordModule之外還有類似的“FilterScriptInjectionModule”怎么辦(別真寫這么一個HttpModule,不合適,老趙只是想不出一個恰當的例子了)?如果兩個Module都采取命名約定的方式,那么如何制定一個兩者能同時認同的約定就也是個麻煩事。

  再者,命名真是我們可以控制的嗎?某些情況下好說,但是假如您在使用WebForms中的控件怎么辦?WebForm中的一個重要特性就是用過Naming Container來避免客戶端ID的沖突。假設我們的頁面是放在一個Master Page中ID為Main的ContentPlaceHolder中,那么ID為txtPassword的文本框在客戶端里生成的HTML便會如下所示——那么我們又能有什么辦法可以做到“命名約定”嗎?

<input name="ctl00$Main$txtPassword" id="ctl00_Main_txtPassword">input>

  嘿,看來這種命名約定的方式有時候真不是那么通用啊。那么我就來設法解決WebForm這個問題。

  其實如果要解決WebForm這個問題,說白了就是要設法可以讓服務器端明確指定一些字段的處理方式。這種“特殊”則意味著對于過濾方式的判斷必須與特定的Page——泛化一下,HttpHandler進行綁定。這里我先談一下我的第一個想法:使用Custom Attribute進行標記的方式。我們構造一個FilterForbiddenWordAttribute,其中包含一個抽象GetFilterType方法根據key來指定過濾方式:

public enum FilterForbiddenWordType
{
    Ignored,
    Normal,
    Json,
    Html
}

public abstract class FilterForbiddenWordAttribute : Attribute
{
    public abstract FilterForbiddenWordType GetFilterType(string key);
}

  我們如果有特別的需求,就可以通過定義一個FilterForbiddenWordHandlerAttribute的子類,重載GetFilterType方法,然后標記在HttpHandler上。如下:

public class DefaultFilterForbiddenWordAttribute :
    FilterForbiddenWordAttribute
{
    public override FilterForbiddenWordType GetFilterType(string key)
    {
        if (key.EndsWith("txtPassword"))
        {
            return FilterForbiddenWordType.Ignored;
        }

        return FilterForbiddenWordType.Normal;
    }
}

[DefaultFilterForbiddenWord]
public partial class Default : System.Web.UI.Page
{
    ...
}

  當然,我們還需要對FilterForbiddenWordModule進行一些修改才能使之生效(朋友們可以先不要看代碼,想想這次改變的關鍵在哪里?):

public class FilterForbiddenWordModule : IHttpModule
{
    ...

    void IHttpModule.Init(HttpApplication context)
    {
        context.PostMapRequestHandler += new EventHandler(OnPostMapRequestHandler);
    }

    private static void OnPostMapRequestHandler(object sender, EventArgs e)
    {
        var context = (sender as HttpApplication).Context;
        var handlerType = context.Handler.GetType();
        var filter = ((FilterForbiddenWordAttribute[])handlerType.GetCustomAttributes(
            typeof(FilterForbiddenWordAttribute), true)).FirstOrDefault(); 

        ProcessCollection(context.Request.QueryString, filter);
        ProcessCollection(context.Request.Form, filter);
    }

    private static void ProcessCollection(
        NameValueCollection collection,
        FilterForbiddenWordAttribute filter)
    {
        var copy = new NameValueCollection();

        foreach (string key in collection.AllKeys)
        {
            var filterType = (filter == null) ? FilterForbiddenWordType.Normal
                : filter.GetFilterType(key);

            Array.ForEach(
                collection.GetValues(key),
                v => copy.Add(key, ForbiddenWord.Filter(v, filterType)));
        }

        ...
    }
}

  修改示例。例如我們在頁面上放置兩個文本框txtPassword和txtNormal:

<asp:TextBox ID="txtPassword" runat="server" TextMode="MultiLine" />
<asp:TextBox ID="txtNormal" runat="server" TextMode="MultiLine" />
<asp:Button ID="Button1" runat="server" Text="Click" />

 

  點擊,效果不言而喻:

  公布答案:因為我們需要等到確認了HttpHandler類型才能獲得FilterForbiddenWordAttribute標記信息,所以這次更新的關鍵是我們必須推遲進行過濾的時機。推遲到哪個階段?自然是能夠確定HttpHandler類型的最早時機,PostMapRequestHandler。我們通過反射來獲取Handler類型上的FilterForbiddenWordAttribute子類的信息,作為Filter傳入帶有額外參數的ProcessCollection方法中。ProcessCollection方法內部會調用根據filter參數來確定某個key的過濾方式:正常(當作純文本進行過濾)、忽略(不過濾)、JSON(只過濾JSON內元素的值)以及HTML(忽視tag和attribute,并考慮文字內的HTML Encode)。其余不變。

  順便說一句,以上代碼其實只是為了寫這些內容而在10分鐘內寫好的,不考慮性能、緩存、同步、邊界等情況——因為我相信看了下面的文字您一定會拋棄這種做法。

[第1頁][第2頁]
0
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()