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

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

Content-Disposition: attachment; filename= FILENAME

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

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

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

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

  • Fire Fox
  • Internet Explorer
  • Сафари
  • Гугл Хром
  • опера

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

RFC 2183 указывает, что такие заголовки должны быть закодированы в соответствии с RFC 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 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 Жуковский: Я не знаю, почему, но, как обсуждалось в Connect , похоже, что это не работает ужасно хорошо.

  • Не существует совместимого способа кодирования имен, отличных от ASCII, в Content-Disposition . Совместимость с браузером – беспорядок .

  • Теоретически правильный синтаксис использования UTF-8 в Content-Disposition очень странный: filename*=UTF-8''foo%c3%a4 (да, это звездочка и без кавычек, кроме пустой одиночной кавычки в середине)

  • Этот заголовок является не совсем стандартным ( спецификация HTTP / 1.1 подтверждает его существование , но не требует от клиентов поддержки).

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

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

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

 /real_script.php/fake_filename.doc 

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

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

 /mot%C3%B6rhead # motörhead 

В RFC 6266 описано « Использование поля заголовка контента-содержимого в протоколе передачи гипертекста (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 имеется также длинный список предложений по повышению интероперабельности. Он также указывает на сайт, который сравнивает реализации . Текущие тесты для всех проходов, подходящие для общих имен файлов, include:

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

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

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

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

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

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

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

 HttpUtility.UrlPathEncode(fileName) 

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

Ява:

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

В 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 Не исправлено
IE 9 Исправлено

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

 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). // ie, %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 = «…», если используются только изо-латинские и «безопасные» символы; если нет, он добавляет имя файла * = 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 

Кредит отправляется в Pure ASP File Upload , реализуя BytesToString() из include_aspuploader.asp в моем собственном коде. Мне удалось получить имена файлов UTF-8 .


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

  • Multipart / form-data и UTF-8 в приложении ASP Classic

  • Unicode, UTF, ASCII, различия формата ANSI

У нас была аналогичная проблема в веб-приложении, и в итоге я прочитал имя файла из HTML и установил, что в кодировке url в новом HTML , Конечно, нам пришлось удалить путь, подобный «C: \ fakepath», который возвращается некоторыми браузерами.

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

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

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

Мое решение описано в другом streamе: Java servlet download filename специальные символы

Мое решение основано на том, как браузеры пытаются считывать значение из параметра filename . Если в параметре filename (например, filename*=utf-8''test.xml ) нет кодировки, браузеры ожидают, что это значение кодируется в собственной кодировке браузера.

Различные браузеры ожидают различной собственной кодировки. Обычно исходная кодировка браузера – utf-8 (FireFox, Opera, Chrome). Но исходная кодировка IE – Win-1250. (Я ничего не знаю о других браузерах.)

Следовательно, если мы поместим значение в filename parametr, которое закодировано utf-8 / win-1250 в соответствии с браузером пользователя, оно должно работать. По крайней мере, это работает для меня.

Короче говоря, если у нас есть файл с именем omáčka.xml ,
для FireFox, Opera и Chrome я отвечу на этот заголовок (закодированный в utf-8):

 Content-Disposition: attachment; filename="omáčka.xml" 

и для IE I ответят этот заголовок (закодированный в win-1250):

 Content-Disposition: attachment; filename="omáèka.jpg" 

Пример Java приведен в моем сообщении, о котором упоминалось выше.

Давайте будем гением компьютера.