在《web開(kāi)發(fā)中,如何讓瀏覽器下載文件?》 中介紹了如何讓瀏覽器下載文件的原理维费,其實(shí)非常簡(jiǎn)單果元,本質(zhì)上就是輸出一個(gè) HTTP header 頭(其實(shí)是一個(gè) MIME header 頭),但對(duì)于中國(guó)開(kāi)發(fā)者來(lái)說(shuō)犀盟,更實(shí)際的問(wèn)題是:在某些瀏覽器上而晒,如果下載的文件是中文名,那很有可能出現(xiàn)亂碼阅畴。
實(shí)際上導(dǎo)致這一亂現(xiàn)的并不是技術(shù)問(wèn)題倡怎,而是標(biāo)準(zhǔn)的問(wèn)題,Web 發(fā)展到如今恶阴,經(jīng)歷了太多的混亂诈胜,為了把亂碼這個(gè)問(wèn)題說(shuō)清楚,我花了一天時(shí)間冯事,尋找了一些相關(guān)資料焦匈。
先上圖吧,這是我在 google 上找到的:
這個(gè)圖由三部分 RFC 組成昵仅,分別是 MIME缓熟、Content-Disposition HEADER累魔、HTTP。
首先 Content-Disposition 這個(gè)頭由 MIME 定義够滑,rfc2231(MIME Parameter Value and Encoded Word Extensions:Character Sets, Languages, and Continuations)是最新的標(biāo)準(zhǔn)垦写,描述了 MIME 中字符集及語(yǔ)言的標(biāo)準(zhǔn),廢棄了 rfc2184 和 rfc204彰触。
rfc2183 基于 rfc2231 定義了在互聯(lián)網(wǎng)上 Content-Disposition 頭的標(biāo)準(zhǔn)梯投,說(shuō)明了字符集及編碼,可以這樣理解 rfc2183 和 rfc2231 是從不同角度建立的標(biāo)準(zhǔn)况毅,雙方并不沖突分蓖。
現(xiàn)在來(lái)說(shuō) HTTP,因?yàn)閬y碼問(wèn)題是在瀏覽器中產(chǎn)生的尔许,也就是說(shuō)肯定和 HTTP 協(xié)議有關(guān)么鹤,rfc2616 是 HTTP/1.1 協(xié)議的總標(biāo)準(zhǔn)(最新標(biāo)準(zhǔn)已經(jīng)分為6個(gè)了),對(duì)于 HTTP 協(xié)議來(lái)說(shuō)味廊,header 只能是 ASCII蒸甜,不會(huì)遇到也沒(méi)有中文的問(wèn)題,但為了支持下載余佛,它借用了 MIME 的 Content-Disposition header柠新。
在早期,HTTP 協(xié)議建議使用 rfc2047 標(biāo)準(zhǔn)(查看上圖衙熔,很早的一個(gè) MIME 標(biāo)準(zhǔn))登颓,但瀏覽器沒(méi)有按照該標(biāo)準(zhǔn)執(zhí)行,這個(gè)時(shí)候出現(xiàn)了混亂红氯。
If a character set other than ISO-8859-1 is used, it MUST be encoded in the warn-text using the method described in RFC 2047
不同瀏覽器為了解決亂碼問(wèn)題框咙,提出了很多不一致的解決方案,比如對(duì) Content-Disposition 中的 filename 值進(jìn)行 base64 編碼或百分號(hào)編碼痢甘。
這個(gè)時(shí)候可苦了程序員喇嘱,為了適應(yīng)不同類(lèi)型的瀏覽器,他們需要判斷 UA塞栅,針對(duì)不同 UA 輸出不同的 Content-Disposition者铜,現(xiàn)在我們公司很多代碼也是這樣的編寫(xiě)方式。
那么有沒(méi)有好的解決方案呢放椰,rfc5987 發(fā)布了(基于 rfc2231作烟、rfc2183),它嚴(yán)格定義了在 HTTP 協(xié)議中如何標(biāo)準(zhǔn)化編碼 header 頭砾医;進(jìn)一步 HTTP 定義了 rfc6266拿撩,它進(jìn)一步標(biāo)準(zhǔn)化了在 HTTP 中如何編碼 Content-Disposition,可以認(rèn)為 rfc5987 和 rfc6266 是一樣的如蚜。
RFC 1806 , from which the often implemented Content-Disposition header in HTTP is derived, has a number of very serious security considerations. Content-Disposition is not part of the HTTP standard, but since it is widely implemented, we are documenting its use and risks for implementors.
那 rfc5987 如何定義編碼標(biāo)注的呢压恒?建議采用 parameter*=charset'lang'value 的格式影暴。如下:
ext-value = charset "'" [ language ] "'" value-chars
charset = "UTF-8" / "ISO-8859-1" / mime-charset
language = <Language-Tag>
value-chars = *( pct-encoded / attr-char )
pct-encoded = "%" HEXDIG HEXDIG
- charset 字符集支持 ASCII 和 UTF-8
- language 可以為空
- 值編碼,采用百分號(hào)編碼
- 當(dāng) parameter 和 parameter* 同時(shí)出現(xiàn)在 HTTP 頭中時(shí)探赫,瀏覽器應(yīng)當(dāng)使用后者型宙。
對(duì)應(yīng)到 Content-Disposition,就是輸出下面的頭:
Content-Disposition: attachment; filename="%E6%B3%95%E9%99%A2%E9%A1%B9%E7%9B%AE-%E7%B3%BB%E7%BB%9F%E6%94%AF%E6%8C%81.txt"; filename*=UTF-8''%22UTF-8%27%27%25E6%25B3%2595%25E9%2599%25A2%25E9%25A1%25B9%25E7%259B%25AE-%25E7%25B3%25BB%25E7%25BB%259F%25E6%2594%25AF%25E6%258C%2581.txt%22
新標(biāo)準(zhǔn)就是使用 filename* 代替 filename 參數(shù)伦吠,注意 UTF-8''
后面的文件名使用百分號(hào)編碼(在 PHP 中使用 rawurlencode() 函數(shù))妆兑。為了兼容老的瀏覽器,同時(shí)也建議包含 filename 參數(shù)毛仪,對(duì)于這些老的瀏覽器來(lái)說(shuō)箭跳,會(huì)忽略 filename* 這個(gè)“不標(biāo)準(zhǔn)的” field。而如果 filename 和 filename* 同時(shí)出現(xiàn)潭千,較新的瀏覽器會(huì)忽略 filename 參數(shù)。
不過(guò)借尿,就我的測(cè)試刨晴,在 IE 瀏覽器中,還是要根據(jù) UA 輸出只包含 filename field 的參數(shù)路翻,光輸出 filename* field 沒(méi)用狈癞。
以下就是完整的示例:
$ua = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
$filename = rawurlencode($filename);
if (stripos($ua, 'msie') !== false || (stripos($ua, 'rv:11') !== false )) {
header('Content-Disposition: attachment; filename="'.$filename.'"');
} else {
header('Content-Disposition: attachment; filename="'.$filename.'";' . 'filename*="UTF-8\'\'' . $filename . '"' );
}
建議閱讀:
推薦大家關(guān)注我的公眾號(hào)(ID:yudadanwx茂契,虞大膽的嘰嘰喳喳)和我的書(shū)《深入淺出HTTPS:從原理到實(shí)戰(zhàn)》