本文是关于“如何编码http包的Content-Disposition中的filename字段?”这个问题的又一次探讨。这个问题在很久之前被提出来以后,到现在仍然没有满意的答案,至少我认为是这样的,所以今天我再次把这个问题抛出来,附上我的解决办法。
我编写了一个基于C++的CGI应用,他可以解析包含特殊字符文件名的文件,比如像这样:weird # € = { } ; filename.txt。
似乎没有一种通用方法来搞定HTTP中的Content-Dispostion使之可以在每个浏览器中都正常工作,测试的浏览器有以下:
- Internet Explorer
- Firefox
- Chrome
- Opera
- Safari
不过我倒是很乐意针对不同的浏览器用不同的办法来编码。
下面就说说我采用的办法:
Internet Explorer(添加双引号并且替换#和;符号):
Content-Disposition: attachment; filename="weird %23 € = { } %3B filename.txt"
Firefox(双引号仍然有用,无需其他改动):
Content-Disposition: attachment; filename="weird # € = { } ; filename.txt"
另有一种可行的替代方案:
Content-Disposition: attachment; filename*=UTF-8‘‘weird%20%23%20%e2%82%ac%20%3D%20%7B%20%7D%20%3B%20filename.txt
Chrome:
只用双引号的时候会出现下面这些问题:
- 文件名中的=符号会丢失
- €会被替换成-符号
用这种办法就能搞定了:
Content-Disposition: attachment; filename*=UTF-8‘‘weird%20%23%20%e2%82%ac%20%3D%20%7B%20%7D%20%3B%20filename.txt
Opera:
用双引号或者语法:filename*=UTF-8”,会产生下面这些问题:
- 文件名中的多个连续空格只剩下一个
- 成对的{}丢失:”ab{}cd.txt” -> “abcd.txt”
- 文件名中的分号会截断后面的字符:”abc ; def.txt” -> “abc”
这是由于文件名长度限制造成的,下面的例子可以在Opera中工作:
Content-Disposition: attachment; filename*=UTF-8‘‘weird%20%23%20%e2%82%ac%20%3D%20%7B%20%7D%20%3B%20filename.txt
Safari:
如果用双引号,€符号会被替换成不可见字符,很遗憾没有合适的办法来解决这个小问题。
但是从上面提到的那个问题原文中得到一个可以参考的办法:
Content-Disposition: attachment; filename*=UTF-8‘‘weird%20%23%20%80%20%3D%20%7B%20%7D%20%3B%20filename.txt
但是这个方案对我来说没有用,这些转义字符没办法被正确的还原所以浏览器尝试了用cgi应用的名称来保存文件。造成这个问题的原因是我采用的编码方式不正确。我没有按照RFC 5987的原则来编码。不过Safari也同样没有采用这种编码方式。所以只能说€字符的编码方法暂时无解了。
顺便提一下,一个UTF-8编码转换器:http://www.rishida.net/tools/conversion/
上文提到的所有测试均用了当前最新版本的浏览器:
- Firefox 7
- Internet Explorer 9
- Chrome 15
- Opera 11.5
- Safari 5.1
PS:我尝试过键盘上所有的特殊字符,但是被我提到的是那些会造成问题的字符。
我顺便尝试了下在文件名中同时包含所有可能出现的特殊字符,测试结果和上面提到的不太一样:
完整的测试字符串:
0!§ $%&()=`´{} []²³@€µ^°~+‘ # - _ . , ; ü ä ö ß 9.jpg
编码后:
0%20%21%20%C2%A7%20%24%20%25%20%26%20%28%20%29%20%3D%20%60%20%C2%B4%20%7B%20%7D%20%20%20%20%5B%20%5D%20%C2%B2%20%C2%B3%20%40%20%E2%82%AC%20%C2%B5%20%5E%20%C2%B0%20~%20%2B%20%27%20%23%20-%20_%20.%20%2C%20%3B%20%C3%BC%20%C3%A4%20%C3%B6%20%C3%9F%209.jpg
Content-Disposition
用这种方式来写:
Content-Disposition: attachment; filename*=UTF-8‘‘0%20%21%20%C2%A7%20%24%20%25%20%26%20%28%20%29%20%3D%20%60%20%C2%B4%20%7B%20%7D%20%20%20%20%5B%20%5D%20%C2%B2%20%C2%B3%20%40%20%E2%82%AC%20%C2%B5%20%5E%20%C2%B0%20~%20%2B%20%27%20%23%20-%20_%20.%20%2C%20%3B%20%C3%BC%20%C3%A4%20%C3%B6%20%C3%9F%209.jpg
得到了如下测试结果:
火狐可以正常工作
Chrome可以正常工作
IE显示:$ % & ( ) = ` ´ { } [ ] ² ³ @ € µ ^ ° ~ + ‘ # – _ . , ; ü ä ö ß 9.jpg 丢失了头6个字符
解释下:出现这个问题是因为浏览器对文件名字符长度的限制造成的:从字符串的开头舍弃一些字符。我没有深入挖掘这一点,不过正常的文件名大约可以长达大概200个字符,那些文件名包含许多转义字符序列的长度甚至可以更多,但是不超过250个字符。所以说这个其实没啥问题。
Opera:0 ! § $ % & ( ) = ` ´ [ ] ² ³ @ € µ ^ ° ~ + ‘ # – _ . , ; ü ä ö ß 9.jpg 和IE中的那样也丢失了一些字符。
说明:我缩减过我的测试字符串,因为我怀疑Opera中也有像IE那样的长度限制问题。
Safari中这种编码无法正常使用。
测试到现在说明一个事实:形如“filename*=UTF-8”filname escape sequence”这样的语法可在除了safari外的其他浏览器中正常工作。但是在Safari中用这种方法也只有€会被替换掉。所以这个问题不大。
关于文件名长度:
测试中发现了一些文件名长度的问题。
在Internet Explorer中:文件名长度最多可以长达147个字符。如果字符串中没有出现转移字符的话,这就是文件名的总长度了。如果字符串中出现了转移字符,情况就有些变化。最终的文件名长度小于147个字符。但是规则有点奇怪,我找不出一个准确的规则。如果我用了两个转义字符,文件名缩短了5个字符长度,但是我用很多转义字符的话文件名最终只缩短了两个字符。
其他浏览器没有这个长度限制问题。只要系统能够处理这个文件,它就能顺利保存这个文件。我测试了下250个字符长度的文件名,chrome提示我要缩减文件名长度,opera会自动缩减到220个字符,火狐自动缩减为210个字符。Opera会从文件名末尾开始缩减。Safari尝试保存那么长的文件名,但是处理失败无法保存,同时在下载列表的文件名中会显示成-1。