雖然Model Dialog不是什麼好東西,礙於它目前還是內部系統的一哥,偶爾伺侯一下難搞的大頭症主角是難免的... orz

這回遇到的難題: A網站的網頁利用showModalDialog顯示來自B網站的網頁,透過window.dialogArguments傳過去的參數以及Modal Dialog中用window.returnValue傳回的結果,因違反Same Origin Policy,判定跨Domain不得存取,全都變成了undefined。

我用以下例子來重現問題,先做一個caller.htm,以showModalDialog呼叫dialog.htm,並將window物件當參數傳過去,之後再接收dialog.htm的傳回值以alert顯示。dialog.htm在同一個網站目錄下,所以URL用相對路徑即可。

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Caller</title>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.js"
            type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            $("#x").click(function () {
                var url = "Dialog.htm";
                //以下URL仍連至相同的網頁,
                //但利用localhost與127.0.0.1的差別製造Cross-Domain條件
                //url = "http://127.0.0.1/jQuery/XSS/Dialog.htm";
                //window物件當成參數傳給Modal Dialog
                var r = window.showModalDialog(url, window);
                //顯示結果
                alert(r);
            });
        });
    </script>
</head>
<body><input type="button" id='x' value="Start" /></body>
</html>

在dialog.htm裡,則有OK及Cancel兩個鈕,按下任一個,會將按鈕的文字指定給window.returnValue傳回caller.htm。另外,為了檢測是否接收到caller.htm傳來的window物件,也一併將window.dialogArguments的檢測結果傳回。

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Dialog</title>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.js"
            type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            $(":button").click(function () {
                var callerWin = window.dialogArguments;
                window.returnValue = 
                    (callerWin || "null") + ":" + this.value;
                window.close();
            });
        });
    </script>
</head>
<body>
<input type="button" id="bOK" value="OK" />
<input type="button" id="bCancel" value="Cancel" />
</body>
</html>

在正常的運作下,我們應該得到[object Window]:OK 或 [object Window]:Cancel。

接著我們在caller.htm中對dialog.htm的URL動點手腳,把它調成httq://127.0.0.1/jQuery/XSS/Dialog.htm,雖然是同一個網頁,但在執行時就會因localhost vs 127.0.0.1被視為不同Domain,重現違反Same Origin Policy的情境,結果就變成了undefined。(若在dialog.htm中斷偵錯,可觀察到window.dialogArguments此時也是undefined)

印象中document.domain是解決跨Domain溝通的法寶,不過依據爬文與自己實測的結果,這招對window.open有效,對window.showModalDialog沒用。

苦思之後,決定採用以下方法繞路解決。先實作一個傳達結果訊息的中介信差程式(Messenger.aspx),允許dialog.htm以id參數為憑,將v參數存入Cache中,稍後caller.htm再憑同一id向信差程式提取結果。換句話說,就是讓caller.htm與dialog.htm透過第三者傳話,達成溝通的目的。

<%@ Page Language="C#" %>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
    string m = Request["m"];
    string id = Request["id"];
    string v = Request["v"];
    switch (m)
    {
        case "set":
            //以識別碼為Key,將值存入Cache
            Cache.Insert(id, v, null, DateTime.Now.AddSeconds(30),
                System.Web.Caching.Cache.NoSlidingExpiration, 
                CacheItemPriority.Default, null);
            break;
        case "check":
            //由Cache取出並清除
            if (Cache[id] != null)
            {
                Response.Write(Cache[id]);
                Cache.Remove(id);
            }
            break;
    }
    Response.End();
}
</script>

caller.htm在呼叫dialog.htm時,要多傳入Messenger.aspx的URL,其中還包存取本次交談資料的識別碼(GUID是上選,這裡為省事只用亂數),而待showModalDialog()結束,立刻自己呼叫Messenger.aspx取出dialog.htm存入的結果顯示出來。

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Caller</title>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.js"
            type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            //需要一個唯一識別碼,GUID是上選,此處用亂數暫代
            var unique = Math.random().toString().substr(2);
            var url = "http://127.0.0.1/jQuery/XSSSync/Dialog.htm";
            //加傳一個回呼URL給dialog.htm,Messager.aspx可協助傳遞結果
            var cbUrl = 
            "http://localhost/jQuery/XSSSync/Messenger.aspx?m=set&id=" + unique;
            url += "?callback=" + escape(cbUrl);
 
            $("#x").click(function () {
                window.showModalDialog(url, window);
                //呼叫完畢,向Messenger.aspx取回結果
                $.get(cbUrl.replace("m=set", "m=check"), {}, function (r) {
                    alert(r);
                });
            });
        });
    </script>
</head>
<body>
<input type="button" id='x' value="Start" />
</body>
</html>

dialog.htm的部分則是從URL中取出Messenger.aspx的網址,並透過$.getScript()方式呼叫Messenger.aspx並存入結果。(Messenger.aspx在不同Domain,再次因Same Origin Policy無法使用$.get(), $.post()呼叫,得靠$.getScript()才能突破限制)

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Dialog</title>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.js"
            type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            $(":button").click(function () {
                //由URL中取出回傳結果用的網址
                var url = location.href;
                var paramPrefix = "callback=";
                var p = url.indexOf(paramPrefix);
                if (p > -1) {
                    //將要傳的結果用&v=...帶入
                    url = unescape(
                          url.substring(p + paramPrefix.length, url.length)) +
                          "&v=" + this.value;
                    //由回呼URL屬不同Domain,使用getScript來克服Cross-Domain限制
                    $.getScript(url, function () {
                        window.close();
                    });
                }
            });
        });
    </script>
</head>
<body>
<input type="button" id="bOK" value="OK" />
<input type="button" id="bCancel" value="Cancel" />
</body>
</html>

呼... 搞定!


Comments

# by keystrokemonitor@yahoo.ca

Thanks for the code, it seems very important; great stuff, keep on updating more like content as like; good luck.

Post a comment