วิธีการเข้ารหัสพารามิเตอร์ชื่อไฟล์ของส่วนหัวของเนื้อหาการจัดการใน HTTP?


535

เว็บแอปพลิเคชันที่ต้องการบังคับให้ดาวน์โหลดทรัพยากรแทนที่จะแสดงผลโดยตรงในเว็บเบราว์เซอร์จะมีContent-Dispositionส่วนหัวในการตอบกลับ HTTP ของแบบฟอร์ม:

Content-Disposition: attachment; filename=FILENAME

filenameพารามิเตอร์สามารถใช้ในการแนะนำชื่อไฟล์ลงในที่ทรัพยากรที่มีการดาวน์โหลดจากเบราว์เซอร์ อย่างไรก็ตาม RFC 2183 (การจัดการเนื้อหา) ระบุในส่วน 2.3 (พารามิเตอร์ชื่อไฟล์) ที่ชื่อไฟล์สามารถใช้อักขระ US-ASCII เท่านั้น:

ไวยากรณ์ [RFC 2045] ปัจจุบัน จำกัด ค่าพารามิเตอร์ (และชื่อไฟล์ Content-Disposition) เป็น US-ASCII เราตระหนักดีถึงความปรารถนาอันยิ่งใหญ่ในการอนุญาตให้มีชุดอักขระโดยพลการในชื่อไฟล์ แต่มันอยู่นอกเหนือขอบเขตของเอกสารนี้เพื่อกำหนดกลไกที่จำเป็น

มีหลักฐานเชิงประจักษ์อย่างไรก็ตามเว็บเบราว์เซอร์ยอดนิยมในปัจจุบันดูเหมือนว่าจะอนุญาตอักขระที่ไม่ใช่ US-ASCII (สำหรับการขาดมาตรฐาน) ที่ไม่เห็นด้วยกับรูปแบบการเข้ารหัสและข้อกำหนดชุดอักขระของชื่อไฟล์ คำถามคือรูปแบบและการเข้ารหัสต่างๆที่ใช้โดยเบราว์เซอร์ยอดนิยมคืออะไรถ้าชื่อไฟล์“ naïvefile” (โดยไม่ต้องใส่เครื่องหมายคำพูดและที่ตัวอักษรตัวที่สามคือ U + 00EF) จำเป็นต้องเข้ารหัสในส่วนหัวของเนื้อหา

สำหรับจุดประสงค์ของคำถามนี้เบราว์เซอร์ยอดนิยมได้แก่ :

  • Firefox
  • Internet Explorer
  • การแข่งรถวิบาก
  • Google Chrome
  • อุปรากร

ทำให้มันใช้งานได้กับ Mobile Safari (raw utf-8 ตามที่แนะนำโดย @Martin Ørding-Thomsen) แต่มันใช้ไม่ได้กับ GoodReader จากอุปกรณ์เดียวกัน ความคิดใด ๆ
Thilo


1
คำตอบของ Kornelพิสูจน์แล้วว่าเป็นเส้นทางที่มีความต้านทานน้อยที่สุดหากคุณสามารถกำหนดส่วนสุดท้ายของเส้นทางได้ Content-Disposition: attachmentคู่นี้ด้วย
Antti Haapala

คำตอบ:


94

มีการอภิปรายเกี่ยวกับเรื่องนี้รวมถึงลิงค์ไปสู่การทดสอบเบราว์เซอร์และความเข้ากันได้ย้อนหลังในRFC 5987 ที่เสนอ"ชุดอักขระและการเข้ารหัสภาษาสำหรับพารามิเตอร์ฟิลด์ส่วนหัวของ Hypertext Transfer Protocol (HTTP)"

RFC 2183ระบุว่าส่วนหัวดังกล่าวควรเข้ารหัสตามRFC 2184ซึ่งล้าสมัยโดยRFC 2231ซึ่งครอบคลุมโดยร่าง RFC ด้านบน


5
โปรดทราบว่าร่างอินเทอร์เน็ต (ไม่ใช่ "ฉบับร่าง RFC") ได้เสร็จสิ้นแล้วและเอกสารสุดท้ายคือ RFC 5987 ( greenbytes.de/tech/webdav/rfc5987.html )
Julian Reschke

11
ที่เกี่ยวข้องกับการนี้ผมค้นพบว่า Firefox (รุ่น 4-9 รวม) แตกถ้ามีเครื่องหมายจุลภาค (,) Content-Disposition: filename="foo, bar.pdf"ในชื่อไฟล์เช่น ผลที่ได้คือ Firefox ดาวน์โหลดไฟล์ได้อย่างถูกต้อง แต่เก็บ.partนามสกุล (เช่นfoo,bar.pdf-1.part) .partแล้วแน่นอนไฟล์จะไม่เปิดอย่างถูกต้องเนื่องจากโปรแกรมประยุกต์ไม่เกี่ยวข้องกับ ตัวอักษร ASCII อื่นดูเหมือนว่าจะใช้ได้
catchdave

3
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับพฤติกรรม IE ดูblogs.msdn.com/b/ieinternals/archive/2010/06/07/…
EricLaw

5
@catchdave: คุณลืม "ไฟล์แนบ"; ส่วนหนึ่ง
Christoffer Hammarström

6
สรุปแล้วนี่เป็นเพียงคำตอบสำหรับลิงก์เท่านั้นที่มี 74 upvotes
Antti Haapala

364

ฉันรู้ว่านี่เป็นโพสต์เก่า แต่ก็ยังมีความเกี่ยวข้องมาก ฉันพบว่าเบราว์เซอร์ที่ทันสมัยรองรับ 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

อัปเดตพฤศจิกายน 2556:

นี่คือรหัสที่ฉันใช้ในปัจจุบัน ฉันยังต้องรองรับ 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 และปรากฎว่าฉันไม่จำเป็นต้องหลบหนี apostrophe (') คุณมีตัวอย่างที่ล้มเหลวหรือไม่?

@Dave Van den Eynde: การรวมชื่อไฟล์ทั้งสองเข้าด้วยกันในบรรทัดเดียวตาม RFC6266 ทำงานได้ยกเว้นสำหรับ Android และ IE7 + 8 และฉันได้อัปเดตรหัสเพื่อแสดงถึงสิ่งนี้ ขอบคุณสำหรับคำแนะนำ

@Thilo: ไม่มีความคิดเกี่ยวกับ GoodReader หรืออื่น ๆ ที่ไม่ใช่เบราว์เซอร์ คุณอาจมีโชคโดยใช้วิธีการของ Android

@Alex Zhukovskiy: ฉันไม่รู้ว่าทำไม แต่ตามที่กล่าวไว้ในการเชื่อมต่อดูเหมือนว่าจะทำงานได้ไม่ดีนัก


1
ใช้งานได้กับ Mobile Safari (raw utf-8 ตามที่แนะนำข้างต้น) แต่ไม่สามารถใช้งานได้กับ GoodReader จากอุปกรณ์เดียวกัน ความคิดใด ๆ
Thilo

1
IE7 และ 8 ต้องใช้ apostrophes เช่นกัน:. แทนที่ ("'", Uri.HexEscape (' \ ''))
TomZ

1
การเขียนอักขระ UTF-8 โดยตรงดูเหมือนจะใช้ได้กับ Firefox, Chrome และ Opera เวอร์ชันปัจจุบัน ไม่ได้ทดสอบ Safari & IE
Martin Tournoij

20
ทำไมไม่รวมพวกเขาเป็นContent-Disposition: attachment; filename*=UTF-8''Na%C3%AFve%20file.txt; filename=Na%C3%AFve%20file.txtและข้ามการดมกลิ่นเบราว์เซอร์? มันจะใช้ได้ไหม
Dave Van den Eynde

9
คนประเภทที่ fastmail พบวิธีแก้ปัญหาอื่น: blog.fastmail.com/2011/06/24/download-non-english-filenames เนื้อหา - การจัดการ: สิ่งที่แนบมา; ชื่อไฟล์ = "foo-% C3% a4.html"; filename * = UTF-8''foo-% c3% a4.html การระบุชื่อไฟล์สองครั้ง (หนึ่งครั้งโดยไม่มีคำนำหน้า UTF-8 และครั้งเดียวกับ) ทำให้มันทำงานใน IE8-11, Edge, Chrome, Firefox และ Safari ( ดูเหมือนว่าแอปเปิ้ลคงซาฟารีจึงทำงานมีเช่นกันตอนนี้)
wullinkm

169
  • ไม่มีวิธีการทำงานร่วมกันในการเข้ารหัสชื่อที่ไม่ใช่ 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 byte-by-byte:

/mot%C3%B6rhead   # motörhead

3
ลอง GetAttachment.aspx / fake_filename.doc id = 34 (แม้ว่ามันอาจจะ Apache เท่านั้นมุมแหลม)
คอร์เนล

2
นี่คือทางออกที่ยอดเยี่ยม; ช่วยฉันได้มากจริงๆ ขอบคุณ
kristopolous

6
ฉันลงเส้นทางกระต่ายและลองวิธีแก้ไขปัญหาอื่น ๆ ; การพยายามสูดดมเบราว์เซอร์ที่ถูกต้องและรุ่นเพื่อตั้งค่าส่วนหัวอย่างถูกต้องเป็นฝันร้ายมากเกินไป Chrome ระบุอย่างไม่ถูกต้องว่าเป็น Safari ซึ่งไม่เหมือนกันเลย (หยุดคอมม่าหากไม่เข้ารหัสอย่างถูกต้อง) ช่วยตัวคุณเองให้เดือดร้อนใช้โซลูชันนี้และตั้งชื่อแทน URL ตามต้องการ
mpen

3
/:id/:filenameวิธีง่ายจริงๆและผลงานขอขอบคุณ!
Luca Steeb

2
พันครั้ง "ใช่" คุณจะชนะเวลาอย่างจริงจังด้วยสิ่งนี้ มากยิ่งขึ้น - บางเบราว์เซอร์ Android จะแบนออกละเว้นContent-Dispositionและสร้างชื่อไฟล์ที่น่าสนใจมากแทน (พวกเขาจะถูกสร้างขึ้นจากเส้นทางของคุณ) ดังนั้นทางออกเดียวในการรักษาความมีสติคือการตั้งค่าContent-Disposition: attachmentและส่งชื่อไฟล์ที่ต้องการเป็นส่วนประกอบของเส้นทางสุดท้าย:
Julik

73

RFC 6266อธิบาย“ การใช้ฟิลด์ส่วนหัวการจัดการเนื้อหาใน Hypertext Transfer Protocol (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ยังมีรายการคำแนะนำที่ยาวเพื่อเพิ่มความสามารถในการทำงานร่วมกัน และยังเป็นจุดที่เว็บไซต์ที่เปรียบเทียบการใช้งาน การทดสอบ all-pass ปัจจุบันที่เหมาะสมสำหรับชื่อไฟล์ทั่วไป ได้แก่ :

  • attwithisofnplain : ชื่อไฟล์ ISO-8859-1 ธรรมดาพร้อมเครื่องหมายคำพูดคู่และไม่มีการเข้ารหัส ต้องใช้ชื่อไฟล์ซึ่งเป็น ISO-8859-1 ทั้งหมดและไม่มีเครื่องหมายเปอร์เซ็นต์อย่างน้อยไม่อยู่หน้าเลขฐานสิบหก
  • attfnboth : สองพารามิเตอร์ตามลำดับที่อธิบายไว้ข้างต้น ควรทำงานกับชื่อไฟล์ส่วนใหญ่ในเบราว์เซอร์ส่วนใหญ่แม้ว่า IE8 จะใช้filenameพารามิเตอร์“”

นั่นRFC 5987ในลำดับที่เปิดRFC 2231ซึ่งอธิบายถึงรูปแบบที่เกิดขึ้นจริง 2231 สำหรับจดหมายเป็นหลักและ 5987 บอกเราว่าส่วนใดที่อาจใช้สำหรับส่วนหัว HTTP เช่นกัน อย่าสับสนนี้กับส่วนหัว MIME ใช้ภายในmultipart/form-dataHTTP ร่างกายซึ่งถูกควบคุมโดยRFC 2388 ( ส่วน 4.4โดยเฉพาะ) และHTML 5 ร่าง


1
ฉันมีปัญหาใน Safari เมื่อดาวน์โหลดไฟล์ที่มีชื่อรัสเซียได้รับอักขระที่ผิดพลาดและไม่สามารถอ่านได้ การแก้ปัญหาได้ช่วย แต่เราต้องส่งส่วนหัวในแถวเดียว (!!!)
evtuhovdo

16

เอกสารต่อไปนี้เชื่อมโยงจากร่าง RFC ที่จิมพูดถึงในคำตอบของเขาเพิ่มเติมตอบคำถามและคุ้มค่าบันทึกโดยตรงที่นี่:

กรณีทดสอบสำหรับส่วนหัว HTTP เนื้อหา - การจัดการและการเข้ารหัส RFC 2231/2047


โปรดทราบว่าเราสามารถเข้ารหัสพารามิเตอร์ชื่อไฟล์ได้ทั้งสองวิธีและปรากฏว่าทำงานได้อย่างถูกต้องกับเบราว์เซอร์เก่าและเบราว์เซอร์ใหม่ (เก่าคือ MSIE8 และ Safari ในกรณีนี้) ตรวจสอบattfnbothในรายงานที่กล่าวถึงโดย @AtifAziz
Pablo Montilla

11

ใน asp.net mvc2 ฉันใช้สิ่งนี้:

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

ฉันเดาว่าถ้าคุณไม่ใช้ mvc (2) คุณสามารถเข้ารหัสชื่อไฟล์โดยใช้

HttpUtility.UrlPathEncode(fileName)

2
การเข้ารหัส URL สำหรับการเข้ารหัสชื่อไฟล์ไม่ถูกต้องเบราว์เซอร์ไม่ควรถอดรหัส URL เหล่านั้น
SerialSeb

IE 11 ไม่ได้ถอดรหัสการเข้ารหัส URL ในฟิลด์นี้
pseudocoder

แต่จะต้องมี UrlEncoded เมื่อเบราว์เซอร์เป็น Chrome หรือ IE ส่วนอื่น ๆ เช่น FF, Safari และ Opera ทำงานได้ดีโดยไม่ต้องเข้ารหัส
Reza

11

ใส่ชื่อไฟล์ในเครื่องหมายคำพูดคู่ แก้ไขปัญหาให้ฉัน แบบนี้:

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

http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download

ฉันได้ทดสอบหลายตัวเลือก เบราว์เซอร์ไม่รองรับสเปคและทำหน้าที่แตกต่างกันฉันเชื่อว่าอัญประกาศเป็นตัวเลือกที่ดี


3
เรื่องนี้น่าเศร้าที่ไม่ได้แก้ปัญหาทั้งหมดที่อธิบายไว้ในคำตอบข้างต้น
Luca Steeb

2
นี้จะช่วยให้คุณกลับมาชื่อไฟล์ที่มีช่องว่าง&, %, #ฯลฯ ดังนั้นมันแก้ที่
Don Cheadle

จะเกิดอะไรขึ้นถ้าชื่อไฟล์มีเครื่องหมายคำพูดคู่ (ใช่สิ่งนี้สามารถเกิดขึ้นได้) ตามที่ระบุไว้ใน RFC 6266 ชื่อไฟล์จะเป็น "เครื่องหมายคำพูด" และตามที่ระบุไว้ในเครื่องหมายคำพูดคู่ RFC 2616 ภายในสตริงข้อความที่ยกมา
Christophe Roussy

10

ฉันใช้ตัวอย่างโค้ดต่อไปนี้สำหรับการเข้ารหัส (สมมติว่า ไฟล์มีชื่อไฟล์และนามสกุลของไฟล์เช่น: 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 + "\"");

ขวาควรอยู่rawurlencodeใน PHP อย่างน้อยสำหรับfilename*=ส่วนหัวการจัดการตั้งแต่value-charsใช้ในext-valueRFC 6266-> RFC 5987 (ดูtools.ietf.org/html/rfc6266#section-4.1 & tools.ietf.org/html/rfc5987#section -3.2.1 ) ไม่อนุญาตให้มีพื้นที่โดยไม่มีการยกเว้นเปอร์เซ็นต์ ( filename=ในทางกลับกันดูเหมือนว่าจะอนุญาตให้มีพื้นที่โดยไม่ต้องหลบหนีได้เลยแม้ว่าจะมีเพียง ASCII เท่านั้นที่อยู่ที่นี่) ไม่จำเป็นต้องเข้ารหัสด้วยความเข้มงวดแบบ rawurlencode ดังนั้นอักขระสองสามตัวสามารถยกเลิกการเข้ารหัสได้
Brett Zamir

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 ไม่ได้รับการแก้ไข
แก้ไข IE 9


5

ฉันทดสอบโค้ดต่อไปนี้ในเบราว์เซอร์หลักทั้งหมดรวมถึง 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.'"');

5

ฉันลงเอยด้วยรหัสต่อไปนี้ในสคริปต์ "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) ));

สิ่งนี้ใช้วิธีมาตรฐานของชื่อไฟล์ = "... " ตราบใดที่มีเพียงอักขระ iso-latin1 และ "ปลอดภัย" เท่านั้น มิฉะนั้นจะเพิ่มชื่อไฟล์ * = UTF-8 '' วิธีการเข้ารหัส url ตามกรณีทดสอบนี้โดยเฉพาะควรใช้งานได้ตั้งแต่ MSIE9 ขึ้นไปและบน FF, Chrome, Safari ล่าสุด สำหรับรุ่น MSIE ที่ต่ำกว่านั้นควรเสนอชื่อไฟล์ที่มีชื่อไฟล์รุ่น ISO8859-1 ซึ่งมีขีดล่างของอักขระที่ไม่ได้อยู่ในการเข้ารหัสนี้

หมายเหตุสุดท้าย: จำนวนสูงสุด ขนาดสำหรับแต่ละฟิลด์ส่วนหัวคือ 8190 ไบต์บนอาปาเช่ UTF-8 สามารถมีอักขระสูงสุดสี่ไบต์ต่ออักขระ หลัง rawurlencode จะเป็น x3 = 12 ไบต์ต่อหนึ่งอักขระ แต่มันก็ยังเป็นไปได้ในทางทฤษฎีที่จะมี "รอยยิ้ม" มากกว่า 600% F0% 9F% 98% 81 ในชื่อไฟล์


... แต่ความยาวชื่อไฟล์ที่ถ่ายโอนได้สูงสุดนั้นขึ้นอยู่กับลูกค้าด้วย เพิ่งค้นพบว่าอย่างมาก [89 smiles😁] .pdf ชื่อไฟล์ผ่าน MSIE11 ใน Firefox37 เป็นอย่างมาก [111x 😁] .pdf Chrome41 ตัดชื่อไฟล์ด้วยรอยยิ้มที่ 110 ที่น่าสนใจคือคำต่อท้ายจะถูกถ่ายโอน
apurkrt

5

หากคุณใช้แบ็กเอนด์ 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);
}

1
encodeURI(str)ดีกว่าที่จะใช้ ตัวอย่างเช่นวันที่ในชื่อไฟล์: encodeURIComponent('"Kornél Kovács 1/1/2016')=> "KornélKovács 1% 2F1% 2F2016" vs. encodeURI('"Kornél Kovács 1/1/2016')=> "KornélKovács 1/1/2016"
gdibble

4

ใน 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แทน


3
แม้ว่ารหัสนี้อาจตอบคำถาม แต่การให้บริบทเพิ่มเติมเกี่ยวกับสาเหตุและ / หรือวิธีการตอบคำถามนั้นจะช่วยปรับปรุงมูลค่าระยะยาวได้อย่างมีนัยสำคัญ โปรดแก้ไขคำตอบของคุณเพื่อเพิ่มคำอธิบาย
Toby Speight

2
อ๊ะไม่มีคำตอบสำหรับโค้ดเท่านั้นที่ได้รับการวิจารณ์หรือวิพากษ์วิจารณ์อย่างนั้น นอกจากนี้ฉันยังพบว่าทำไมคำตอบที่ดีพออยู่แล้ว: IE ไม่ได้แปลชื่อไฟล์ * = utf-8 แต่ต้องการชื่อไฟล์เวอร์ชัน ISO8859-1 ซึ่งสคริปต์นี้มีให้ ต้องการเพียงแค่ให้รหัสง่ายๆที่ใช้งานง่ายสำหรับ PHP
กุสตาฟ

ฉันคิดว่าสิ่งนี้ได้ถูกลดระดับลงเนื่องจากคำถามไม่ใช่ภาษาที่เฉพาะเจาะจง แต่เกี่ยวกับ RFC ที่ควรใช้เมื่อใช้การเข้ารหัสส่วนหัว ขอบคุณสำหรับคำตอบนี้สำหรับ PHP โค้ดนี้ทำให้ความเศร้าโศกของฉันหายไป
j4k3

ขอบคุณ. คำตอบนี้อาจไม่ได้ตอบคำถามอย่างเคร่งครัด แต่เป็นสิ่งที่ฉันกำลังมองหาและช่วยฉันแก้ไขปัญหาใน Python
Lyndsy Simon

1
ฉันค่อนข้างมั่นใจว่ารหัสนี้สามารถใช้เป็นเวกเตอร์การโจมตีได้หากผู้ใช้สามารถควบคุมชื่อของไฟล์ได้
Antti Haapala

3

แค่อัปเดตตั้งแต่ฉันพยายามทำทุกอย่างในวันนี้เพื่อตอบสนองต่อปัญหาของลูกค้า

  • ด้วยข้อยกเว้นของ 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 จะได้รับการประมวลผลอย่างถูกต้องในข้อใดข้อหนึ่งข้างต้น

นี่คือการปรับปรุงที่ดี คุณสามารถอธิบายเพิ่มเติมเกี่ยวกับการทดสอบที่คุณลองได้หรือไม่?
แบรด

3

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

1

โซลูชัน 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ชื่อไฟล์ทำงานได้


ลิงค์ที่มีประโยชน์


-1

เรามีปัญหาที่คล้ายกันในโปรแกรมประยุกต์บนเว็บและจบลงด้วยการอ่านชื่อไฟล์จาก HTML ที่<input type="file">และการตั้งค่าที่อยู่ในรูปแบบเข้ารหัส URL ใน <input type="hidden">HTML แน่นอนว่าเราต้องลบเส้นทางเช่น "C: \ fakepath \" ที่ส่งคืนโดยเบราว์เซอร์บางตัว

ของหลักสูตรนี้ไม่ได้ตอบคำถาม OPs โดยตรง แต่อาจเป็นทางออกสำหรับคนอื่น ๆ


1
ปัญหาที่แตกต่างอย่างสิ้นเชิง คำถามที่เป็นเรื่องเกี่ยวกับการดาวน์โหลด , การตอบของคุณเป็นเรื่องเกี่ยวกับการอัปโหลด
Oskar Berggren

-3

ปกติแล้วฉันจะใช้การเข้ารหัส URL (ด้วย% xx) ชื่อไฟล์และดูเหมือนว่าจะใช้ได้กับทุกเบราว์เซอร์ คุณอาจต้องการทำแบบทดสอบ


10
ฉันได้ทดสอบในไม่กี่และมันไม่ทำงานในเบราว์เซอร์ทั้งหมดดังนั้นคำถาม :)
Atif Aziz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.