browser تحميل - كيف يتم ترميز معلمة اسم الملف لرأس Content-Disposition في HTTP؟




شرح header (15)

تُصدر تطبيقات الويب التي تريد فرض مورد ليتم تنزيله بدلاً من تقديمه مباشرة في مستعرض ويب رأس Content-Disposition في استجابة HTTP للنموذج:

Content-Disposition: attachment; filename= FILENAME

يمكن استخدام معلمة filename لاقتراح اسم للملف الذي يتم تنزيل المورد من خلاله. RFC 2183 (المحتوى - الترتيب) ، ومع ذلك ، ينص في المقطع 2.3 (معلمة اسم الملف) أن اسم الملف يمكن فقط استخدام أحرف US-ASCII:

القواعد الحالية [RFC 2045] تقيّد قيم المعلمات (وبالتالي أسماء ملفات ترتيب المحتوى) إلى US-ASCII. نحن ندرك الرغبة الكبيرة في السماح بمجموعات الحروف التعسفية في أسماء الملفات ، ولكنه يتجاوز نطاق هذه الوثيقة لتحديد الآليات الضرورية.

هناك أدلة تجريبية ، على الرغم من ذلك ، يبدو أن معظم متصفحات الويب الشائعة اليوم تسمح بأحرف غير US-ASCII حتى الآن (لعدم وجود معيار) لا تتفق على نظام الترميز ومواصفات مجموعة الأحرف لاسم الملف. السؤال إذن هو ، ما هي المخططات والتشفيرات المختلفة التي تستخدمها المتصفحات الشائعة إذا كان اسم الملف "naïvefile" (بدون علامتي الاقتباس وحيث يكون الحرف الثالث هو U + 00EF) يلزم تشفيره في رأسية ترتيب المحتوى؟

لغرض هذا السؤال ، أصبحت المتصفحات الشائعة :

  • ثعلب النار
  • متصفح الانترنت
  • رحلات السفاري
  • جوجل كروم
  • دار الأوبرا

Answers

انتهى بي المطاف مع التعليمات البرمجية التالية في البرنامج النصي "download.php" الخاص بي (استناداً إلى هذا 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 و "safe" المستخدمة ؛ إذا لم يكن كذلك ، فإنه يضيف اسم الملف * = UTF-8 '' url-encoded way. وفقًا لحالة الاختبار المحددة هذه ، يجب أن تعمل من MSIE9 ، وعلى FF ، Chrome ، Safari حديثًا ؛ في إصدار MSIE السفلي ، يجب أن يقدم اسم الملف الذي يحتوي على إصدار ISO8859-1 من اسم الملف ، مع وجود أحرف سفلية على أحرف ليست في هذا الترميز.

ملاحظة أخيرة: الحد الأقصى حجم لكل حقل رأس 8190 بايت على اباتشي. يمكن أن يصل UTF-8 إلى أربعة بايت لكل حرف؛ بعد rawurlencode ، يكون x3 = 12 بايت لكل حرف واحد. غير فعالة إلى حد ما ، ولكن يجب أن يكون من الممكن نظريًا الحصول على أكثر من 600 "ابتسامة"٪ F0٪ 9F٪ 98٪ 81 في اسم الملف.


في واجهة برمجة تطبيقات ASP.NET على الويب ، أقوم بتشفير اسم الملف:

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



  • لا توجد طريقة قابلة للتشغيل المتبادل لترميز أسماء غير ASCII في Content-Disposition . توافق المتصفح هو الفوضى .

  • greenbytes.de/tech/webdav/rfc5987.html لاستخدام 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


في PHP هذا بالنسبة لي (بافتراض أن اسم الملف هو UTF8 مشفر):

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

تم اختباره ضد IE8-11 و Firefox و Chrome.
إذا كان المستعرض يستطيع تفسير filename * = utf-8 ، فسيستخدم إصدار UTF8 من اسم الملف ، وإلا فسيستخدم اسم الملف المشفر. إذا كان اسم الملف الخاص بك يحتوي على أحرف لا يمكن تمثيلها في ISO-8859-1 ، فربما ترغب في استخدام iconv بدلاً من ذلك.


عادة ما أقوم بتشفير عناوين URL (مع٪ xx) أسماء الملفات ، ويبدو أنها تعمل في جميع المتصفحات. قد ترغب في القيام ببعض الاختبارات على أي حال.



في asp.net mvc2 يمكنني استخدام شيء من هذا القبيل:

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

أعتقد إذا كنت لا تستخدم mvc (2) يمكنك فقط ترميز اسم الملف باستخدام

HttpUtility.UrlPathEncode(fileName)

إذا كنت تستخدم backode nodejs ، فيمكنك استخدام الكود التالي الذي وجدته here

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

لقد اكتشفت حلًا يعمل مع جميع المتصفحات (أي جميع المتصفحات التي قمت بتثبيتها - IE8 و FF16 و Opera 12 و Chrome 22).

يتم وصف حل بلدي في موضوع آخر: جافا servlet تنزيل اسم الملف أحرف خاصة

ويستند حل بلدي على حقيقة ، وكيف يحاول المتصفحات قراءة القيمة من معلمة 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 I ، استجاب هذا العنوان (المشفر في utf-8):

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

و IE أنا الرد على هذا الرأس (المشفرة في الفوز -1250):

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

مثال Java موجود في مشاركتي المذكورة أعلاه.


أعلم أن هذه رسالة قديمة ولكنها لا تزال ذات صلة. لقد وجدت أن المتصفحات الحديثة تدعم rfc5987 ، مما يسمح بتشفير UTF-8 ، ونسبة مئوية مشفرة (تشفير عنوان URL). ثم يصبح Naïve file.txt:

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

سفاري (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æøåÆØÅäööïëêîâéíúýúýúýúý½§! # ¤٪ & () = `@ £ $ {[]} + ´¨ ^ ~ -_.،؛ النص

على 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 فان دن Eynde: الجمع بين أسماء الملفات اثنين على سطر واحد وفقا ل RFC6266 يعمل باستثناء أندرويد و IE7 + 8 ولقد قمت بتحديث رمز لعكس هذا. شكرا لك على الاقتراح.

Thilo: لا توجد فكرة عن GoodReader أو أي متصفح آخر غير. قد يكون لديك بعض الحظ باستخدام منهج Android.

@ الكسكس جوكوفسكي: أنا لا أعرف لماذا ولكن كما هو موضح في Connect لا يبدو أنه يعمل بشكل جيد بشكل رهيب.


هناك مناقشة هذا ، بما في ذلك الارتباطات إلى اختبار المستعرض والتوافق مع الإصدارات السابقة ، في RFC 5987 المقترح ، "مجموعة الأحرف وترميز اللغة لمعلمات حقل رأس بروتوكول نقل النص التشعبي (HTTP)."

يشير RFC 2183 إلى أنه يجب ترميز مثل هذه الرؤوس وفقًا لـ RFC 2184 ، والتي تم استبدالها بواسطة RFC 2231 ، والتي تمت تغطيتها بواسطة مسودة RFC أعلاه.


أستخدم مقتطفات الشفرة التالية للتشفير (بافتراض أن fileName يحتوي على اسم الملف وامتداده ، أي: 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 + "\"");

يصف RFC 6266 " استخدام حقل رأس Content-Disposition Header في بروتوكول نقل النص التشعبي (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 هناك أيضًا قائمة طويلة من الاقتراحات لزيادة قابلية التشغيل المتداخل. يشير أيضًا إلى موقع يقارن عمليات التنفيذ . تتضمن اختبارات تمرير الكل الحالية المناسبة لأسماء الملفات الشائعة ما يلي:

  • attwithisofnplain : اسم ملف ISO-8859-1 عادي مع علامات اقتباس مزدوجة وبدون تشفير. هذا يتطلب اسم ملف هو كل ISO-8859-1 ولا يحتوي على علامات النسبة المئوية ، على الأقل ليس أمام الأرقام السداسية.
  • attfnboth : attfnboth بالترتيب الموصوف أعلاه. يجب أن تعمل مع معظم أسماء الملفات في معظم المتصفحات ، على الرغم من استخدام IE8 لمعلمة " filename ".

يشير RFC 5987 بدوره إلى RFC 2231 ، الذي يصف التنسيق الفعلي. 2231 هو في المقام الأول للبريد ، ويخبرنا 5987 ما هي الأجزاء التي يمكن استخدامها لرؤوس HTTP أيضًا. لا تخلط بين هذا ورؤوس MIME المستخدمة داخل نص HTTP multipart/form-data ، والذي يحكمه RFC 2388 ( القسم 4.4 على وجه الخصوص) ومسودة HTML 5 .


header('Content-type: image/png') مع خدمة PHP 5.5 التي تخدم IE11 ، كما هو موضح في تدفق الصور كنص

header('Content-Type: image/png') عملت ، كما هو الحال في الصورة ظهرت كصورة

الفرق الوحيد هو رأس المال 'T'.





browser http-headers specifications