Como codificar o parâmetro nome do arquivo do cabeçalho Content-Disposition em HTTP?
-
01-07-2019 - |
Pergunta
aplicativos da Web que deseja forçar um recurso para ser baixado em vez de diretamente prestados em um problema com o navegador Web um cabeçalho Content-Disposition
na resposta HTTP com o formato:
Content-Disposition: attachment; filename=FILENAME
O parâmetro filename
pode ser usada para sugerir um nome para o arquivo no qual o recurso é baixado pelo navegador. RFC 2183 (Content-Disposition), no entanto, os estados em seção 2.3 (o nome do arquivo de parâmetros) que o nome do arquivo só pode usar caracteres US-ASCII:
Current [RFC 2045] gramática restringe os valores dos parâmetros (e, portanto, nomes de arquivos de conteúdo-Disposição) para US-ASCII. Reconhecemos o grande desejabilidade de permitir arbitrária conjuntos de caracteres em nomes de arquivos, mas é além do escopo deste documento para definir os mecanismos necessários.
Existem evidências empíricas, no entanto, que a maioria dos navegadores da Web populares hoje parecem permitir caracteres não-US-ASCII ainda (para a falta de um padrão) discordam sobre a especificação esquema de codificação e conjunto de caracteres do nome do arquivo. Questão é, então, quais são os vários esquemas e codificações empregados pelos browsers mais populares, se o nome do arquivo “naïvefile” (sem aspas e onde a terceira carta é U + 00EF) necessários para ser codificado no cabeçalho Content-Disposition?
Para efeitos desta questão, navegadores populares sendo:
- Firefox
- Internet Explorer
- Safari
- Google Chrome
- Opera
Solução
Não há discussão sobre isso, incluindo links para testes de navegador e compatibilidade com versões anteriores, na proposta RFC 5987 , "conjunto de caracteres e codificação de idiomas para o Hypertext Transfer Protocol (HTTP) cabeçalho campo parâmetros."
RFC 2183 indica que tais cabeçalhos deve ser codificado de acordo com a RFC 2184 , que foi tornado obsoleto por RFC 2231 , abrangidos pelo projecto RFC acima.
Outras dicas
Eu sei que este é um post antigo, mas ainda é muito relevante. Eu descobri que os navegadores modernos suportam rfc5987, que permite a codificação UTF-8, percentual codificado (url-codificado). Então file.txt Naïve torna-se:
Content-Disposition: attachment; filename*=UTF-8''Na%C3%AFve%20file.txt
Safari (5) não suporta isso. Em vez disso você deve usar o padrão Safari de escrever o nome do arquivo diretamente no seu utf-8 cabeçalho codificado:
Content-Disposition: attachment; filename=Naïve file.txt
IE8 e mais velhos não suportam ele quer e você precisa usar o padrão IE de codificação UTF-8, percentual codificado:
Content-Disposition: attachment; filename=Na%C3%AFve%20file.txt
Em ASP.Net Eu uso o seguinte código:
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);
I testado o anterior utilizando IE7, IE8, IE9, Chrome 13, Opera 11, FF5, Safari 5.
Atualizar Novembro de 2013:
Aqui está o código que eu uso atualmente. Eu ainda tenho que apoiar IE8, então eu não pode se livrar da primeira parte. Acontece que navegadores no Android usar o construída em gerenciador de download Android e pode nomes de arquivo não confiável de análise na forma padrão.
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);
O acima agora testado em IE7-11, Chrome 32, Opera 12, FF25, Safari 6, usando este nome de arquivo para download:! ?? abcABCæøåÆØÅäöüïëêîâéíáóúýñ½§ # ¤% & () = '@ £ $ € {[]} + '^ ~' -_,;. txt
No IE7 ele funciona para alguns personagens, mas não todos. Mas quem se preocupa com o IE7 hoje em dia?
Esta é a função uso I para gerar nomes de arquivos seguros para Android. Note que eu não sei quais personagens são suportados em Android, mas que eu testei que estes trabalhos certa:
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: I testado no IE7 e IE8 e descobriu-se que eu não tinha necessidade de escapar apóstrofo ( '). Você tem um exemplo onde ele falhar?
@ Dave Van den Eynde: Combinando os dois nomes de arquivos em uma linha como de acordo com RFC6266 funciona, exceto para Android e IE7 + 8 e eu tenho atualizado o código para refletir isso. Obrigado pela sugestão.
@Thilo: Nenhuma idéia sobre GoodReader ou qualquer outro não-browser. Você pode ter um pouco de sorte, usando a abordagem Android.
@Alex Zhukovskiy: Não sei porquê, mas como discutido em Conectar não parece ao trabalho terrivelmente bem.
-
Não há nenhuma maneira interoperável de nomes não-ASCII codificar em
Content-Disposition
. Compatibilidade do navegador é uma bagunça . -
O teoricamente sintaxe correta para o uso de UTF-8 em
Content-Disposition
é muito estranho:filename*=UTF-8''foo%c3%a4
(sim, isso é um asterisco, e sem aspas, exceto uma cotação única vazia no meio) -
Este cabeçalho é meio-não-muito-padrão ( HTTP / 1.1 especificação reconhece sua existência , mas não requer clientes para apoiá-lo).
Não é um simples e alternativa muito robusto: use uma URL que contém o nome do arquivo que você deseja
.Quando o nome após a última barra é o que você quer, você não precisa de nenhum cabeçalhos extra!
Esse truque funciona:
/real_script.php/fake_filename.doc
E se o seu servidor suporta reescrita de URL (por exemplo mod_rewrite
no Apache), então você pode esconder totalmente a parte script.
Personagens de URLs deve estar em UTF-8, urlencoded byte a byte:
/mot%C3%B6rhead # motörhead
RFC 6266 descreve o “ O uso do Content-Disposition cabeçalho campo no Hypertext Transfer Protocol (HTTP ) ”. Citando que:
6. Internacionalização considerações
O parâmetro “
filename*
” ( Seção 4.3 ), usando a codificação definida em [ RFC5987 ], permite que o servidor personagens de transmissão fora da ISO-8859-1 conjunto de caracteres, e também para especificar opcionalmente a linguagem em uso.
E na sua seção exemplos :
Este exemplo é o mesmo que o descrito acima, mas adicionando o "filename" parâmetro para a compatibilidade com os agentes do utilizador não implementar RFC 5987 :
Content-Disposition: attachment; filename="EURO rates"; filename*=utf-8''%e2%82%ac%20rates
Nota: Os agentes que não suportam a RFC 5987 codificação ignorar “
filename*
” quando ele ocorre depois de “filename
”.
Na Apêndice D há também uma longa lista de sugestões para aumentar a interoperabilidade. Ele também aponta para um site que compara implementações . testes de passa-tudo atual adequados para nomes de arquivos comuns incluem:
- attwithisofnplain : plain ISO-8859-1 nome de arquivo com aspas e sem codificação. Isso requer um nome de arquivo que é tudo o ISO-8859-1 e não contém sinais de percentagem, pelo menos não na frente de dígitos hexadecimais.
- attfnboth : dois parâmetros na ordem descrita acima. Deve funcionar para a maioria dos nomes de arquivos na maioria dos navegadores, embora IE8 irá usar o parâmetro “
filename
”.
RFC 5987 em referências turno RFC 2231 , que descreve o formato real. 2231 é principalmente para e-mail, e 5987 nos diz que partes podem ser usados ??para cabeçalhos HTTP também. Não confunda isso com cabeçalhos MIME usados ??dentro de um multipart/form-data
HTTP corpo , que é regido por RFC 2388 ( secção 4.4 em particular) e o HTML 5 projecto .
O documento a seguir ligada a partir o projecto RFC mencionado por Jim em sua resposta mais endereços a questão e definitivamente vale a pena uma nota direto aqui:
casos de teste para HTTP cabeçalho Content-Disposition e RFC 2231/2047 Encoding
em asp.net MVC2 i usar algo como isto:
return File(
tempFile
, "application/octet-stream"
, HttpUtility.UrlPathEncode(fileName)
);
Eu acho que se você não usar MVC (2) você pode apenas codificar o nome do arquivo usando
HttpUtility.UrlPathEncode(fileName)
Coloque o nome do arquivo entre aspas duplas. Resolveu o problema para mim. Como esta:
Content-Disposition: attachment; filename="My Report.doc"
http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download
Eu testei várias opções. Navegadores não suportam as especificações e agir de forma diferente, acredito aspas é a melhor opção.
Eu uso os seguintes trechos de código para codificar (assumindo fileName contém o nome do arquivo e extensão do arquivo, ou seja .: 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 + "\"");
Em ASP.NET Web API, eu URL codificar o nome do arquivo:
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;
}
}
Eu testei o seguinte código em todos os principais navegadores, incluindo Explorers mais velhos (através do modo de compatibilidade), e funciona bem em todos os lugares:
$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.'"');
Se você estiver usando uma nodejs backend você pode usar o seguinte código eu encontrei aqui
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);
}
Eu acabei com o seguinte código no meu script "download.php" (com base em este blogPost e estes casos de teste ).
$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) ));
Isto usa a forma padrão de nome de arquivo = "..." Enquanto há apenas iso-latin1 e caracteres 'seguros' utilizados; se não, ele adiciona o nome do arquivo * = UTF-8 '' maneira url-codificado. De acordo com a este caso de teste específico , ele deve funcionar a partir MSIE9, e em FF recente, Chrome , Safari; na menor versão MSIE, deve oferecer filename contendo a versão ISO8859-1 do nome do arquivo, com sublinhados em personagens não neste codificação.
Nota final: o máx. tamanho para cada campo de cabeçalho é 8190 bytes em Apache. UTF-8 pode ser de até quatro bytes por caractere; após rawurlencode, é x3 = 12 bytes por uma personagem. Muito ineficiente, mas deve ainda ser teoricamente possível ter mais de 600 "sorrisos" % F0% 9F% 98% 81 no nome do arquivo.
No PHP esta fez isso por mim (assumindo que o nome do arquivo é UTF8 codificado):
header('Content-Disposition: attachment;'
. 'filename="' . addslashes(utf8_decode($filename)) . '";'
. 'filename*=utf-8\'\'' . rawurlencode($filename));
Testado contra IE8-11, Firefox e Chrome.
Se o navegador pode interpretar filename * = utf-8 ele irá usar a versão UTF8 do nome do arquivo, então ele irá usar o nome do arquivo decodificado. Se o seu nome de arquivo contém caracteres que não podem ser representados em ISO-8859-1 que você pode querer considerar o uso iconv
vez.
Clássico ASP Solution
A maioria dos navegadores modernos suportam passar o Filename
como UTF-8
agora, mas como foi o caso com um arquivo de upload uso solução I que foi baseado em FreeASPUpload.Net (site não existir mais, link aponta para archive.org ) isso não iria funcionar como a análise do binário contou com a leitura cordas único byte ASCII codificados, que funcionou bem quando você passou UTF-8 codificado dados até chegar a caracteres ASCII não suporta.
No entanto, eu era capaz de encontrar uma solução para obter o código para ler e analisar o binário como 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
O crédito vai para arquivo ASP Pure Carregar por implementar a função BytesToString()
de include_aspuploader.asp
no meu próprio código I foi capaz de obter nomes de arquivos UTF-8
de trabalho.
Links Úteis
Apenas uma atualização desde que eu estava tentando tudo isso hoje em resposta a uma questão do cliente
- Com exceção do Safari configurado para japonês, todos os navegadores nosso cliente testados funcionaram melhor com filename = text.pdf - em que o texto é um valor para o cliente serializado por ASP.Net/IIS em utf-8, sem codificação de URL. Por alguma razão, Safari configurado para Inglês aceitaria e devidamente salvar um arquivo com utf-8 nome japonês, mas que mesmo browser configurado para o japonês iria salvar o arquivo com as utf-8 caracteres não-interpretada. Todos os outros navegadores testados parecia funcionar melhor / fino (independentemente da configuração de idioma) com o nome do arquivo utf-8 codificado sem codificação url.
- Eu não poderia encontrar um único navegador implementação Rfc5987 / 8187 em tudo . Eu testei com a mais recente Chrome, Firefox constrói mais IE 11 e Edge. Tentei configurar o cabeçalho com apenas filename * = utf-8''texturlencoded.pdf, definindo-o com tanto filename = text.pdf; nome do arquivo * = UTF-8''texturlencoded.pdf. Não uma característica de Rfc5987 / 8187 parecia estar sendo processado corretamente em qualquer um dos acima.
Nós tivemos um problema semelhante em uma aplicação web, e acabou lendo o nome do arquivo do <input type="file">
HTML, e definindo que, na forma de código URL em uma nova <input type="hidden">
HTML. É claro que tivemos para remover o caminho como "C: \ fakepath \" que é retornado por alguns navegadores.
É claro que isto não responde directamente questão PO, mas pode ser uma solução para os outros.
Eu normalmente URL-codificar (com% xx) os nomes dos arquivos, e parece funcionar em todos os navegadores. Você pode querer fazer alguns testes de qualquer maneira.