David's Blog

96edf86e6333e32a652deb8940dd8bde

[CodeIgniter] 解決 CI 下載函式 force_download 碰到 IE 下載中文檔名變亂碼

| 0 comments

要讓瀏覽器強制下載某個檔案,而不是直接開啟在瀏覽器裡面,絕大部分的做法應該都是用HTTP Header送出Content-Disposition,將它設定為attachment,並指定一個檔名,那麼瀏覽器就會將HTTP Body裡面的二進位資料做為檔案內容,開啟下載對話框,以所指定的檔名,讓使用者下載檔案。


以PHP為例,簡單的寫法大概是像這樣:

$filename = 'fubar.doc';
$data = file_get_contents('download/'.$filename);
header('Content-Type: "application/octet-stream"');
header('Content-Disposition: attachment; filename="'.$filename.'"');
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".strlen($data));
echo $data;

以前當我寫到檔案下載的這類功能時,常常記不住這堆header的寫法,直到我的膝蓋中了一箭…不,直到我遇見了CodeIgniter…

CodeIgniter有個很好用的helper函式叫force_download,幫我們把這些落落長的header都寫好好了,只要把它load進來,然後這樣給它用下去,剩下的事情它都幫你搞定:

$this->load->helper('download');
$filename = 'fubar.doc';
$data = file_get_contents('download/'.$filename);
force_download($filename, $data);

是不是很簡單呢? 於是我就很快樂的將它用到我最近的一個案子上,直到我的膝蓋又中了一箭…不,直到我用IE來測試的時候,發現當我檔名用中文的時候,IE跳出來的下載對話框上面的檔名竟然變成了亂碼,像這樣…

$this->load->helper('download');
$filename = '今日正咩一枚.jpg';
$data = file_get_contents('download/'.$filename);
force_download($filename, $data);

唉唷~~這怎麼可以呢,人家火狐和窟窿明明都沒問題的呀。經過咕狗大人的指點,原來這算是IE的一個Bug,IE對於Content-Disposition是用ANSI編碼,碰到你用Unicode編碼的檔名就爆炸了。而偏偏CI是阿凸啊寫的,人家下載檔案才不會用到中文檔名咧,所以force_download就沒有處理到中文檔名碰到IE的問題了。

解決辦法

所以該怎麼解決呢,有三種辦法

1. 叫user去改IE的設定
到[工具]->[網際網路選項]->[進階],找到 [傳送UTF-8 URL] 那個給它打勾
但是我們做網站的,面對的user百百款,怎麼可能叫他們自己去改IE的設定呢,這實在很夭壽,所以只好我們這邊自己動…

2. 改force_download這個函式
修改system/helpers/download_helper.php這支程式,找到最後面送出header的那一段,把Content-Disposition後面的filename用iconv把檔名改編碼成Big5,像這樣:

        // Generate the server headers
        if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE"))
        {
            header('Content-Type: "'.$mime.'"');
            header('Content-Disposition: attachment; filename="'.iconv('utf-8', 'big5', $filename).'"');
            header('Expires: 0');
            header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
            header("Content-Transfer-Encoding: binary");
            header('Pragma: public');
            header("Content-Length: ".strlen($data));
        }
        else
        {
            header('Content-Type: "'.$mime.'"');
            header('Content-Disposition: attachment; filename="'.$filename.'"');
            header("Content-Transfer-Encoding: binary");
            header('Expires: 0');
            header('Pragma: no-cache');
            header("Content-Length: ".strlen($data));
        }

不過這會動到CI的系統核心,我個人不是很喜歡,因為如果你下次CI做更新的時候,肯定會忘了再來這裡改一遍,到時候又會爆炸了…

3. 修改你的controller程式
這是比較好的方法,在你自己的controller裡面就把檔名轉成Big5再帶進去,就不需要動到核心啦

$this->load->helper('download');
$filename = '今日正咩一枚.jpg';
$data = file_get_contents('download/'.$filename);
// 判斷檔名是不是中文
if ( mb_strlen($filename, 'Big5') != strlen($filename) ) {
    $filename = iconv('UTF-8', 'Big5', $filename);
}
force_download($filename, $data);

最後還是那句老話,IE加油,好嗎?

參考
ASP.NET 如何設定強制下載檔案並正確處理中文檔名的問題
[CodeIgniter] 解決 CI 下載函數 force_download 在 IE 底下檔案標題亂碼
[CodeIgniter] 解決 CI 下載函數 force_download 使用 IE 下載時檔名亂碼

延伸閱讀
“直到我的膝蓋中了一箭”的由來

Leave a Reply

Required fields are marked *.