天天看點

探究 Content-Disposition:解決下載下傳中文檔案名亂碼

作者:jay的實驗室
探究 Content-Disposition:解決下載下傳中文檔案名亂碼

今天解決了一個設定下載下傳檔案名為中文的問題:直接在Content-Disposition中設定中文會導緻亂碼。按照網上的辦法(Content-Disposition + UTF-8)就搞定了。不過為了能搞清楚問題的關鍵所在,我還是去看了下官方文檔,了解了下Content-Disposition的字段與意義。使用Content-Disposition可以設定檔案名,但是要設定中文就需要進行編碼,而RFC 822規定Message隻能為ASCII,這就是問題所在。

Content-Disposition的定義

Hypertext Transfer Protocol – HTTP/1.1中的描述

Content-Disposition is not part of the HTTP standard, but since it is widely implemented, we are documenting its use and risks for implementors.

The Content-Disposition response-header field has been proposed as a means for the origin server to suggest a default filename if the user requests that the content is saved to a file. This usage is derived from the definition of Content-Disposition in RFC 1806.

RFC 1806中的描述

the Content-Disposition header field is defined as follows:

disposition := "Content-Disposition" ":"
                   disposition-type
                   *(";" disposition-parm)

    disposition-type := "inline"
                      / "attachment"
                      / extension-token
                      ; values are not case-sensitive

    disposition-parm := filename-parm / parameter

    filename-parm := "filename" "=" value;
           

‘extension-token’, ‘parameter’ and`’value’ are defined according to [RFC 822] and [RFC 1521].

首先要注意的是disposition-type。由上面給出的是Content-Disposition header 字段我們可以知道,Content-Disposition有兩種type,即inline和attachment。根據文檔中的介紹可知,inline類型會自動顯示附件内容,比如顯示一個圖檔;而attachment不會自動顯示,在郵件中可能會顯示為一個帶圖示的附件,在浏覽器中可能會提示下載下傳。

其次就是disposition-parm。主要作用就是提供一個建議的檔案名(filename-parm),用戶端(浏覽器、郵件系統)在盡可能的情況下會以該檔案名去儲存檔案。盡可能的意思是存在不一樣的情況,比如檔案名非法、存在同名檔案,這些情況下用戶端會采取一些措施,比如修改檔案名。

最後看看filename-parm的value。這個value就是檔案名(本文的目的是給value設定一個中文,主要的坑就是這裡)。

通過上面的介紹,要給前端發送一個檔案,并且定義檔案的名字為中文,可以在發送檔案之前傳回這樣一個HTTP Response Header :

Content-Disposition: attachment; filename=檔案.txt           

需要注意的是,RFC 822( Standard for ARPA Internet Text Messages)規定了文本消息隻能為ASCII,是以這個Content-Disposition是非法的。RFC 1521(Multipurpose Internet Mail Extensions)基于前者對編碼方式進行了拓展,使用了4種機制:

MIME-Version header

Content-Type header

Content-Transfer-Encoding header

Content-ID and Content-Description header

可惜這些與給HTTP Response Header中設定中文沒啥關系。後來google了一下,通過stackoverflow找到了一個叫 RFC 5987 - Character Set and Language Encoding for Hypertext Transfer Protocol (HTTP) Header Field Parameters 的文檔,頓時撥開迷霧見青天:

By default, message header field parameters in HTTP ([RFC2616]) messages cannot carry characters outside the ISO-8859-1 character set. RFC 2231 defines an encoding mechanism for use in MIME headers. This document specifies an encoding suitable for use in HTTP header fields that is compatible with a profile of the encoding defined in RFC 2231.

文中的Guidelines for Usage in HTTP Header Field Definitions給出了一個通用表達式:

foo-header  = "foo" LWSP ":" LWSP token ";" LWSP title-param
     title-param = "title" LWSP "=" LWSP value
                 / "title*" LWSP "=" LWSP ext-value
     ext-value   = charset  "'" [ language ] "'" value-chars
     charset     = "UTF-8" / "ISO-8859-1" / mime-charset
     value-chars = *( pct-encoded / attr-char )           

将前面非法的Content-Disposition轉化過來就是:

Content-Disposition : attachment; filename* = UTF-8''%E6%96%87%E4%BB%B6.txt           

這裡對“檔案.txt”進行了編碼:先進行UTF-8編碼,再進行pct-encoded編碼。其實就是URL_ENCODE的過程。。。

Node中的用法

除了使用filename*=UTF-8”+value外,還需要對中文進行編碼。在node中的寫法:

let name = urlencode("檔案.txt", "utf-8");
res.setHeader("Content-Disposition", "attachment; filename* = UTF-8''"+name);           

親測在Chrome、Edge、IE 11 下有效。

寫到這裡的心情:IETF的RFC真多,切實感受到了HTTP的發展 -_-||