How to encode the filename parameter of Content-Disposition header in HTTP?

前端 未结 18 1780
北荒
北荒 2020-11-21 06:15

Web applications that want to force a resource to be downloaded rather than directly rendered in a Web browser issue a Content-Disposition hea

相关标签:
18条回答
  • 2020-11-21 06:57

    Just an update since I was trying all this stuff today in response to a customer issue

    • With the exception of Safari configured for Japanese, all browsers our customer tested worked best with filename=text.pdf - where text is a customer value serialized by ASP.Net/IIS in utf-8 without url encoding. For some reason, Safari configured for English would accept and properly save a file with utf-8 Japanese name but that same browser configured for Japanese would save the file with the utf-8 chars uninterpreted. All other browsers tested seemed to work best/fine (regardless of language configuration) with the filename utf-8 encoded without url encoding.
    • I could not find a single browser implementing Rfc5987/8187 at all. I tested with the latest Chrome, Firefox builds plus IE 11 and Edge. I tried setting the header with just filename*=utf-8''texturlencoded.pdf, setting it with both filename=text.pdf; filename*=utf-8''texturlencoded.pdf. Not one feature of Rfc5987/8187 appeared to be getting processed correctly in any of the above.
    0 讨论(0)
  • 2020-11-21 07:04

    PHP framework Symfony 4 has $filenameFallback in HeaderUtils::makeDisposition. You can look into this function for details - it is similar to the answers above.

    Usage example:

    $filenameFallback = preg_replace('#^.*\.#', md5($filename) . '.', $filename);
    $disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename, $filenameFallback);
    $response->headers->set('Content-Disposition', $disposition);
    
    0 讨论(0)
  • 2020-11-21 07:05

    I ended up with the following code in my "download.php" script (based on this blogpost and these test cases).

    $il1_filename = utf8_decode($filename);
    $to_underscore = "\"\\#*;:|<>/?";
    $safe_filename = strtr($il1_filename, $to_underscore, str_repeat("_", strlen($to_underscore)));
    
    header("Content-Disposition: attachment; filename=\"$safe_filename\""
    .( $safe_filename === $filename ? "" : "; filename*=UTF-8''".rawurlencode($filename) ));
    

    This uses the standard way of filename="..." as long as there are only iso-latin1 and "safe" characters used; if not, it adds the filename*=UTF-8'' url-encoded way. According to this specific test case, it should work from MSIE9 up, and on recent FF, Chrome, Safari; on lower MSIE version, it should offer filename containing the ISO8859-1 version of the filename, with underscores on characters not in this encoding.

    Final note: the max. size for each header field is 8190 bytes on apache. UTF-8 can be up to four bytes per character; after rawurlencode, it is x3 = 12 bytes per one character. Pretty inefficient, but it should still be theoretically possible to have more than 600 "smiles" %F0%9F%98%81 in the filename.

    0 讨论(0)
  • 2020-11-21 07:06

    I tested the following code in all major browsers, including older Explorers (via the compatibility mode), and it works well everywhere:

    $filename = $_GET['file']; //this string from $_GET is already decoded
    if (strstr($_SERVER['HTTP_USER_AGENT'],"MSIE"))
      $filename = rawurlencode($filename);
    header('Content-Disposition: attachment; filename="'.$filename.'"');
    
    0 讨论(0)
  • 2020-11-21 07:10

    I know this is an old post but it is still very relevant. I have found that modern browsers support rfc5987, which allows utf-8 encoding, percentage encoded (url-encoded). Then Naïve file.txt becomes:

    Content-Disposition: attachment; filename*=UTF-8''Na%C3%AFve%20file.txt
    

    Safari (5) does not support this. Instead you should use the Safari standard of writing the file name directly in your utf-8 encoded header:

    Content-Disposition: attachment; filename=Naïve file.txt
    

    IE8 and older don't support it either and you need to use the IE standard of utf-8 encoding, percentage encoded:

    Content-Disposition: attachment; filename=Na%C3%AFve%20file.txt
    

    In ASP.Net I use the following code:

    string contentDisposition;
    if (Request.Browser.Browser == "IE" && (Request.Browser.Version == "7.0" || Request.Browser.Version == "8.0"))
        contentDisposition = "attachment; filename=" + Uri.EscapeDataString(fileName);
    else if (Request.Browser.Browser == "Safari")
        contentDisposition = "attachment; filename=" + fileName;
    else
        contentDisposition = "attachment; filename*=UTF-8''" + Uri.EscapeDataString(fileName);
    Response.AddHeader("Content-Disposition", contentDisposition);
    

    I tested the above using IE7, IE8, IE9, Chrome 13, Opera 11, FF5, Safari 5.

    Update November 2013:

    Here is the code I currently use. I still have to support IE8, so I cannot get rid of the first part. It turns out that browsers on Android use the built in Android download manager and it cannot reliably parse file names in the standard way.

    string contentDisposition;
    if (Request.Browser.Browser == "IE" && (Request.Browser.Version == "7.0" || Request.Browser.Version == "8.0"))
        contentDisposition = "attachment; filename=" + Uri.EscapeDataString(fileName);
    else if (Request.UserAgent != null && Request.UserAgent.ToLowerInvariant().Contains("android")) // android built-in download manager (all browsers on android)
        contentDisposition = "attachment; filename=\"" + MakeAndroidSafeFileName(fileName) + "\"";
    else
        contentDisposition = "attachment; filename=\"" + fileName + "\"; filename*=UTF-8''" + Uri.EscapeDataString(fileName);
    Response.AddHeader("Content-Disposition", contentDisposition);
    

    The above now tested in IE7-11, Chrome 32, Opera 12, FF25, Safari 6, using this filename for download: 你好abcABCæøåÆØÅäöüïëêîâéíáóúýñ½§!#¤%&()=`@£$€{[]}+´¨^~'-_,;.txt

    On IE7 it works for some characters but not all. But who cares about IE7 nowadays?

    This is the function I use to generate safe file names for Android. Note that I don't know which characters are supported on Android but that I have tested that these work for sure:

    private static readonly Dictionary<char, char> AndroidAllowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._-+,@£$€!½§~'=()[]{}0123456789".ToDictionary(c => c);
    private string MakeAndroidSafeFileName(string fileName)
    {
        char[] newFileName = fileName.ToCharArray();
        for (int i = 0; i < newFileName.Length; i++)
        {
            if (!AndroidAllowedChars.ContainsKey(newFileName[i]))
                newFileName[i] = '_';
        }
        return new string(newFileName);
    }
    

    @TomZ: I tested in IE7 and IE8 and it turned out that I did not need to escape apostrophe ('). Do you have an example where it fails?

    @Dave Van den Eynde: Combining the two file names on one line as according to RFC6266 works except for Android and IE7+8 and I have updated the code to reflect this. Thank you for the suggestion.

    @Thilo: No idea about GoodReader or any other non-browser. You might have some luck using the Android approach.

    @Alex Zhukovskiy: I don't know why but as discussed on Connect it doesn't seem to work terribly well.

    0 讨论(0)
  • 2020-11-21 07:10

    I use the following code snippets for encoding (assuming fileName contains the filename and extension of the file, i.e.: test.txt):


    PHP:

    if ( strpos ( $_SERVER [ 'HTTP_USER_AGENT' ], "MSIE" ) > 0 )
    {
         header ( 'Content-Disposition: attachment; filename="' . rawurlencode ( $fileName ) . '"' );
    }
    else
    {
         header( 'Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode ( $fileName ) );
    }
    

    Java:

    fileName = request.getHeader ( "user-agent" ).contains ( "MSIE" ) ? URLEncoder.encode ( fileName, "utf-8") : MimeUtility.encodeWord ( fileName );
    response.setHeader ( "Content-disposition", "attachment; filename=\"" + fileName + "\"");
    
    0 讨论(0)
提交回复
热议问题