Search code examples
javaspring-mvcencodingdownloadcyrillic

How to return downloaded file name in Cyrillic?


I want to return file with Cyrillic name.

Now my code looke like:

@GetMapping("/download/{fileId}")
    public void download(@PathVariable Long fileId, HttpServletResponse response) throws IOException {
        ...
        response.setContentType("txt/plain" + "; charset=" + "WINDOWS-1251");
        String filename = "русское_слово.txt";
        response.addHeader("Content-disposition", "attachment; filename=" + filename);
        response.addHeader("Access-Control-Expose-Headers", "Content-disposition");
        //...
    }

When I access url from browser - browser provides me dialog to save file on disk but it show _ instead of Cyrillic symbols.

Looks like it is response header encoding issue:

{
  "access-control-expose-headers": "Content-disposition",
  "content-disposition": "attachment; filename=???_??.txt",
  "date": "Fri, 28 Dec 2018 15:53:44 GMT",
  "transfer-encoding": "chunked",
  "content-type": "txt/plain;charset=WINDOWS-1251"
}

I tried following option:

response.addHeader("Content-disposition", "attachment; filename*=UTF-8''" + filename);

and following:

response.addHeader("Content-disposition", "attachment; filename*=UTF-8''" + URLEncoder.encode(filename,"UTF-8"));

but it doesn't help

How can I fix this issue?


Solution

  • If you're on Spring 5+ you can use ContentDisposition:

    String filename = "русское слово.txt";
    
    ContentDisposition contentDisposition = ContentDisposition.builder("attachment")
        .filename(filename, StandardCharsets.UTF_8)
        .build();
    System.out.println(contentDisposition.toString());
    

    Output:

    attachment; filename*=UTF-8''%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%BE%D0%B5%20%D1%81%D0%BB%D0%BE%D0%B2%D0%BE.txt
    

    The ContentDisposition hides all the work you're trying to do (see its toString implementation):

    if (this.filename != null) {
        if (this.charset == null || StandardCharsets.US_ASCII.equals(this.charset)) {
            sb.append("; filename=\"");
            sb.append(this.filename).append('\"');
        }
        else {
            sb.append("; filename*=");
            sb.append(encodeHeaderFieldParam(this.filename, this.charset));
        }
    }
    

    Also if you don't want to deal with HttpServletRequest directly you can return ResponseEntity instead:

    @RequestMapping("/")
    public ResponseEntity<Resource> download() {
      HttpHeaders httpHeaders = new HttpHeaders();
      String filename = "русское_слово.txt";
    
      ContentDisposition contentDisposition = ContentDisposition.builder("attachment")
          .filename(filename, StandardCharsets.UTF_8)
          .build();
      httpHeaders.setContentDisposition(contentDisposition);
    
      return new ResponseEntity<>(new ByteArrayResource(new byte[0]),
          httpHeaders, HttpStatus.OK);
    }