JavaScript 可以在瀏覽器執行程式,這就牽涉到許多安全性問題,使用者下載了網頁,下載了 JavaScript 程式碼偷偷地在執行,有些動作必須加以限制,以免使用者的電腦遭竊取資料、讀寫或破壞,最基本的,瀏覽器不允許 JavaScript 對客戶端電腦作檔案存取的動作。
安全還有其他許多因素要考量,舉例來說,在過去,瀏覽器上可以設狀態列文字,不過這也會有安全疑慮,因為在某些情況下,使用者可以透過 JavaScript 來變造狀態列訊息來欺瞞使用者,狀態列文字的設定有所疑慮,事實上,新的瀏覽器也不再包括狀態列了。
使用 JavaScript 可以另開視窗,不過沒辦法開啟太小的視窗,這是個很理所當然的考量,因為若可以開很小的視窗,使用者沒注意到它,那在這個小視窗中就可以偷偷摸摸作些壞事了。
在瀏覽器中基於安全,<input>
的 type
為 file
時,你設定的 value
會被瀏覽器忽略,只能由使用者親自選取檔案。你透過 JavaScript 來設定檔案選取框的 value
值也沒用。
那麼,如果想要設定一個按鈕,讓使用者可以清除選取的檔案要怎麼作?透過 JavaScript 將 value
設為空字串是沒用的。不過可以重新要求瀏覽器建立新的選取框。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<div id="fileupload"><input name="upload" type="file"></div>
<button id="clear">Clear</button>
<script type="text/javascript">
document.getElementById('clear').onclick = function() {
let fileupload = document.getElementById('fileupload');
fileupload.innerHTML = fileupload.innerHTML;
};
</script>
</body>
</html>
在上例中,若選取了檔案,並按下 Clear 按鈕時,取得的 innerHTML
會是靜態的 <input name="upload" type="file">
,當它被指定回 innerHTML
,瀏覽器會重新剖析,等於要求瀏覽器丟掉舊的 DOM,根據指定的 HTML 建立新的 DOM,在使用者的觀感中,自然就像是清除了選取框的內容。
再舉使用 JavaScript 取得檔案大小為例,在 Firefox、Chrome 中,可以透過 files.item(0).size
特性來取得檔案大小。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<input id="upload" name="upload" type="file"><br>
<button id="size">Size</button><br>
<span id="console"></span>
<script type="text/javascript">
document.getElementById('size').onclick = function() {
let size = document.getElementById('upload').files.item(0).size;
document.getElementById('console').innerHTML = 'Size: ' + size;
};
</script>
</body>
</html>
舊版 Internet Explorer 無法使用以上方法,在古早的 Internet Explorer 6 是透過建立 Image
或 ActiveXObject
(Scripting.FileSystemObject
)來取得檔案大小,但因安全性問題,在後來的 Internet Explorer 就行不通了。
類似的限制還有 iframe
。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<iframe id="page" name="page" src="https://openhome.cc"></iframe>
<span id="console"></span>
<script type="text/javascript">
window.onload = function() {
alert(window.frames['page'].document.body.innerHTML);
};
</script>
</body>
</html>
你可以取得 iframe
的內容,不過僅限與這個文件有相同來源的其他網頁才可以,例如,放在我的網站 就可以,放在 JS Bin 就不行。
若沒有這個限制,如果你是在內部網域,我指定你內部網域中某個內部網站的網址,就可以取得網頁的內容,之後再上傳,也就是說,沒有這個限制,我就可以偷你內部網站中的東西。
如果使用 Ajax,建立的 Ajax 非同步物件也會有類似的限制,預設只能使用非同步物件來取得與文件相同來源的其他網頁,不同來源的文件在過去是直接被禁止的,而新的規範中,必須伺服器有支援相對應的協議才可以。
這稱之為同源策略(Same-origin policy)。文件相同來源指的是,與目前文件的 URL 協定、主機與埠號相同的其他文件,才可以被 JavaScript 取得,只要 URL 協定、主機與埠號其中一個不同,就不可以取得。
例如若目前文件來源為 http://caterpillar.onlyfun.net:80/Gossip/demo.html。那麼以下視為不同源…
- https://caterpillar.onlyfun.net:80/Gossip/demo.html(協定不同)
- http://openhome.cc:80/Gossip/demo.html(主機資訊不同,即使實體機器是同一台)
- http://192.168.0.1:80/Gossip/demo.html(視為主機資訊不同)
- http://caterpillar.onlyfun.net:8080/Gossip/demo.html(埠號不同)
<script>
的 src
可以指定外部 URL,來自不同 URL 的 .js 檔案,可以在同一個頁面中運作,呼叫彼此變數或函式,但它們可取得的頁面必須是與目前頁面相同來源,而不是與 .js 檔案相同來源。
子網域的文件,預設是不同源,但可以透過 document.domain
設定子網域為同源,document.domain
預設就是文件來源,例如若是 http://caterpillar.onlyfun.net:80/Gossip/demo.html,則 document.domain
就是 'caterpillar.onlyfun.net'
。預設無法存取 ooo.onlyfun.net、xxx.onlyfun.net 的文件,可以將 document.domain
設定為 'onlyfun.net'
,如此來自 ooo.onlyfun.net、xxx.onlyfun.net 的文件就會被視為同源,注意! document.domain
只能設為頂層網域,不能設其他網域。
以上簡介的是一些常見的 JavaScript 安全議題,實際上還有更多,甚至有些瀏覽器沒限制的,也可能被防毒軟體或防火牆防堵。