TIPS-跨Domain傳遞Modal Dialog結果
1 |
雖然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.