2011-03-16 54 views
10

我正在寫一個簡單的文件下載servlet,我無法獲得正確的文件名。嘗試URLEncoding和MimeEncoding在現有的答案中看到的文件名,但沒有一個工作。Java servlet下載文件名特殊字符

以下片段中的fileData對象包含MIME類型,字節[]內容和文件名,至少需要ISO-8859-2字符集,ISO-8859-1是不夠的。

如何讓我的瀏覽器正確顯示下載的文件名?

這裏是文件名的例子:á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(); 
    } 
+0

請給,你會得到什麼,而不是文件名怎麼看一些例子。 – BalusC 2011-03-16 12:53:55

+0

árvíztűrőtükörfúrógép.xls - >árvíztqrptükörfúrógép.xls – jabal 2011-03-16 13:15:49

+1

是的,你是對的。這兩個字符在ISO-8859-2中僅在ISO-8859-2中不存在,導致每個匈牙利開發者都遇到許多問題.. :-) – jabal 2011-03-16 13:28:32

回答

20

我發現了適用於我安裝的所有瀏覽器(IE8,FF16,Opera12,Chrome22)的解決方案。
這是基於這樣一個事實,即如果沒有指定[不同的]編碼,瀏覽器期望在文件名參數中使用瀏覽器本機編碼編碼的值。

通常瀏覽器的本機編碼是utf-8(FireFox,Opera,Chrome)。但IE的本機編碼是Win-1250。

因此,如果我們將值放入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 ... 
} 

當然,這隻在上面提到的瀏覽器上進行過測試,我無法保證100%這個功能可以在任何瀏覽器中工作。

注意#1(@fallen): 使用URLEncoder.encode()方法是不正確的。儘管方法的名稱,它不會將字符串編碼爲URL編碼,但它確實編碼爲表單編碼。 (表單編碼類似於URL編碼,在很多情況下它會產生相同的結果,但是有一些差異,例如空格字符''編碼不同:'+'而不是'%20')

對於正確的URL編碼字符串,你應該使用URI類:

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

我認爲如果你的文件名中包含「但除此之外,這真是太棒了 - 謝謝!」 – teedyay 2012-12-18 12:21:42

+3

IE的本地編碼是中歐/東歐代碼頁?你一定是在開玩笑。它唯一顯示的是IE使用本地瀏覽器的系統區域設置不幸的是,我不認爲有一種可靠的方法可以從服務器上檢測到它 – 2013-02-27 05:32:42

+1

爲什麼它能正常工作?如果最初的'fileName'只是一個單獨的字符,例如'ő',那麼'fileName。 getBytes(「UTF-8」)將返回一個包含兩個元素「0xC5 0x91」的字節數組,上面的解決方案遍歷這兩個字節並將它們附加到一個新的字符串中,這個新的字符串將是兩個*字符* *字節*長。到底是什麼?順便說一下它的工作原理,但我無法繞過我的頭爲什麼 – 2015-06-02 13:42:35

3

不幸的是,它依賴於瀏覽器。請參閱this討論這個問題的話題。要解決您的問題,請查看this site,其中包含不同標題的示例以及它們在不同瀏覽器中的行爲。

1

我最近在我的應用程序中解決了這個問題。 這裏是唯一的解決方案,它可悲的是在IE瀏覽器上失敗。

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

+0

謝謝,但我仍在尋找終極解決方案.. :-)目前我將每個change更改爲u和ő到o的文件名,這比哪個更好?分數。 – jabal 2011-05-10 08:09:15

+0

任何人都可以告訴我什麼是我使用Safari 5.1.7的結果。我有同樣的問題。上面的代碼在firefox,chrome和IE瀏覽器上運行良好,但它不適用於safari。 – vermaraj 2014-07-31 09:45:43

3

基於這裏給出的偉大的答案,我已經開發了我已經投入生產的擴展版本。基於RFC 5987this測試套件。

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()); 
    } 

} 
+0

最好的答案。 – talipkorkmaz 2015-07-06 14:01:54

0
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); 
    } 
} 
0

取所有我讀到目前爲止,這對我的作品:

 

    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);