كيفية تشفير معلمة اسم الملف لرأس ترتيب المحتوى في HTTP؟

StackOverflow https://stackoverflow.com/questions/93551

سؤال

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

Content-Disposition: attachment; filename=اسم الملف

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

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

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

لغرض هذا السؤال، المتصفحات الشعبية كون:

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

المحلول

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

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

نصائح أخرى

أعلم أن هذا منشور قديم ولكنه لا يزال وثيق الصلة بالموضوع.لقد وجدت أن المتصفحات الحديثة تدعم 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æøåÆØÅäöüïëêîâéíáóúýñ½§!#¤%&()=`@$$€{[]}+´¨^~'-_,;.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);
}

@تومز:لقد اختبرت في IE7 وIE8 واتضح أنني لست بحاجة إلى الهروب من الفاصلة العليا (').هل لديك مثال حيث فشل؟

@ ديف فان دن إيندي:يعمل الجمع بين اسمي الملفين في سطر واحد وفقًا لـ RFC6266 باستثناء Android وIE7+8 وقمت بتحديث الكود ليعكس ذلك.شكرا لك على هذا الاقتراح.

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

@أليكس جوكوفسكي:لا أعرف لماذا ولكن كما تمت مناقشته يتصل لا يبدو أنه يعمل بشكل جيد للغاية.

هناك بديل بسيط وقوي للغاية: استخدم عنوان URL الذي يحتوي على اسم الملف الذي تريده.

عندما يكون الاسم الموجود بعد الشرطة المائلة الأخيرة هو الاسم الذي تريده، فلن تحتاج إلى أي رؤوس إضافية!

تعمل هذه الخدعة:

/real_script.php/fake_filename.doc

وإذا كان خادمك يدعم إعادة كتابة عنوان URL (على سبيل المثال، mod_rewrite في Apache) فيمكنك إخفاء جزء البرنامج النصي بالكامل.

يجب أن تكون الأحرف الموجودة في عناوين URL بالترميز UTF-8، وأن يتم ترميزها بايت بايت:

/mot%C3%B6rhead   # motörhead

آر إف سي 6266 يصف "استخدام حقل رأس ترتيب المحتوى في بروتوكول نقل النص التشعبي (HTTP)".نقلا عن ذلك:

6.اعتبارات التدويل

ال "filename*" معامل (القسم 4.3) ، باستخدام الترميز المحدد في [[RFC5987] ، يسمح للخادم بنقل الأحرف خارج مجموعة أحرف ISO-8859-1 ، وكذلك تحديد اللغة المستخدمة اختياريا.

وفيهم قسم الأمثلة:

هذا المثال هو نفسه الموجود أعلاه ، ولكن إضافة معلمة "اسم الملف" للتوافق مع وكلاء المستخدمين لا ينفذون آر إف سي 5987:

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

ملحوظة:وكلاء المستخدم هؤلاء الذين لا يدعمون آر إف سي 5987 تجاهل الترميز "filename*"عندما يحدث بعد"filename”.

في الملحق د هناك أيضًا قائمة طويلة من الاقتراحات لزيادة إمكانية التشغيل البيني.ويشير أيضًا إلى موقع يقارن التنفيذ.تتضمن اختبارات النجاح الحالية المناسبة لأسماء الملفات الشائعة ما يلي:

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

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

الوثيقة التالية مرتبطة من مشروع RFC ذكرها جيم في إجابته يتناول السؤال بشكل أكبر ويستحق بالتأكيد ملاحظة مباشرة هنا:

حالات اختبار لرأس ترتيب محتوى HTTP وترميز RFC 2231/2047

في asp.net mvc2 أستخدم شيئًا مثل هذا:

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

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

HttpUtility.UrlPathEncode(fileName)

ضع اسم الملف بين علامتي اقتباس مزدوجتين.حل المشكلة بالنسبة لي.مثله:

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

http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download

لقد اختبرت خيارات متعددة.المتصفحات لا تدعم المواصفات وتتصرف بشكل مختلف، وأعتقد أن علامات الاقتباس المزدوجة هي الخيار الأفضل.

أستخدم مقتطفات التعليمات البرمجية التالية للتشفير (على افتراض اسم الملف يحتوي على اسم الملف وامتداد الملف، أي:اختبار.txt):


بي أتش بي:

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 + "\"");

في 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

لقد اختبرت الكود التالي في جميع المتصفحات الرئيسية، بما في ذلك المتصفحات الأقدم (عبر وضع التوافق)، وهو يعمل بشكل جيد في كل مكان:

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

إذا كنت تستخدم الواجهة الخلفية لـ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);
}

انتهى بي الأمر بالرمز التالي في البرنامج النصي "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 في اسم الملف.

في 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 بدلاً من.

حل 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 أسماء الملفات تعمل.


روابط مفيدة

مجرد تحديث منذ أن كنت أحاول كل هذه الأشياء اليوم ردًا على مشكلة تتعلق بالعميل

  • باستثناء Safari الذي تم تكوينه للغة اليابانية، عملت جميع المتصفحات التي اختبرها عملاؤنا بشكل أفضل مع filename=text.pdf - حيث يمثل النص قيمة العميل التي تم تسلسلها بواسطة ASP.Net/IIS بتنسيق utf-8 بدون تشفير عنوان url.لسبب ما، سيقبل Safari الذي تم تكوينه للغة الإنجليزية ملفًا باسم utf-8 الياباني ويحفظه بشكل صحيح، ولكن نفس المتصفح الذي تم تكوينه للغة اليابانية سيحفظ الملف بأحرف utf-8 دون تفسير.يبدو أن جميع المتصفحات الأخرى التي تم اختبارها تعمل بشكل أفضل/جيد (بغض النظر عن تكوين اللغة) باستخدام اسم الملف utf-8 المشفر بدون تشفير عنوان url.
  • لم أتمكن من العثور على متصفح واحد يطبق Rfc5987/8187 على الاطلاق.لقد اختبرت باستخدام أحدث إصدارات Chrome وFirefox بالإضافة إلى IE 11 وEdge.لقد حاولت تعيين الرأس باستخدام filename*=utf-8''texturlencoded.pdf فقط، وإعداده باستخدام كل من filename=text.pdf;اسم الملف*=utf-8''texturlencoded.pdf.لا يبدو أنه تمت معالجة أي ميزة من ميزات Rfc5987/8187 بشكل صحيح في أي مما سبق.

واجهنا مشكلة مماثلة في أحد تطبيقات الويب، وانتهى بنا الأمر بقراءة اسم الملف من HTML <input type="file">, وتعيين ذلك في النموذج المشفر بعنوان URL في ملف HTML جديد <input type="hidden">.بالطبع كان علينا إزالة المسار مثل "C:\fakepath\" الذي يتم إرجاعه بواسطة بعض المتصفحات.

بالطبع هذا لا يجيب بشكل مباشر على سؤال OPs، ولكنه قد يكون حلاً للآخرين.

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top