Java servlet 下载文件名特殊字符

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5325322/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 10:39:45  来源:igfitidea点击:

Java servlet download filename special characters

javaservletsencodingdownload

提问by jabal

I am writing a simple file download servlet and I can't get correct filenames. Tried URLEncoding and MimeEncoding the filename as seen in existing answers, but none of them worked.

我正在编写一个简单的文件下载 servlet,但无法获得正确的文件名。尝试 URLEncoding 和 MimeEncoding 现有答案中看到的文件名,但它们都不起作用。

The fileData object in the following snippet contains the mime type, the byte[] content and the filename, that needs at least ISO-8859-2 charset, ISO-8859-1 is not enough.

以下代码段中的 fileData 对象包含 mime 类型、byte[] 内容和文件名,至少需要 ISO-8859-2 字符集,ISO-8859-1 是不够的。

How can I get my browser to display the downloaded filename correctly?

如何让浏览器正确显示下载的文件名?

Here is an example of the filename: árvízt?r?tük?rfúrógép.xls and it results in: árvíztqrptük?rfúrógép.xls

这是文件名的示例:árvízt?r?tük?rfúrógép.xls,结果为:árvíztqrptük?rfúrógép.xls

  protected void renderMergedOutputModel(Map model, HttpServletRequest req, HttpServletResponse res) throws Exception {

    RateDocument fileData = (RateDocument) model.get("command.retval");
    OutputStream out = res.getOutputStream();
    if(fileData != null) {
        res.setContentType(fileData.getMime());
        String enc = "utf-8"; //tried also: ISO-8859-2

        String encodedFileName = fileData.getName();
            // also tried URLencoding and mime encoding this filename without success

        res.setCharacterEncoding(enc); //tried with and without this
        res.setHeader("Content-Disposition", "attachment; filename=" + encodedFileName);
        res.setContentLength(fileData.getBody().length);
        out.write(fileData.getBody());
    } else {
        res.setContentType("text/html");
        out.write("<html><head></head><body>Error downloading file</body></html>"
                .getBytes(res.getCharacterEncoding()));
    }
    out.flush();
  }

回答by sporak

I found out solution that works in all browsers I have installed (IE8, FF16, Opera12, Chrome22).
It's based on the fact, that browsers expect value in filename parameter, that is encoded in browsers native encoding, if no [different] encoding is specified.

我找到了适用于我安装的所有浏览器(IE8、FF16、Opera12、Chrome22)的解决方案。
它基于这样一个事实,即浏览器期望文件名参数中的值,如果未指定 [不同] 编码,则该参数以浏览器本机编码进行编码。

Usually browser's native encoding is utf-8 (FireFox, Opera, Chrome). But IE's native encoding is Win-1250.

通常浏览器的原生编码是 utf-8(FireFox、Opera、Chrome)。但是 IE 的原生编码是 Win-1250。

So if we put value into filename parametr, that is encoded by utf-8/win-1250 according to user's browser, it should work. At least, it works for me.

因此,如果我们将 value 放入 filename 参数中,即根据用户的浏览器由 utf-8/win-1250 编码,它应该可以工作。至少,它对我有用。

String fileName = "árvízt?r?tük?rfúrógép.xls";

String userAgent = request.getHeader("user-agent");
boolean isInternetExplorer = (userAgent.indexOf("MSIE") > -1);

try {
    byte[] fileNameBytes = fileName.getBytes((isInternetExplorer) ? ("windows-1250") : ("utf-8"));
    String dispositionFileName = "";
    for (byte b: fileNameBytes) dispositionFileName += (char)(b & 0xff);

    String disposition = "attachment; filename=\"" + dispositionFileName + "\"";
    response.setHeader("Content-disposition", disposition);
} catch(UnsupportedEncodingException ence) {
    // ... handle exception ...
}

Of course, this is tested only on browsers mentioned above and I cannot guarante on 100% that this will work in any browser all time.

当然,这仅在上面提到的浏览器上进行了测试,我不能 100% 保证这将在任何浏览器中始终有效。

Note #1 (@fallen): It's not correct to use URLEncoder.encode() method. Despite method's name, it doesn't encode string into URL-encoding, but it does encode into form-encoding. (Form-encoding is quite similiar to URL-encoding and in a lot of cases it produces same results. But there are some differences. For example space character ' ' is encoded different: '+' instead of '%20')

Note #1 (@fallen):使用 URLEncoder.encode() 方法是不正确的。尽管方法的名称,它不会将字符串编码为 URL 编码,但它会编码为表单编码。(表单编码与 URL 编码非常相似,在很多情况下它会产生相同的结果。但有一些差异。例如,空格字符 ' ' 的编码不同:'+' 而不是 '%20')

For correct URL-encoded string you should use URI class:

对于正确的 URL 编码字符串,您应该使用 URI 类:

URI uri = new URI(null, null, "árvízt?r?tük?rfúrógép.xls", null);
System.out.println(uri.toASCIIString());

回答by ilalex

Unfortunately, it depends on the browser. See thistopic of discussion this problem. To solve your problem, look at this sitewith examples of different headers and their behavior in diffrent browsers.

不幸的是,这取决于浏览器。看这个话题讨论这个问题。要解决您的问题,请查看此站点,其中包含不同标题及其在不同浏览器中的行为的示例。

回答by Michael-O

Based on the great answers given here, I have developed an extended version which I have put into production already. Based on RFC 5987and thistest suite.

基于这里给出的很好的答案,我开发了一个扩展版本,我已经投入生产了。基于RFC 5987这个测试套件。

String filename = "freaky-multibyte-chars";
StringBuilder contentDisposition = new StringBuilder("attachment");
CharsetEncoder enc = StandardCharsets.US_ASCII.newEncoder();
boolean canEncode = enc.canEncode(filename);
if (canEncode) {
    contentDisposition.append("; filename=").append('"').append(filename).append('"');
} else {
    enc.onMalformedInput(CodingErrorAction.IGNORE);
    enc.onUnmappableCharacter(CodingErrorAction.IGNORE);

    String normalizedFilename = Normalizer.normalize(filename, Form.NFKD);
    CharBuffer cbuf = CharBuffer.wrap(normalizedFilename);

    ByteBuffer bbuf;
    try {
        bbuf = enc.encode(cbuf);
    } catch (CharacterCodingException e) {
        bbuf = ByteBuffer.allocate(0);
    }

    String encodedFilename = new String(bbuf.array(), bbuf.position(), bbuf.limit(),
            StandardCharsets.US_ASCII);

    if (StringUtils.isNotEmpty(encodedFilename)) {
        contentDisposition.append("; filename=").append('"').append(encodedFilename)
                .append('"');
    }

    URI uri;
    try {
        uri = new URI(null, null, filename, null);
    } catch (URISyntaxException e) {
        uri = null;
    }

    if (uri != null) {
        contentDisposition.append("; filename*=UTF-8''").append(uri.toASCIIString());
    }

}

回答by fallen

I have recently solved this issue in my application. here is the solution for firefox only, it sadly fails on IE.

我最近在我的应用程序中解决了这个问题。这是仅适用于 Firefox 的解决方案,遗憾的是它在 IE 上失败了。

response.addHeader("Content-Disposition", "attachment; filename*='UTF-8'" + URLEncoder.encode("árvízt?r?tük?rfúrógép", "UTF-8") + ".xls");

response.addHeader("Content-Disposition", "attachment; filename*='UTF-8'" + URLEncoder.encode("árvízt?r?tük?rfúrógép", "UTF-8") + ".xls");

回答by reznic

private void setContentHeader(HttpServletResponse response, String userAgent, String fileName) throws UnsupportedEncodingException {
    fileName = URLEncoder.encode(fileName, "UTF-8");
    boolean isFirefox = (userAgent.indexOf("Firefox") > -1);
    if (isFirefox) {
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + fileName);
    } else {
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName);
    }
}

回答by Valerii Starovoitov

Summing all I read so far this works for me:

总结我到目前为止阅读的所有内容,这对我有用:

    URI uri = new URI( null, null, fileName, null);
    String fileNameEnc = uri.toASCIIString(); //URL encoded.
    String contDisp = String.format( "attachment; filename=\"%s\";filename*=utf-8''%s", fileName, fileNameEnc);
    response.setHeader( "Content-disposition", contDisp);