Как закодировать параметр имени файла заголовка Content-Disposition в HTTP? - PullRequest
489 голосов
/ 18 сентября 2008

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

Content-Disposition: attachment; filename=<em>FILENAME</em>

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

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

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

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

  • Firefox
  • Internet Explorer
  • Safari
  • Google Chrome
  • Opera

Ответы [ 17 ]

340 голосов
/ 19 июля 2011

Я знаю, что это старый пост, но он все еще очень актуален. Я обнаружил, что современные браузеры поддерживают 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

В 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 с использованием этого имени файла для загрузки: + ^ ~ -_,;. 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, и я обновил код, чтобы отразить это. Спасибо за предложение.

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

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

161 голосов
/ 19 октября 2008

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

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

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

/real_script.php/fake_filename.doc

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

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

/mot%C3%B6rhead   # motörhead
89 голосов
/ 18 сентября 2008

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

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

64 голосов
/ 05 января 2014

RFC 6266 описывает « Использование поля заголовка расположения содержимого в протоколе передачи гипертекста (HTTP) ». Цитата из этого:

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

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

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

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

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

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

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

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

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

16 голосов
/ 18 сентября 2008

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

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

10 голосов
/ 10 июля 2015

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

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

http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download

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

10 голосов
/ 15 июля 2010

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

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

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

HttpUtility.UrlPathEncode(fileName)
9 голосов
/ 19 апреля 2013

Я использую следующие фрагменты кода для кодирования (при условии, что 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 + "\"");
8 голосов
/ 25 июня 2015

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

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

5 голосов
/ 31 мая 2012

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

$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.'"');
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...