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

StackOverflow https://stackoverflow.com/questions/93551

Вопрос

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

Content-Disposition: attachment; filename=ИМЯ ФАЙЛА

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

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

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

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

  • Fire Fox
  • Интернет Эксплорер
  • Сафари
  • Гугл Хром
  • Опера
Это было полезно?

Решение

Обсуждается это, включая ссылки на тестирование браузеров и обратную совместимость, в предлагаемом РФК 5987, «Набор символов и языковая кодировка для параметров поля заголовка протокола передачи гипертекста (HTTP)».

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

Другие советы

Я знаю, что это старый пост, но он по-прежнему очень актуален.Я обнаружил, что современные браузеры поддерживают rfc5987, который позволяет использовать кодировку utf-8, процентное кодирование (URL-кодирование).Тогда Naïve file.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

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

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

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

@Алекс Жуковский:Я не знаю почему, но как обсуждалось в Соединять кажется, это не очень хорошо работает.

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

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

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

/real_script.php/fake_filename.doc

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

Символы в URL-адресах должны быть в кодировке UTF-8 с побайтовой кодировкой:

/mot%C3%B6rhead   # motörhead

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

6.Вопросы интернационализации

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

И в их раздел примеров:

Этот пример такой же, как и вышеуказанный, но добавление параметра «имя файла» для совместимости с пользовательскими агентами, не реализующими РФК 5987:

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

Примечание:Те пользовательские агенты, которые не поддерживают РФК 5987 кодирование игнорируется »filename*», когда это происходит после «filename”.

В Приложение Д существует также длинный список предложений по повышению совместимости.Это также указывает на сайт, на котором сравниваются реализации.Текущие комплексные тесты, подходящие для распространенных имен файлов, включают:

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

Что РФК 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)

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

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

http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download

Я протестировал несколько вариантов.Браузеры не поддерживают спецификации и действуют по-другому. Я считаю, что двойные кавычки — лучший вариант.

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

Джава:

fileName = request.getHeader ( "user-agent" ).contains ( "MSIE" ) ? URLEncoder.encode ( fileName, "utf-8") : MimeUtility.encodeWord ( fileName );
response.setHeader ( "Content-disposition", "attachment; filename=\"" + fileName + "\"");

В веб-API ASP.NET я 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

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

$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» (на основе этот пост в блоге и эти тестовые случаи).

$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 и "safe";если нет, он добавляет имя файла*=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 как 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-файла путем реализации BytesToString() функция от include_aspuploader.asp в моем собственном коде я смог получить UTF-8 имена файлов рабочие.


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

Просто обновление, так как сегодня я пробовал все это в ответ на проблему клиента.

  • За исключением Safari, настроенного для японского языка, все браузеры, протестированные нашими клиентами, лучше всего работали с filename=text.pdf, где текст — это значение клиента, сериализованное ASP.Net/IIS в формате utf-8 без кодировки URL-адреса.По какой-то причине Safari, настроенный для английского языка, примет и правильно сохранит файл с японским именем utf-8, но тот же браузер, настроенный для японского языка, сохранит файл с неинтерпретированными символами utf-8.Все остальные протестированные браузеры работали лучше/отлично (независимо от языковой конфигурации) с именем файла в кодировке utf-8 без кодировки URL.
  • Я не смог найти ни одного браузера, реализующего Rfc5987/8187. совсем.Я тестировал последние сборки Chrome, Firefox, а также IE 11 и Edge.Я попытался установить заголовок только с именем файла*=utf-8''texturlencoded.pdf, установив его как с именем файла=text.pdf;имя_файла*=utf-8''texturlencoded.pdf.Ни одна функция Rfc5987/8187 не обрабатывалась правильно ни в одном из вышеперечисленных случаев.

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top