Как кодировать параметр filename заголовка Content-Disposition в HTTP?

веб-приложения, которые хотят заставить ресурс быть загрузить, а не вынесено в веб-браузере проблема a Content-Disposition заголовок в HTTP-ответ вида:

Content-Disposition: attachment; filename=FILENAME

на может использоваться для указания имени файла, в который браузер загружает ресурс. RFC 2183 (Content-Disposition), однако, говорится в 2.3 (Имя Параметр), что имя файла может использовать только символы US-ASCII:

текущая [RFC 2045] грамматика ограничивает значения параметров (и, следовательно, Назначение содержимого файлов) в США-ASCII. Мы признаем великое желательность разрешения произвольных наборы символов в именах файлов, но это за рамками настоящего документа определите необходимые механизмы.

есть эмпирические данные, тем не менее, что большинство популярных веб-браузеров сегодня, кажется, разрешить символы, отличные от US-ASCII, но (из-за отсутствия стандарта) не согласны со схемой кодирования и спецификацией набора символов имени файла. Вопрос в том, какие различные схемы и кодировки используются популярными браузерами, если имя файла "naïvefile" (без кавычек и где третья буква U+00EF) необходимо закодировать в заголовок Content-Disposition?

для целей этого вопроса,популярные браузеры существо:

  • в Firefox
  • Internet Explorer
  • сафари
  • Google Chrome
  • Опера

17 ответов


существует обсуждение этого, включая ссылки на тестирование браузера и обратную совместимость, в предложенном RFC 5987, " набор символов и кодировка языка для параметров поля заголовка протокола передачи гипертекста (HTTP)."

RFC 2183 указывает на то, что такие заголовки должны быть закодированы в соответствии с RFC 2184, который был заменен на RFC 2231, охватывается проектом RFC выше.


Я знаю, это старый пост, но он по-прежнему очень актуальна. Я обнаружил, что современные браузеры поддерживают rfc5987, который позволяет кодировать utf-8, процент кодируется (url-кодируется). Тогда наивный файл.txt становится:

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

Safari (5) не поддерживает это. Вместо этого вы должны использовать стандарт Safari для записи имени файла непосредственно в кодированном заголовке utf-8:

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

IE8 и старше не поддерживают его, и вам нужно использовать стандарт IE кодировки utf-8, в процентах закодировано:

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

In ASP.Net я использую следующий код:

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

я протестировал выше, используя IE7, IE8, IE9, Chrome 13, Opera 11, FF5, Safari 5.

обновление ноября 2013 года:

вот код, который я сейчас использую. Мне все еще нужно поддерживать IE8, поэтому я не могу избавиться от первой части. Оказывается, браузеры на Android используют встроенный менеджер загрузки Android и не могут надежно анализировать имена файлов в стандарте путь.

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

вышеуказанное теперь протестировано в IE7-11, Chrome 32, Opera 12, FF25, Safari 6, используя это имя файла для загрузки: §abcabcæøåæøåäöüïëêîâéíáóúýñ½§!#¤%&()=`@£$€{[]}+^~'-_,;.txt

на IE7 он работает для некоторых символов, но не для всех. Но кто заботится о IE7 в настоящее время?

это функция, которую я использую для создания безопасных имен файлов для Android. Обратите внимание, что я не знаю, какие символы поддерживаются на Android, но я проверил, что они работают для конечно:

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: я тестировал в IE7 и IE8, и оказалось, что мне не нужно избегать Апострофа ('). У вас есть пример где это не удается?

@Dave Van den Eynde: объединение двух имен файлов в одной строке, как в соответствии с RFC6266 работает, за исключением Android и IE7+8, и я обновил код, чтобы отразить это. Спасибо за предложение.

@Thilo: нет идеи о GoodReader или любом другом не-браузере. Возможно, Вам повезет с помощью Android подход.

@Alex Zhukovskiy: я не знаю, почему, но как обсуждалось на подключиться кажется, это не очень хорошо работает.


существует простая и очень надежная альтернатива:используйте URL, содержащий имя файла, которое вы хотите.

когда имя после последней косой черты является тем, что вы хотите, вам не нужны дополнительные заголовки!

этот трюк работает:

/real_script.php/fake_filename.doc

и если ваш сервер поддерживает перезапись URL (например,mod_rewrite в Apache), то вы можете полностью скрыть скрипт часть.

символы в URL-адресах должны быть в UTF-8, urlencoded байт за байтом:

/mot%C3%B6rhead   # motörhead

RFC 6266 описывает "использование поля заголовка Content-Disposition в протоколе передачи гипертекста (HTTP)". Цитата из этого:

6. Соображения Интернационализации

в "filename*" параметр (4.3), используя определенную кодировку в [ RFC5987], позволяет серверу передавать символы вне ISO-8859-1 набор символов, а также дополнительно укажите язык в употреблении.

и в примеры:

этот пример совпадает с приведенным выше, но добавляет " filename" параметр совместимости с агентами пользователей, не реализующими RFC 5987:

Content-Disposition: attachment;
                     filename="EURO rates";
                     filename*=utf-8''%e2%82%ac%20rates

Примечание: те агенты пользователей, которые не поддерживают RFC 5987 кодировка игнорировать "filename*" когда это происходит после "filename".

на Приложение D существует также длинный список предложений по повышению совместимости. Он также указывает на сайт, который сравнивает реализаций. Текущие тесты all-pass, подходящие для общих имен файлов, включают:

  • attwithisofnplain: простое имя файла ISO-8859-1 с двойными кавычками и без кодировки. Для этого требуется имя файла, которое является ISO-8859-1 и не содержит знаков процента, по крайней мере, не перед шестнадцатеричными цифрами.
  • attfnboth: два параметра в порядке, описанном выше. Должен работать для большинства имен файлов в большинстве браузеров, хотя IE8 будет использовать "filename параметр".

это RFC 5987 в свою очередь, ссылается RFC 2231, который описывает фактический формат. 2231 в основном для почты, и 5987 говорит нам, какие части могут использоваться для HTTP-заголовков. Не путайте это с заголовками MIME используется внутри multipart/form-data адресу http тело, который регулируется RFC 2388 (4.4 в частности) и HTML 5 черновик.


следующий документ, связанный с проект RFC упомянутые Джим в своем ответе далее обращается к вопросу и, безусловно, стоит отметить здесь:

тестовые примеры для заголовка HTTP Content-Disposition и кодировки RFC 2231/2047


в asp.net mvc2 я использую что-то вроде этого:

return File(
    tempFile
    , "application/octet-stream"
    , HttpUtility.UrlPathEncode(fileName)
    );

Я думаю, если вы не используете mvc(2), Вы можете просто кодировать имя файла, используя

HttpUtility.UrlPathEncode(fileName)

Я использую следующие фрагменты кода для кодирования (предположим имя файла содержит имя файла и расширение файла, т. е.: 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 + "\"");

In ASP.NET Web API, я url кодирую имя файла:

public static class HttpRequestMessageExtensions
{
    public static HttpResponseMessage CreateFileResponse(this HttpRequestMessage request, byte[] data, string filename, string mediaType)
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
        var stream = new MemoryStream(data);
        stream.Position = 0;

        response.Content = new StreamContent(stream);

        response.Content.Headers.ContentType = 
            new MediaTypeHeaderValue(mediaType);

        // URL-Encode filename
        // Fixes behavior in IE, that filenames with non US-ASCII characters
        // stay correct (not "_utf-8_.......=_=").
        var encodedFilename = HttpUtility.UrlEncode(filename, Encoding.UTF8);

        response.Content.Headers.ContentDisposition =
            new ContentDispositionHeaderValue("attachment") { FileName = encodedFilename };
        return response;
    }
}

IE 9 Not fixed
IE 9 Fixed


поместите имя файла в двойные кавычки. Решил проблему за меня. Вот так:

Content-Disposition: attachment; filename="My Report.doc"

http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download


я протестировал следующий код во всех основных браузерах, включая старые полярники (через режим совместимости), и он хорошо работает везде:

$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.'"');

Если вы используете бэкэнд nodejs, вы можете использовать следующий код, который я нашел здесь

var fileName = 'my file(2).txt';
var header = "Content-Disposition: attachment; filename*=UTF-8''" 
             + encodeRFC5987ValueChars(fileName);

function encodeRFC5987ValueChars (str) {
    return encodeURIComponent(str).
        // Note that although RFC3986 reserves "!", RFC5987 does not,
        // so we do not need to escape it
        replace(/['()]/g, escape). // i.e., %27 %28 %29
        replace(/\*/g, '%2A').
            // The following are not required for percent-encoding per RFC5987, 
            // so we can allow for a little better readability over the wire: |`^
            replace(/%(?:7C|60|5E)/g, unescape);
}

Я закончил со следующим кодом в моем " download.на php" скрипт (на основе это blogpost и тесты).

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

Это использует стандартный способ filename="..."пока используются только iso-latin1 и "безопасные" символы; если нет, он добавляет имя файла*=UTF-8" url-кодированным способом. Согласно этот конкретный тест, он должен работать от MSIE9 вверх, и на недавнем FF, Chrome, Safari; на более низкой версии MSIE, он должен предложите имя файла, содержащее версию ISO8859-1 имени файла, с подчеркиваниями на символах, не входящих в эту кодировку.

последнее замечание: Макс. размер для каждого поля заголовка составляет 8190 байт на Apache. UTF-8 может быть до четырех байт на символ; после rawurlencode это x3 = 12 байт на один символ. Довольно неэффективно, но все равно теоретически возможно иметь более 600 "улыбок" %F0%9F%98% 81 в имени файла.


в PHP это сделало это для меня (предполагая, что имя файла кодируется UTF8):

header('Content-Disposition: attachment;'
    . 'filename="' . addslashes(utf8_decode($filename)) . '";'
    . 'filename*=utf-8\'\'' . rawurlencode($filename));

протестировано против IE8-11, Firefox и Chrome.
Если браузер может интерпретировать имя файла*=utf-8 он будет использовать версию UTF8 имени файла, иначе он будет использовать декодированное имя файла. Если имя файла содержит символы, которые не могут быть представлены в ISO-8859-1, вы могли бы хотеть рассмотреть, используя iconv вместо.


классическое решение ASP

большинство современных браузеров поддерживают передачу Filename as UTF-8 теперь, но как и в случае с решением для загрузки файлов, которое я использую, основанное на FreeASPUpload.Net(сайт уже не существует, ссылка archive.org) он не будет работать как анализ бинарных опирался на чтение одного байта в ASCII закодированные строки, которые работали хорошо, когда вы прошли в кодировке UTF-8 данные, пока вы доберетесь до символов ASCII не поддерживает.

однако я смог найти решение, чтобы получить код для чтения и анализа двоичного файла как UTF-8.

Public Function BytesToString(bytes)    'UTF-8..
  Dim bslen
  Dim i, k , N 
  Dim b , count 
  Dim str

  bslen = LenB(bytes)
  str=""

  i = 0
  Do While i < bslen
    b = AscB(MidB(bytes,i+1,1))

    If (b And &HFC) = &HFC Then
      count = 6
      N = b And &H1
    ElseIf (b And &HF8) = &HF8 Then
      count = 5
      N = b And &H3
    ElseIf (b And &HF0) = &HF0 Then
      count = 4
      N = b And &H7
    ElseIf (b And &HE0) = &HE0 Then
      count = 3
      N = b And &HF
    ElseIf (b And &HC0) = &HC0 Then
      count = 2
      N = b And &H1F
    Else
      count = 1
      str = str & Chr(b)
    End If

    If i + count - 1 > bslen Then
      str = str&"?"
      Exit Do
    End If

    If count>1 then
      For k = 1 To count - 1
        b = AscB(MidB(bytes,i+k+1,1))
        N = N * &H40 + (b And &H3F)
      Next
      str = str & ChrW(N)
    End If
    i = i + count
  Loop

  BytesToString = str
End Function

заслуга Чистая загрузка файла ASP реализация С include_aspuploader.asp в моем собственном коде я смог получить UTF-8 имена рабочих.


Полезные Ссылки


у нас была аналогичная проблема в веб-приложении, и в итоге мы прочитали имя файла из HTML <input type="file">, и установка этого в URL-кодированной форме в новом HTML <input type="hidden">. Конечно, нам пришлось убрать тропинку, как ...C:\fakepath\ " это возвращается некоторыми браузерами.

конечно, это не дает прямого ответа на вопрос OPs, но может быть решением для других.


Я обычно URL-кодирую (с %xx) имена файлов, и, похоже, он работает во всех браузерах. В любом случае, вы можете сделать несколько тестов.


Я нашел решение, которое работает для всех моих браузеров (IE. все браузеры, которые я установил-IE8, FF16, Opera 12, Chrome 22).

мое решение описано в другом потоке: Java servlet скачать имя файла специальные символы

мое решение основано на том, как браузеры пытаются прочитать значение из