Как закодировать параметр имени файла заголовка Content-Disposition в HTTP?
Веб-приложения, которые хотят принудительно загружать ресурс, а не напрямую отображаться в веб-браузере, вызывают заголовок 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?
Для целей этого вопроса популярными браузерами являются:
- Firefox
- Internet Explorer
- Safari
- Google Chrome
- Opera
Ответы
Ответ 1
Существует обсуждение этого вопроса, включая ссылки на тестирование браузера и обратную совместимость, в предлагаемом RFC 5987," Набор символов и кодирование языка для Параметры поля заголовка гипертекстового протокола (HTTP).
RFC 2183 указывает, что такие заголовки должны быть закодированы в соответствии с RFC 2184, который был устарел RFC 2231, рассмотренный выше в проекте RFC.
Ответ 2
Я знаю, что это старый пост, но он по-прежнему очень важен. Я обнаружил, что современные браузеры поддерживают 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, и оказалось, что мне не нужно было избегать апострофа ('). У вас есть пример, где он терпит неудачу?
@Dave Van den Eynde: объединение двух имен файлов в одной строке, как в соответствии с RFC6266, за исключением Android и IE7 + 8, и я обновил код, чтобы отразить это. Спасибо за предложение.
@Thilo: Не знаю о GoodReader или любом другом браузере. Возможно, вам удастся использовать подход Android.
@Alex Жуковский: Я не знаю почему, но, как обсуждалось в Connect, он не похоже, работают ужасно хорошо.
Ответ 3
Существует простая и очень надежная альтернатива: использовать URL-адрес, содержащий нужное имя файла.
Когда имя после последней косой черты - это тот, который вам нужен, вам не нужны дополнительные заголовки!
Этот трюк работает:
/real_script.php/fake_filename.doc
И если ваш сервер поддерживает переписывание URL (например, mod_rewrite
в Apache), вы можете полностью скрыть часть script.
Символы в URL-адресах должны быть в UTF-8, по умолчанию указывается побайтно:
/mot%C3%B6rhead # motörhead
Ответ 4
RFC 6266 описывает "Использование поля заголовка Content-Disposition в протоколе передачи гипертекста (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, используемыми внутри тела HTTP multipart/form-data
, который регулируется RFC 2388 (раздел 4.4 в частности) и проект HTML 5.
Ответ 5
Следующий документ, связанный с проект RFC, указанный Jim в своем ответе далее рассматривается вопрос и, безусловно, стоит прямо здесь:
Тестовые примеры для заголовка Content-Disposition HTTP и кодировки RFC 2231/2047
Ответ 6
в asp.net mvc2 я использую что-то вроде этого:
return File(
tempFile
, "application/octet-stream"
, HttpUtility.UrlPathEncode(fileName)
);
Я предполагаю, что если вы не используете mvc (2), вы можете просто закодировать имя файла, используя
HttpUtility.UrlPathEncode(fileName)
Ответ 7
Поместите имя файла в двойные кавычки. Решил проблему для меня. Как это:
Content-Disposition: attachment; filename="My Report.doc"
http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download
Я протестировал несколько вариантов. Браузеры не поддерживают спецификации и действуют по-другому, я считаю, что двойные кавычки - лучший вариант.
Ответ 8
Я использую следующие фрагменты кода для кодирования (при условии, что имя_файла содержит имя файла и расширение файла, т.е.: 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 + "\"");
Ответ 9
В 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]()
Ответ 10
Я тестировал следующий код во всех основных браузерах, включая более старых Исследователей (через режим совместимости), и он хорошо работает везде:
$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.'"');
Ответ 11
Если вы используете бэкэнд 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);
}
Ответ 12
В моем "download.php" script (на основе this 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 в имени файла.
Ответ 13
В 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
.
Ответ 14
Классическое решение 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
имена файлов.
Полезные ссылки
Ответ 15
Просто обновление, так как я пробовал все это сегодня в ответ на проблему клиента
- За исключением Safari, настроенного для японского языка, все протестированные нашим клиентом браузеры лучше всего работали с filename = text.pdf - где text - это значение клиента, сериализованное 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, по-видимому, не обрабатывается правильно ни в одном из вышеперечисленных.
Ответ 16
PHP-фреймворк Symfony 4 имеет $filenameFallback
в HeaderUtils::makeDisposition
. Вы можете посмотреть на эту функцию для деталей - это похоже на ответы выше.
Пример использования:
$filenameFallback = preg_replace('#^.*\.#', md5($filename) . '.', $filename);
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename, $filenameFallback);
$response->headers->set('Content-Disposition', $disposition);
Ответ 17
У нас была аналогичная проблема в веб-приложении, и в итоге я прочитал имя файла из HTML <input type="file">
и установил это в кодировке url в новом HTML <input type="hidden">
. Конечно, нам пришлось удалить путь, подобный "C:\fakepath", который возвращается некоторыми браузерами.
Конечно, это напрямую не отвечает на вопрос OP, но может быть решением для других.
Ответ 18
Я обычно кодирую URL-адрес (с% xx) именами файлов и, похоже, работает во всех браузерах. Возможно, вы захотите сделать некоторые тесты.
Ответ 19
Я нашел решение, которое работает для всех моих браузеров (т.е. всех браузеров, которые я установил - IE8, FF16, Opera 12, Chrome 22).
Мое решение описано в другой теме:
Java сервлет скачать имя файла специальных символов
Мое решение основано на том, как браузеры пытаются считывать значение из параметра 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 я отвечает этот заголовок (закодирован в win-1250):
Content-Disposition: attachment; filename="omáèka.jpg"
Пример Java в моем сообщении, о котором упоминалось выше.