在開發強調"彈性"的系統功能時,我偶爾會遇到要由動態產生的複雜運算式決定流程的情境,例如: "( (a + b) > 0 || c > 100 && ( d || e ) )",當運算式參數可預期或是會固定依某些條件變化時,這倒還不算什麼難題;但若是運算式被要求開放使用者或使用元件的開發人員自由設定,就真的不是一般Coding做法能解決的,得寫個Parser解析使用者輸入的運算式再算出結果。而Parser得掌握所有的語法規則,涵蓋各種可能出現的組合,要寫得夠強韌不易出錯實非易事。

相形之下,Javascript裡有eval()函數可用就省事很多,等同於Script Engine本身就提供強大的Parser,可以解析任意Javascript指令並正確執行。

那... 何不直接偷來用?

以下的程式碼(非原創,網路上有許多類似的參考,例如: CodeProject的範例),利用在Mini C# Lab用過的CodeDom即時編譯技術,建了一個JScript物件來執行Javascript eval()函數,將結果以字串形式傳回。

/*
using System.CodeDom.Compiler;
using System.Reflection;
*/ 
public class JSEvaluator
{
    static Type _jseType = null;
    static object _jse = null;
    static JSEvaluator()
    {
        CodeDomProvider provider = CodeDomProvider.CreateProvider("JScript");
        CompilerParameters p = new CompilerParameters();
        p.GenerateInMemory = true;
        CompilerResults r = provider.CompileAssemblyFromSource(p,
@"class JSEvaluator {  
public function Eval(expr : String) : String { return eval(expr); } 
}");
        Assembly asm = r.CompiledAssembly;
        _jseType = asm.GetType("JSEvaluator");
        _jse = Activator.CreateInstance(_jseType);
    }
 
    public static string Eval(string expr) {
        return _jseType.InvokeMember("Eval", BindingFlags.InvokeMethod,
            null, _jse, new object[] { expr }).ToString();
    }
}

這樣子,寫一行JSEvaluator.Eval("( ( 4 + 5 * 18 ) > 12 || 'abc' == 'ABC' && 'aaaa'.indexOf('a') > -1 )")就能得到true/false,再複雜(謎之聲: 這範例分明是在找碴吧?)的Javascipt運算式都可以支援! 如此,系統就可以透過程式動態組裝複雜的條件,或是開放具有Javascript背景的開發人員依特定規則自訂條件,條件邏輯可以變化的空間就相當驚人了。

【資安考量】

至於會不會像SQL Injection一樣,被人在運算式中夾帶惡意內容而衍生風險呢? 依據MSDN上的說明:

The code passed to the eval method is executed in a restricted security context, unless the string "unsafe" is passed as the second parameter. The restricted security context forbids access to system resources, such as the file system, the network, or the user interface. A security exception is generated if the code attempts to access those resources.

依我的解讀,只要不打開"unsafe"參數,遭受入侵破壞的風險應該不會太高。(若有錯請指正)

***提醒*** 允許外界傳入程式碼執行還是會存在一定的風險,請務必酌斟使用,可參考後續文章裡的延伸討論。


Comments

# by ChrisTorng

如果寫死迴圈,不曉得會不會像 IE 般會出現執行過久是否要中斷之類的訊息... 如果有提示訊息,也只有在本機程式上有用。如果用在 Web Server 上,根本沒人幫忙按掉該訊息,那個 request 就卡死了... 如果沒有提示訊息,在 Web Server 上無窮執行下去,就是 CPU 100% 了...該網站即使還能動,也很慢很慢了吧...

# by ChrisTorng

這個 JScript 是 .NET CLR 的 JScript.NET? Windows 內的 JScript? IE 的 JScript? 如果是 JScript.NET,或許可以考慮建立 JScript.NET 的專案,然後建置成 dll 就可以給其他專案參考用了...? 不必由 C#/VB 語言中用一堆 Reflection...也可以很方便地將 JScript.NET 中其他的好用功能公開讓其它語言直接呼叫。

# by Jeffrey

to ChrisTorng,我同意你的看法,只要有開放外界自由輸入程式碼的場合,就一定存在被惡搞的可能。這樣的技術我目前是開放給Developer透過XML檔設定判斷運算式的做法提供較大的條件化彈性,原則上等同信任來自於Developer的Code。 即便因為被駭的風險看來沒有很高,我亦不建議任意接受不限定使用者所輸入的文字送到eval中執行。 至於JScript.NET Class Build成DLL也是個好主意(雖然見識過BCL如何大膽使用Relection後,目前我對它已經沒有太多運用上的顧忌),晚點我來補充一下做法。 謝謝您的意見。

# by Wolke

大大出個JQuery或JavaScript的題目嘛! C#沒在寫,很難參加!!!

Post a comment