FireFox中file控件不能取到客戶端文件的完整路徑的問題(2012.11.27)

        今天做項目的時候碰到了這個問題,就在網上查了一下相關的資料,以下資料摘自博友(http://www.cnblogs.com/jaxu/archive/2009/04/19/1439016.html

        相信很多人都使用過<input type="file"/>這樣的HTML控件,它看起來非常普通,是我們在做Web應用程序中用於上傳客戶端本地文件時不可缺少的控件,然而最近我發現這個控件在最新的FireFox瀏覽器(或者最新的IE8中也會存在這個問題,我沒有嘗試過,讀者可以試一下)中卻失去了效果,導致我們在通過這個控件的value屬性得到的值中只包含了文件名而沒有文件路徑,這個在IE7中是可以正常獲取到全文件名的(即文件完整路徑+文件名)。IE7和大部分當前流行的瀏覽器(如FireFox2版本)都可以獲取到文件的路徑,但是FireFox3卻不行,我查了很多資料,發現這是FireFox3爲了彌補在低版本中可能會引起安全問題的一個漏洞,據說黑客會通過FireFox的這一安全隱患向服務器上傳文件!其實我也搞不懂,不就是本地文件的路徑麼?怎麼會影響到服務器的安全問題呢?看來高手們還真的很強!!

    來說說我爲什麼要得到本地所選的文件的路徑。大家都知道163郵箱,裏面在上傳郵件附件的時候是允許選擇多附件的,我要做的功能類似於這個,不過我在這裏並不是要研究163是如何實現這個功能,我只想在用戶選擇文件的時候動態在一個Div中添加他所選擇的文件的信息和一個刪除按鈕,然後將這個文件的信息保存在頁面的一個隱藏域中,當用戶保存頁面時服務器端代碼根據頁面隱藏域中的信息將用戶所選的文件上傳到服務器上。當然,頁面隱藏域中的信息至少要包含用戶本地所選文件的路徑,否則就不知道在什麼地方去找文件了。有關如何實現動態添加HTML節點不是本文的重點,這裏我也不貼代碼了,下面說說我所遇到的問題。

    下面是一段用於測試問題的代碼。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">     
<html xmlns="http://www.w3.org/1999/xhtml" >     
<head>     
    <title>Untitled Page</title>     
</head>     
<body>     
    <input id="File1" type="file" /><input id="btAdd" type="button" value="Add" οnclick="alert(document.getElementById('File1').value);" />     
</body>     
</html>

運行後在IE7中的結果:

運行後在FireFox3中的結果:

ffpop

       那麼我如何才能在FireFox3中取得本地文件的路徑呢?就像上面我在IE7中得到的那個值一樣!暫且撇開這個問題,先說說在FireFox3中如何上傳一個文件吧。既然FireFox3中將獲取本地文件的路徑的方法當做一個安全隱患被禁止了,那麼它一定有相關的方法來解決這個問題,否則FireFox3就不能實現在客戶端上傳文件的功能了,就像前兩天我的一個同事說的一樣,要真是這樣,FireFox就廢了!其實FireFox3中引入了一個新的接口用來解決這個問題,那就是nsIDOMFile,它專門被用來從客戶端的input type="file"的控件中獲取文件數據,這樣就可以將本地的文件保存到服務器上。這是一個非常好的解決辦法,以至於我們在FireFox3中開發這樣的應用程序時比先前簡單獲取value值然後再通過服務器端代碼上傳文件要簡單許多,不過令人擔憂的是,這個接口只適用於FireFox,在IE和其它的瀏覽器中並不支持。一會兒再說如何解決瀏覽器的兼容性問題,先看一下在FireFox3中怎麼使用nsIDOMFile。

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"     
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">     
     
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">     
<head>     
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />     
    <title>input type=file & Firefox 3</title>     
</head>     
     
<body>     
    
<h1>input type=file & Firefox 3</h1>     
    
<script type="text/javascript">     
// <![CDATA[     
    
function inputFileOnChange() {    
    if(document.getElementById('my-file').files) {     
        // Support: nsIDOMFile, nsIDOMFileList     
        alert('value: ' + document.getElementById('my-file').value);     
        alert('files.length: ' + document.getElementById('my-file').files.length);     
        alert('fileName: ' + document.getElementById('my-file').files.item(0).fileName);     
        alert('fileSize: ' + document.getElementById('my-file').files.item(0).fileSize);     
        alert('dataurl: ' + document.getElementById('my-file').files.item(0).getAsDataURL());     
        alert('data: ' + document.getElementById('my-file').files.item(0).getAsBinary());     
        alert('datatext: ' + document.getElementById('my-file').files.item(0).getAsText("utf-8"));     
    };      
};      
     
// ]]>     
</script>     
    
<div>     
    <input type="file" name="my-file" id="my-file" οnchange="inputFileOnChange();" />     
</div>     
    
</body>     
</html>

document.getElementById('my-file').files方法用於獲取到用戶所選擇的文件的集合,一般情況下都是選擇單一文件(貌似FireFox這樣做是支持多文件選擇的,不過沒有試過,讀者可以自己去嘗試),item數組可以得到其中的某一個文件,然後我們就可以使用nsIDOMFile所提供的屬性和方法了。它包括2個屬性和3個方法:

fileName:用於獲取到用戶所選文件的名稱,這和直接取value值所得到的結果一樣。

fileSize:得到用戶所選文件的大小。

getAsBinary():得到用戶所選文件的二進制數據。

getAsDataURL():得到用戶所選文件的路徑,該路徑被加密了,目前只能在FireFox中使用。

getAsText():得到用戶所選文件的指定字符編碼的文本。

    讀者可以參考這個地址:https://developer.mozilla.org/en/nsIDOMFile

    有一點需要說明,方法getAsDataURL()可以取得用戶所選文件的本地路徑,但是這個路徑的字符串文本被FireFox加密了,並且這段密文只能被FireFox識別,其它的瀏覽器不能識別,也就是說我將被加密後的路徑直接賦值給一個img標籤的src屬性,在FireFox中是可以直接顯示出圖片的,而在IE中卻不行。從這一點來看,FireFox是不是有點王者風範呢?居然連大名鼎鼎的IE都不支持!

    再回到本文一開始所提的那個問題上來。即然我不能在FireFox中得到用戶所選文件的本地路徑,而且採用getAsDataURL()方法得到的這個路徑也不能體現通用性,那怎麼才能徹底解決這個問題呢?簡單思考一下,瀏覽器爲什麼能夠調用OS的文件打開對話框,從而進一步得到用戶所選的文件的信息呢?瀏覽器不是僅僅只能解釋HTML文本麼?沒錯,這個是瀏覽器的基本功能,至於如果調用OS提供的功能接口,那是瀏覽器的各個不同廠商自己要做的工作,這個似乎沒有一個統一的標準,也不會被列入到W3C的規範中,FireFox3就是一個特例。這樣看來,我們只有自己編寫代碼來調用OS的文件打開對話框了,這看起來是一件非常辛苦的事情,最好的辦法莫過於編寫ActiveX控件嵌入到瀏覽器中來執行,幸運的是IE和FireFox都提供了現成的方法供我們調用,我們只需要在js腳本中調用即可。當得知這一點時,我差點高興得一晚上沒有睡覺。

    好了,現在來看我是怎麼做的了!

<html>     
<head>     
    <title>Untitled Page</title>     
     
    <script type="text/javascript">     
function readFile(fileBrowser) {     
    if (navigator.userAgent.indexOf("MSIE")!=-1)     
        readFileIE(fileBrowser);      
    else if (navigator.userAgent.indexOf("Firefox")!=-1 || navigator.userAgent.indexOf("Mozilla")!=-1)     
        readFileFirefox(fileBrowser);      
    else     
        alert("Not IE or Firefox (userAgent=" + navigator.userAgent + ")");     
}      
     
function readFileFirefox(fileBrowser) {     
    try {     
        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");     
    } 
    catch (e) {     
        alert('Unable to access local files due to browser security settings. To overcome this, follow these steps: (1) Enter "about:config" in the URL field; (2) Right click and select New->Boolean; (3) Enter "signed.applets.codebase_principal_support" (without the quotes) as a new preference name; (4) Click OK and try loading the file again.');     
        return;     
    }      
     
    var fileName=fileBrowser.value;     
    var file = Components.classes["@mozilla.org/file/local;1"]     
        .createInstance(Components.interfaces.nsILocalFile);      
    try {     
        // Back slashes for windows     
        file.initWithPath( fileName.replace(/\//g, "\\\\") );     
    }     
    catch(e) {     
        if (e.result!=Components.results.NS_ERROR_FILE_UNRECOGNIZED_PATH) throw e;     
        alert("File '" + fileName + "' cannot be loaded: relative paths are not allowed. Please provide an absolute path to this file.");     
        return;     
    }      
     
    if ( file.exists() == false ) {     
        alert("File '" + fileName + "' not found.");     
        return;     
    }      
    alert(file.path); // I test to get the local file's path.     
    var is = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance( Components.interfaces.nsIFileInputStream );     
    try { is.init( file,0x01, 00004, null); }     
    catch (e) {     
        if (e.result!=Components.results.NS_ERROR_FILE_ACCESS_DENIED) throw e;     
        alert("Unable to access local file '" + fileName + "' because of file permissions. Make sure the file and/or parent directories are readable.");     
        return;     
    }      
    var sis = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance( Components.interfaces.nsIScriptableInputStream );     
    sis.init( is );      
    var data = sis.read( sis.available() );     
     
    alert("Data from file: " + data); // I test to get the local file's data.     
}     
     
function readFileIE(fileBrowser) {     
    var data;     
    try {     
        var fso = new ActiveXObject("Scripting.FileSystemObject");     
     
        var fileName=fso.GetAbsolutePathName(fileBrowser.value);     
        if (!fso.FileExists(fileName)) {     
            alert("File '" + fileName + "' not found.");     
            return;     
        }      
     
        var file = fso.OpenTextFile(fileName, 1);     
     
        data = file.ReadAll();     
        alert("Data from file: " + data);     
        file.Close();      
    }      
    catch(e) {     
        if (e.number == -2146827859) {     
            // This is what we get if the browser's security settings forbid     
            // the use of the FileSystemObject ActiveX control     
            alert('Unable to access local files due to browser security settings. To overcome this, go to Tools->Internet Options->Security->Custom Level. Find the setting for "Initialize and script ActiveX controls not marked as safe" and change it to "Enable" or "Prompt"');     
        }      
        else if (e.number == -2146828218) {     
            // This is what we get if the browser can't access the file     
            // because of file permissions     
            alert("Unable to access local file '" + fileName + "' because of file permissions. Make sure the file and/or parent directories are readable.");     
        }      
        else throw e;     
    }      
}      
    </script>     
     
</head>     
<body>     
    <form name="form1">     
    Browse to select a file      
    <input type="file" name="fileBrowser" size="125" οnchange="readFile(this)" />     
    </form>     
</body>     
</html>

首先我們需要判斷用戶瀏覽器的類型來選擇執行不同的function,IE中直接調用Scripting.FileSystemObject這個ActiveXObject,不過如果用戶的IE沒有打開“對未標記爲可安全執行腳本的ActiveX控件初始化並執行腳本”的話是不能執行這行代碼的,在腳本中給出了提示,告訴用戶必須將這個選項打開。

IE7

    IE的問題不大,因爲我們完全可以直接使用input的value值得到文件的路徑,這裏給出的方法主要是可以得到文件的二進制數據,重點看看在FireFox3中是如何做的。FireFox3中需要用戶在配置頁面中添加一個名稱爲signed.applets.codebase_principal_support的鍵值,將值設爲true,然後就可以通過代碼中給出的方法得到文件的本地路徑了,同樣也可以得到文件的二進制數據。下面是在ForeFox3中成功獲取到本地文件路徑的截圖。

ffsuccess

    有關about:config和如何配置FireFox瀏覽器,讀者可以參考下面的鏈接:

http://www.shanzhuang.com/content/aboutconfig%E8%AE%BE%E7%BD%AE%E6%8A%80%E5%B7%A7

http://www.cnblogs.com/looky/archive/2008/03/18/1111859.html

    這樣,我的問題就可以解決了,不管是在IE中,還是在FireFox中,我都可以獲取到用戶所選文件的本地路徑,然後保存在頁面的隱藏域中,當用戶提交頁面時,服務器端代碼便可以通過頁面隱藏域中的值得到用戶所選擇的文件的路徑,從而上傳文件到服務器。

 

發佈了54 篇原創文章 · 獲贊 61 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章