要讓瀏覽器強制下載某個檔案,而不是直接開啟在瀏覽器裡面,絕大部分的做法應該都是用HTTP Header送出Content-Disposition,將它設定為attachment,並指定一個檔名,那麼瀏覽器就會將HTTP Body裡面的二進位資料做為檔案內容,開啟下載對話框,以所指定的檔名,讓使用者下載檔案。
以PHP為例,簡單的寫法大概是像這樣:
1 2 3 4 5 6 7 |
$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進來,然後這樣給它用下去,剩下的事情它都幫你搞定:
1 2 3 4 |
$this->load->helper('download'); $filename = 'fubar.doc'; $data = file_get_contents('download/'.$filename); force_download($filename, $data); |
是不是很簡單呢? 於是我就很快樂的將它用到我最近的一個案子上,直到我的膝蓋又中了一箭…不,直到我用IE來測試的時候,發現當我檔名用中文的時候,IE跳出來的下載對話框上面的檔名竟然變成了亂碼,像這樣…
1 2 3 4 |
is->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,像這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// 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再帶進去,就不需要動到核心啦
1 2 3 4 5 6 7 8 |
$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 下載時檔名亂碼
延伸閱讀
“直到我的膝蓋中了一箭”的由來