How to encode cyrillic (non-ASCII) filename in the header Content-Disposition for different browsers

After spending several hours aggregating data from numerous sources, I decided to compose this article. The code has been tested in IE, Chrome and Firefox. I want to save a file with name «мале нький.txt» — I added a white space intentionally to test this case. Please follow the link for details.

I will stick to the crucial aspect — creation of a proper header.

 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
...
 
    private static final String CONTENT_DISP_PREFIX = "attachment; filename=";
    private static final String CONTENT_DISP_EXTRA_PREFIX = "attachment; filename*=UTF-8''";
    private static final String USER_AGENT_FIREFOX = "Firefox";
...
 
    @RequestMapping(value = "get/{fileId}", method = RequestMethod.GET)
    @ResponseBody
    public void getFile(@PathVariable("fileId") Long fileId,
                          HttpServletRequest request,
                          HttpServletResponse response) {
        String ua = request.getHeader(HttpHeaders.USER_AGENT);
        // we are in controller's method which handles requests for a file and sends response with it
        String filename = "мале нький.txt";
        String encodedFileName = null;
        try {
            // Specific handling for non-ASCII characters
            encodedFileName = rfc5987_encode(attachedFile.getFileName());
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
 
        if (!ua.contains(USER_AGENT_FIREFOX)) {
            response.setHeader(CONTENT_DISP_HEADER, CONTENT_DISP_PREFIX + "\"" + encodedFileName + "\"");
        } else {
            response.setHeader(CONTENT_DISP_HEADER, CONTENT_DISP_EXTRA_PREFIX  + encodedFileName );
        }
...

Encoding method:

    public static String rfc5987_encode(final String s) throws UnsupportedEncodingException {
        final byte[] s_bytes = s.getBytes("UTF-8");
        final int len = s_bytes.length;
        final StringBuilder sb = new StringBuilder(len << 1);
        final char[] digits = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
        final byte[] attr_char = {'!','#','$','&','+','-','.','0','1','2','3','4','5','6','7','8','9',           'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','^','_','`',                        'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','|', '~'};
        for (int i = 0; i < len; ++i) {
            final byte b = s_bytes[i];
            if (Arrays.binarySearch(attr_char, b) >= 0)
                sb.append((char) b);
            else {
                sb.append('%');
                sb.append(digits[0x0f & (b >>> 4)]);
                sb.append(digits[b & 0x0f]);
            }
        }
 
        return sb.toString();
    }
You can leave a response, or trackback from your own site.

Leave a Reply