跨指令碼處理不同安全執行程序中內容

Adobe AIR 1.0 以及更新的版本

執行階段安全性模型會將程式碼與不同的來源隔離。藉由跨指令碼處理不同安全執行程序中內容,您可以讓某個安全執行程序中的內容存取另一個安全執行程序中選取的屬性和方法。

AIR 安全執行程序和 JavaScript 程式碼

AIR 會強制落實相同來源的原則,避免某個網域中的程式碼與另一個網域中的內容互動。所有檔案都會根據其來源,放置在安全執行程序中。一般而言,應用程式安全執行程序中的內容不能違反相同來源原則,也不能針對從應用程式安裝目錄以外載入的內容進行跨指令碼處理。不過,AIR 提供了幾項技巧,可以讓您對非應用程式的內容進行跨指令碼處理。

其中一項技巧是使用 frame 或 iframe,將應用程式內容對應至不同的安全執行程序中。所有自應用程式之安全執行程序區域載入的網頁,其行為就如同這些網頁是自遠端網域載入一樣。例如,將應用程式內容對應至「example.com」網域,此內容即可針對自 example.com 載入的網頁進行跨指令碼處理。

由於這項技巧會將應用程式內容放置在不同的安全執行程序中,所以此內容中的程式碼也將不再受到限制,可以在經過評估的字串中執行程式碼。即使不需要對遠端內容進行跨指令碼處理,您還是可以使用這項安全執行程序對應技巧來減少這些限制。使用許多 JavaScript 架構的其中一種架構,或使用必須依賴評估字串才能執行的現有程式碼時,以這種方式對應內容就特別有用。不過,您必須考量並避免其它風險,也就是當執行應用程式安全執行程序以外的內容時,可能會遭插入或執行不受信任的內容。

同時,對應至其它安全執行程序的應用程式內容便無法存取 AIR API,因此這項安全執行程序對應技巧無法用於將 AIR 功能公開給在應用程式安全執行程序以外執行的程式碼。

另外還有一項跨指令碼處理技巧,可讓您在非應用程式安全執行程序中的內容與應用程式安全執行程序中這個內容的父輩文件之間,建立名為「安全執行程序橋接」的介面。這座橋樑可以讓子系內容存取父輩所定義的屬性和方法、讓父輩存取子系所定義的屬性和方法,或兩者。

最後,您也可以從應用程式安全執行程序和其它安全執行程序 (選擇性) 執行跨網域的 XMLHttpRequest。

如需詳細資訊,請參閱 HTML 的 frame 和 iframe 元素 Adobe AIR 的 HTML 安全性 ,以及 XMLHttpRequest 物件

將應用程式內容載入至非應用程式安全執行程序

若要讓應用程式內容可以安全地針對從應用程式安裝目錄以外載入的內容進行跨指令碼處理,您可以使用 frame iframe 元素,將應用程式內容載入至與該外部內容相同的安全執行程序中。如果您不需要對遠端內容進行跨指令碼處理,但仍希望載入應用程式安全執行程序以外之應用程式的網頁,可以使用 http://localhost/ 或其它不具有風險的值做為來源網域。

AIR 中為 frame 元素加入了新的特質 sandboxRoot documentRoot ,可以讓您指定載入至該 frame 的應用程式檔案是否必須對應至非應用程式安全執行程序。剖析為 sandboxRoot URL 下方路徑的檔案會改為從 documentRoot 目錄載入。基於安全性的考量,以這種方式載入的應用程式內容會被視為就像實際是從 sandboxRoot URL 載入一樣。

sandboxRoot 屬性會指定要使用的 URL,以判斷要在哪個安全執行程序或網域中放置 frame 內容。這裡必須使用 file: http: https: URL 配置。如果您指定相對 URL,內容就會保留在應用程式安全執行程序中。

documentRoot 屬性會指定要從其中載入 frame 內容的來源目錄。這裡必須使用 file: app: app-storage: URL 配置。

下列範例會對應安裝在應用程式之 sandbox 子目錄中的內容,以便在遠端安全執行程序和 www.example.com 網域中執行:

<iframe 
    src="http://www.example.com/local/ui.html"  
    sandboxRoot="http://www.example.com/local/"  
    documentRoot="app:/sandbox/"> 
</iframe>

ui.html 網頁可以使用下列指令碼標籤,從本機 sandbox 資料夾載入 Javascript 檔案:

<script src="http://www.example.com/local/ui.js"></script>

此外,這個網頁也可以使用如下所示的指令碼標籤,從遠端伺服器中的目錄載入內容:

<script src="http://www.example.com/remote/remote.js"></script>

sandboxRoot URL 將會遮蔽遠端伺服器中位於相同 URL 的所有內容:在上述範例中,您無法存取位於 www.example.com/local/ (或其中任何子目錄) 中的遠端內容,因為 AIR 會將要求重新對應至本機應用程式目錄。無論這些要求是因為網頁瀏覽、XMLHttpRequest 或其它載入內容的方式而衍生,AIR 都會重新對應這些要求。

設定安全執行程序橋接介面

安全執行程序橋接的使用時機如下:當應用程式安全執行程序中的內容必須存取非應用程式安全執行程序中的內容所定義之屬性或方法時,或者當非應用程式的內容必須存取應用程式安全執行程序中的內容所定義之屬性或方法時。使用子系文件之 window 物件的 childSandboxBridge parentSandboxBridge 屬性,即可建立安全執行程序橋接。

建立子系安全執行程序橋接

childSandboxBridge 屬性可以讓子系文件將介面公開給父輩文件中的內容。若要公開介面,請將 childSandbox 屬性設定為子系文件中的函數或物件。之後,您便能從父輩文件中的內容存取該物件或函數。下列範例會說明在子系文件中執行的指令碼如何將包含函數和屬性的物件公開給其父輩:

var interface = {}; 
interface.calculatePrice = function(){ 
    return ".45 cents"; 
} 
interface.storeID = "abc" 
window.childSandboxBridge = interface;

如果這個子系是載入至 ID 指定為「child」的 iframe 中,您便可以藉由讀取 frame 的 childSandboxBridge 屬性,從父輩內容存取介面:

var childInterface = document.getElementById("child").contentWindow.childSandboxBridge; 
air.trace(childInterface.calculatePrice()); //traces ".45 cents" 
air.trace(childInterface.storeID)); //traces "abc"

建立父輩安全執行程序橋接

parentSandboxBridge 屬性可以讓父輩文件將介面公開給子系文件中的內容。若要公開介面,父輩文件必須將子系文件的 parentSandbox 屬性設定為父輩文件中定義的函數或物件。之後,您便能子系中的內容存取該物件或函數。在下列範例中,會說明在父輩 frame 中執行的指令碼如何將包含函數和屬性的物件公開給子系文件:

var interface = {}; 
interface.save = function(text){ 
    var saveFile = air.File("app-storage:/save.txt"); 
    //write text to file 
} 
document.getElementById("child").contentWindow.parentSandboxBridge = interface;

使用這個介面,子系 frame 中的內容便能將文字儲存至名為 save.txt 的檔案,但是無法存取檔案系統中的其它任何項目。子系內容可以呼叫 save 函數,如下所示:

var textToSave = "A string."; 
window.parentSandboxBridge.save(textToSave);

應用程式內容必須將限制最多的介面公開給其它安全執行程序。非應用程式內容本質上必須視為不值得信任的內容,因為很容易在不小心或惡意情況下,在其中插入程式碼。您必須在適當的位置加上保護措施,避免透過父輩安全執行程序橋接公開的介面遭濫用。

在頁面載入期間存取父輩安全執行程序橋接

父輩安全執行程序橋接必須在子系文件中的指令碼執行之前先經過設定,Script 才能存取此安全執行程序橋接。Window、frame 和 iframe 物件都會在新網頁的 DOM 建立之後、尚未剖析任何指令碼或新增 DOM 元素之前傳送 dominitialize 事件。您可以在網頁建構程序初期,使用 dominitialize 事件建立安全執行程序橋接,讓子系文件中的所有指令碼都可以存取此橋接。

在下列範例中,會示範如何建立父輩安全執行程序橋接,以回應從子系 frame 傳送的 dominitialize 事件:

<html> 
<head> 
<script> 
var bridgeInterface = {}; 
bridgeInterface.testProperty = "Bridge engaged"; 
function engageBridge(){ 
    document.getElementById("sandbox").contentWindow.parentSandboxBridge = bridgeInterface; 
} 
</script> 
</head> 
<body> 
<iframe id="sandbox" 
            src="http://www.example.com/air/child.html"  
            documentRoot="app:/" 
            sandboxRoot="http://www.example.com/air/" 
            ondominitialize="engageBridge()"/> 
</body> 
</html>

下列 child.html 文件會示範子系內容如何存取父輩安全執行程序橋接:

<html> 
    <head> 
        <script> 
            document.write(window.parentSandboxBridge.testProperty); 
        </script>   
    </head>   
    <body></body> 
</html>

若要針對子系 window (而非 frame) 偵聽 dominitialize 事件,您必須將偵聽程式加入至使用 window.open() 函數建立的新子系 window 物件:

var childWindow = window.open(); 
childWindow.addEventListener("dominitialize", engageBridge()); 
childWindow.document.location = "http://www.example.com/air/child.html";

在此案例中,無法將應用程式內容對應至非應用程式安全執行程序中。這項技巧只有在從應用程式目錄以外載入 child.html 時才有用。您仍然可以將 window 中的應用程式內容對應至非應用程式安全執行程序,但是必須先載入中間網頁。中間網頁本身會使用 frame 載入子系文件,並將文件對應至所需的安全執行程序。

如果您使用 HTMLLoader 類別的 createRootWindow() 函數來建立 window,則此新的 window 就不是文件 ( createRootWindow() 的呼叫來源) 的子系。因此,您無法在呼叫端 window 與載入至新 window 的非應用程式內容之間建立安全執行程序橋接,而是必須改為在新的 window 中載入中間網頁,讓網頁本身使用 frame 載入子系文件。之後,您便可以在新 window 的父輩文件與載入至 frame 的子系文件之間建立安全執行程序橋接。