it-swarm-eu.dev

Jak zakódovat parametr název záhlaví Content-Disposition v HTTP?

Webové aplikace, které chtějí vynutit, aby byl zdroj stažen , nikoli přímo vykreslen ve webovém prohlížeči vydejte hlavičku Content-Disposition v odpovědi HTTP formuláře:

Content-Disposition: attachment; filename=FILENAME

Parametr filename lze použít k navržení názvu souboru, do kterého je zdroj stahován prohlížečem. RFC 218 (Dispozice obsahu) však v oddíl 2. (Parametr názvu souboru) uvádí, že název souboru může používat pouze znaky US-ASCII:

Aktuální gramatika [RFC 2045] omezuje hodnoty parametrů (a tedy názvy souborů Content-Disposition) na US-ASCII. Uvědomujeme si, že je žádoucí povolit libovolné sady znaků v názvech souborů, ale vymezení potřebných mechanismů je mimo rozsah tohoto dokumentu.

Existuje však empirický důkaz, že většina populárních webových prohlížečů dnes zřejmě povoluje znaky mimo ASCII mimo USA (pro nedostatek standardu) nesouhlasí s kódovacím schématem a specifikací znakové sady názvu souboru. Otázka tedy zní, jaká jsou různá schémata a kódování používaná populárními prohlížeči, pokud je třeba do záhlaví Content-Disposition zakódovat název souboru „naïvefile“ (bez uvozovek a kde třetí písmeno je U + 00EF)?

Pro účely této otázky populární prohlížeče jsou:

  • Firefox
  • Internet Explorer
  • Safari
  • Google Chrome
  • Opera
490
Atif Aziz

Diskutuje se o tom, včetně odkazů na testování prohlížeče a zpětné kompatibility, v navrhovaném RFC 5987 , "Znaková sada a kódování jazyků pro parametry pole záhlaví protokolu HTTP (Hypertext Transfer Protocol)."

RFC 218 označuje, že taková záhlaví by měla být kódována podle RFC 2184 , který byl zastarán RFC 2231 , na který se vztahuje výše uvedený koncept RFC.

89
Jim

Vím, že se jedná o starý příspěvek, ale stále je to velmi důležité. Zjistil jsem, že moderní prohlížeče podporují rfc5987, což umožňuje kódování utf-8, procentuální kódování (kódování url). Pak naivní soubor.txt se stane:

Content-Disposition: attachment; filename*=UTF-8''Na%C3%AFve%20file.txt

Safari (5) to nepodporuje. Místo toho byste měli použít standard Safari pro psaní názvu souboru přímo do záhlaví kódovaného utf-8:

Content-Disposition: attachment; filename=Naïve file.txt

IE8 a starší to také nepodporují a musíte použít standard IE kódování utf-8, procentuální kódování:

Content-Disposition: attachment; filename=Na%C3%AFve%20file.txt

V ASP.Net používám následující kód:

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);

Výše jsem testoval pomocí IE7, IE8, IE9, Chrome 13, Opera 11, FF5, Safari 5.

Aktualizace listopad 2013:

Zde je kód, který v současné době používám. Stále musím podporovat IE8, takže se nemůžu zbavit první části. Ukázalo se, že prohlížeče na Android používají vestavěný Android správce stahování a nemohou standardně spolehlivě analyzovat názvy souborů.

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);

Výše uvedené nyní testované v IE7-11, Chrome 32, Opera 12, FF25, Safari 6, pomocí tohoto souboru ke stažení: 你好 abcABCæøåÆØÅäöüïëêêââííáúúñ½§! € {[]} + ´¨ ^ ~ '-_,;. Txt

Na IE7 funguje pro některé znaky, ale ne pro všechny. Ale koho dnes zajímá IE7?

Tuto funkci používám ke generování bezpečných názvů souborů pro Android. Všimněte si, že nevím, které znaky jsou na Android podporovány, ale že jsem testoval, zda tyto funkce fungují:

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: Testoval jsem v IE7 a IE8 a ukázalo se, že jsem nemusel uniknout apostrofu ('). Máte příklad, kde selže?

@ Dave Van den Eynde: Kombinace dvou názvů souborů na jednom řádku podle RFC6266 funguje s výjimkou Android a IE7 + 8 a já jsem kód aktualizoval, aby to odrážel. Děkuji za návrh.

@Thilo: Žádná představa o GoodReaderu nebo jiném prohlížeči. Možná budete mít štěstí pomocí přístupu Android.

@Alex Zhukovskiy: Nevím proč, ale jak se diskutuje o Connect , nezdá se, že by fungoval strašně dobře.

340

Existuje jednoduchá a velmi robustní alternativa: použijte adresu URL, která obsahuje požadovaný název souboru .

Když je jméno za posledním lomítkem to, co chcete, nepotřebujete žádné další záhlaví!

Tento trik funguje:

/real_script.php/fake_filename.doc

A pokud váš server podporuje přepisování URL (např. mod_rewrite v Apache), můžete skriptovou část zcela skrýt.

Znaky v URL by měly být v UTF-8, urlencote byte-by-byte:

/mot%C3%B6rhead   # motörhead
161
Kornel

RFC 6266 popisuje „ použití pole záhlaví Content-Disposition v protokolu přenosu hypertextů (HTTP) “ “. Citace z toho:

6. Internacionalizace

Parametr „filename*“ ( oddíl 4. ) pomocí kódování definovaného v [ RFC5987 ] umožňuje serveru vysílat znaky mimo znakovou sadu ISO-8859-1 a také volitelně specifikovat používaný jazyk.

A v jejich příkladová část :

Tento příklad je stejný jako ten výše, ale přidání parametru "filename" pro kompatibilitu s uživatelskými agenty, které nevykonávají RFC 5987 :

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

Poznámka: Ti agenti uživatelů, kteří nepodporují kódování RFC 5987 , ignorují „filename*“, když k nim dojde za „filename“.

V Dodatek D je také dlouhý seznam návrhů na zvýšení interoperability. Také ukazuje na web, který porovnává implementace . Aktuální all-pass testy vhodné pro běžné názvy souborů zahrnují:

  • attwithisofnplain : holý název souboru ISO-8859-1 s dvojitými uvozovkami a bez kódování. To vyžaduje název souboru, který je celý ISO-8859-1 a neobsahuje znaky procent, alespoň ne před hexadecimální číslice.
  • attfnboth : dva parametry ve výše uvedeném pořadí. Měl by fungovat pro většinu názvů souborů ve většině prohlížečů, i když IE8 použije parametr „filename“.

To RFC 5987 zase odkazy RFC 2231 , která popisuje skutečný formát. 2231 je primárně pro poštu a 5987 nám říká, jaké části mohou být použity také pro HTTP hlavičky. Nepleťte si to se záhlavími MIME používanými uvnitř těla multipart/form-data HTTP , které se řídí RFC 2388 ( zejména oddíl 4.4 ) a koncept HTML 5 .

64
MvG

Následující dokument propojený z návrh RFC uvedený Jim ve své odpovědi dále řeší otázku a rozhodně stojí za přímou poznámku zde:

Testovací případy pro HTTP Content-Disposition header a RFC 2231/2047 Encoding

16
Atif Aziz

v asp.net mvc2 používám něco takového:

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

Myslím, že pokud nepoužíváte mvc (2), stačí jen zakódovat název souboru pomocí

HttpUtility.UrlPathEncode(fileName)
11
Elmer

Vložte název souboru do uvozovek. Vyřešil mi problém. Takhle:

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

http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download

Vyzkoušel jsem několik možností. Prohlížeče nepodporují specifikace a jednají jinak, věřím, že nejlepší volbou jsou dvojité uvozovky.

10

Pro kódování používám následující fragmenty kódu (za předpokladu, že fileName obsahuje název souboru a příponu souboru, tj .: 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 ) );
}

Jáva:

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

V rozhraní ASP.NET Web API kóduji URL název souboru:

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

8
martinoss

Pokud používáte backend nodejs, můžete použít následující kód, který jsem našel zde

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);
}
5
Emanuele Spatola

Následující kód jsem testoval ve všech hlavních prohlížečích, včetně starších průzkumníků (v režimu kompatibility) a funguje dobře všude:

$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.'"');
5
Stano

Ve svém skriptu „download.php“ jsem skončil s následujícím kódem (na základě tento blogpost a tyto testovací případy ).

$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) ));

Toto používá standardní způsob filename = "...", pokud jsou použity pouze znaky iso-latin1 a "bezpečné"; pokud ne, přidá název souboru * = UTF-8 '' url. Podle tento konkrétní testovací případ , mělo by to fungovat od MSIE9 nahoru a na posledních FF, Chrome, Safari; u nižší verze MSIE by měl nabídnout název souboru obsahující verzi názvu souboru ISO8859-1 s podtržítky na znaky, které nejsou v tomto kódování.

Závěrečná poznámka: max. velikost každého pole záhlaví je na Apache 8190 bajtů. UTF-8 může mít až čtyři bajty na znak; po rawurlencode je x3 = 12 bajtů na jeden znak. Docela neefektivní, ale mělo by být teoreticky možné mít v názvu souboru více než 600 "úsměvy"% F0% 9F% 98% 81.

4
renergy

V PHP to pro mě udělal (za předpokladu, že název souboru je kódován UTF8):

header('Content-Disposition: attachment;'
    . 'filename="' . addslashes(utf8_decode($filename)) . '";'
    . 'filename*=utf-8\'\'' . rawurlencode($filename));

Testováno na IE8-11, Firefox a Chrome.
Pokud prohlížeč dokáže interpretovat název souboru * = utf-8, použije verzi souboru UTF8, jinak použije dekódovaný název souboru. Pokud vaše jméno souboru obsahuje znaky, které nelze v ISO-8859-1 reprezentovat, můžete místo toho zvážit použití iconv.

3
Gustav

Jen aktualizace, protože jsem dnes zkoušel všechny tyto věci v reakci na problém se zákazníkem

  • S výjimkou Safari nakonfigurovaného pro japonštinu fungovaly všechny prohlížeče, které náš zákazník testoval, nejlépe s filename = text.pdf - kde text je hodnota pro zákazníka serializovaná ASP.Net/IIS v utf-8 bez kódování url. Z nějakého důvodu by Safari nakonfigurovaný pro angličtinu přijal a správně uložil soubor s japonským názvem utf-8, ale stejný prohlížeč nakonfigurovaný pro japonštinu by uložil soubor s neinterpretovanými znaky utf-8. Zdá se, že všechny ostatní testované prohlížeče fungují nejlépe/dobře (bez ohledu na jazykovou konfiguraci) s názvem utf-8 bez kódování url.
  • Nemohl jsem najít jediný prohlížeč implementující Rfc5987/8187 vůbec. Testoval jsem s nejnovějším prohlížečem Chrome, Firefoxem plus plus [IE 11 a Edge. Snažil jsem se nastavit záhlaví s pouhým názvem souboru * = utf-8''texturlencoded.pdf, nastavit s oběma filename = text.pdf; filename * = utf-8''texturlencoded.pdf. Zdá se, že ani jedna funkce Rfc5987/8187 nebyla správně zpracována v žádné z výše uvedených možností.
1
user1664043

Klasické řešení ASP

Většina moderních prohlížečů nyní podporuje předávání Filename jako UTF-8, ale jako v případě řešení pro nahrávání souborů, které používám, které bylo založeno na FreeASPUpload.Net (web již neexistuje) , odkaz ukazuje na archive.org ) , že by to nefungovalo tak, že by se binární analýza spoléhala na čtení jednobajtových řetězců kódovaných jedním bajtem ASCII, což fungovalo dobře, když jste předávali data kódovaná UTF-8, dokud se nedostanete ke znakům ASCII nepodporuje.

Byl jsem však schopen najít řešení, jak získat kód pro čtení a analýzu binárního kódu jako 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

Kredit jde do Pure ASP File Upload implementací funkce BytesToString() z include_aspuploader.asp v mém vlastním kódu jsem byl schopen uvést UTF-8 do názvů souborů.


Užitečné odkazy

1
Lankymart