在XHR上传中处理非ASCII文件名

5
我有一段相当标准的javascript/XHR拖放文件上传代码,但最近遇到了一个不幸的实际问题。我在桌面(Win7)上有一个名为“TEST-é-TEST.txt”的文件。在Chrome(30.0.1599.69)中,它以UTF-8的文件名传输到服务器,这样就没问题了。但是在Firefox(24.0)中,当它到达服务器时,文件名似乎被破坏了。
我不信任Firebug/Chrome告诉我的编码,所以我查看了请求数据包的十六进制代码。除了非ASCII字符在两个浏览器中确实被编码不同之外,其他一切都相同:
Chrome: C3 A9 (this is the expected UTF-8 for that character)
Firefox: EF BF BD (UTF-8 "replacement character"?!)

这是Firefox的一个bug吗?我尝试将文件重命名,将é替换为ó,但Firefox十六进制值仍然相同...所以这种混淆看起来确实像是浏览器的bug。(例如,如果Firefox混淆地发送ISO-8859-1而没有进行处理,我会看到E9字节,我可以在服务器端处理它,但它不应该被混淆!)
无论原因是什么,是否有任何客户端或服务器端可以纠正这个问题?如果确实发送了替换字符到服务器,那么它似乎无法恢复,所以我几乎肯定需要在客户端上进行处理。
是的,存在此代码的页面具有charset=utf-8,并且Firefox确认它在View>Character Encoding下感知页面为UTF-8。
此外,如果我将文件名转储到console.log中,它看起来很好-我猜它只是在/之后混淆了setRequestHeader("X-File-Name",file.name)。
最后,setRequestHeader()传递的值应该能够具有U+00FF以下的代码点,因此U+00E9(é)和U+00F3(ó)不应该引起问题,尽管更高的代码可能会触发SyntaxError:http://www.w3.org/TR/XMLHttpRequest2/#the-setrequestheader-method

如果不知道XHR在您的情况下应该从哪里获取文件名,很难在此处提供任何有用的信息。那个文件名是从哪里来的? - Boris Zbarsky
我刚刚硬编码了文件名,行为相同。xhr.setRequestHeader("X-File-Name", "TEST-é-TEST.txt"); - dlo
我认为这真的可以缩小问题所在的范围:当你传递一个包含非ASCII值的值给Firefox的setRequestHeader()方法时。如何克服? - dlo
说得对,就在顶部:24.0(如果你正在查看FF源代码,我会印象深刻……我也试过,但那是我第一次尝试深入研究) - dlo
嗯,处理FF源代码是我的日常工作,所以... ;) 在Firefox 24中,我们应该按原样传递字节。在Firefox 23中,我们曾将它们转换为UTF-8。你用什么来检查请求数据包? - Boris Zbarsky
显示剩余8条评论
1个回答

10
非常感谢Boris的帮助。以下是我在评论交互中发现的总结:
1)核心问题是HTTP请求标头应为ISO-8859-1。Chrome和Firefox的早期版本都会在setRequestHeader()调用中不加修改地传递UTF-8字符串。但这在FF24.0中发生了变化(显然Chrome也很快会改变),因此FF丢弃高字节并仅传递每个字符的低字节。在我提出的问题示例中,这是可以恢复的,但具有更高代码的字符可能无法恢复。
2)一种解决方法是在客户端进行编码,例如:
setRequestHeader('X-File-Name',encodeURIComponent(filename))

然后在服务器端进行解码,例如在PHP中:

$filename=rawurldecode($_SERVER['HTTP_X_FILE_NAME'])

3) 请注意,这只是有问题的原因是我的ajax文件上传方法是将原始文件数据发送到请求正文中,因此我需要通过自定义请求标头(如许多在线教程中所示)发送文件名。如果我使用FormData,我就不必担心这个问题了。我相信如果您想要稳定的、基于标准的Unicode文件名支持,您应该使用FormData而不是请求标头方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接