簡體   English   中英

如何為 HTTP 標頭編碼 UTF8 文件名? (蟒蛇,姜戈)

[英]How to encode UTF8 filename for HTTP headers? (Python, Django)

我對 HTTP 標頭有疑問,它們是用 ASCII 編碼的,我想提供一個視圖來下載名稱可以是非 ASCII 的文件。

response['Content-Disposition'] = 'attachment; filename="%s"' % (vo.filename.encode("ASCII","replace"), )

我不想使用 static 文件來處理非 ASCII 文件名的相同問題,但在這種情況下,文件系統及其文件名編碼會出現問題。 (我不知道目標操作系統。)

我已經嘗試過 urllib.quote(),但它引發了 KeyError 異常。

可能我做錯了什么,但也許這是不可能的。

這是一個常見問題。

沒有可互操作的方法來做到這一點。 一些瀏覽器實現專有擴展(IE、Chrome),其他實現 RFC 2231(Firefox、Opera)。

請參閱http://greenbytes.de/tech/tc2231/ 上的測試用例。

更新:截至 2012 年 11 月,所有當前的桌面瀏覽器都支持 RFC 6266 和 RFC 5987 中定義的編碼(Safari >= 6、IE >= 9、Chrome、Firefox、Opera、Konqueror)。

不要在 Content-Disposition 中發送文件名。 沒有辦法讓非 ASCII 標頭參數跨瀏覽器(*)工作。

相反,只發送“Content-Disposition:attachment”,並將文件名作為 URL 編碼的 UTF-8 字符串保留在 URL 的尾隨 (PATH_INFO) 部分,供瀏覽器默認選擇和使用。 與 Content-Disposition 相比,瀏覽器處理 UTF-8 URL 的可靠性要高得多。

(*:實際上,目前甚至沒有一個標准來說明該如何做,因為 RFC 2616、2231 和 2047 之間的關系非常不正常,這是 Julian 試圖在規范級別澄清的。一致的瀏覽器支持是在遙遠的未來。)

請注意,在 2011 年, RFC 6266 (尤其是附錄 D)對此問題進行了權衡,並提出了具體的建議。

也就是說,你可以發出一個filename ,只有ASCII字符,然后filename*與那些理解代理RFC 5987格式的文件名。

通常這看起來像filename="my-resume.pdf"; filename*=UTF-8''My%20R%C3%A9sum%C3%A9.pdf filename="my-resume.pdf"; filename*=UTF-8''My%20R%C3%A9sum%C3%A9.pdf ,其中 Unicode 文件名(“My Résumé.pdf”)被編碼為 UTF-8 然后百分比編碼(注意,不要使用+表示空格)。

請實際閱讀 RFC 6266 和 RFC 5987(或使用一個強大且經過測試的庫來為您抽象),因為我在這里的總結缺乏重要的細節。

Django 2.1 (請參閱問題#16470 )開始,您可以使用FileResponse ,它將正確設置附件的Content-Disposition標頭。 Django 3.0 (issue #30196 ) 開始,它還將為inline文件正確設置它。

例如,要返回一個名為my_img.jpg且 MIME 類型為image/jpeg作為 HTTP 響應:

response = FileResponse(open("my_img.jpg", 'rb'), as_attachment=True, content_type="image/jpeg")
return response

或者,如果您不能使用FileResponse ,您可以使用FileResponse源中的相關部分來自己設置Content-Disposition標頭。 這是該來源目前的樣子:

from urllib.parse import quote

disposition = 'attachment' if as_attachment else 'inline'
try:
    filename.encode('ascii')
    file_expr = 'filename="{}"'.format(filename)
except UnicodeEncodeError:
    file_expr = "filename*=utf-8''{}".format(quote(filename))
response.headers['Content-Disposition'] = '{}; {}'.format(disposition, file_expr)

我可以說我使用較新的( RFC 5987 )格式成功地指定了用電子郵件表單( RFC 2231 )編碼的標頭。 我想出了以下基於 django-sendfile 項目代碼的解決方案。

import unicodedata
from django.utils.http import urlquote

def rfc5987_content_disposition(file_name):
    ascii_name = unicodedata.normalize('NFKD', file_name).encode('ascii','ignore').decode()
    header = 'attachment; filename="{}"'.format(ascii_name)
    if ascii_name != file_name:
        quoted_name = urlquote(file_name)
        header += '; filename*=UTF-8\'\'{}'.format(quoted_name)

    return header

# e.g.
  # request['Content-Disposition'] = rfc5987_content_disposition(file_name)

我只用Django 1.8Python 3.4上測試了我的代碼。 因此django-sendfile 中的類似解決方案可能更適合您。

Django 的跟蹤器中有一張長期存在的票證,它承認這一點,但尚未提出任何補丁。 所以不幸的是,這與我能找到的使用強大的測試庫一樣接近,如果有更好的解決方案,請告訴我。

來自 Django 的escape_uri_path function 是對我有用的解決方案。

閱讀此處的 Django 文檔,了解當前指定了哪些 RFC 標准。

from django.utils.encoding import escape_uri_path

file = "response.zip"
response = HttpResponse(content_type='application/zip')
response['Content-Disposition'] = f"attachment; filename*=utf-8''{escape_uri_path(file)}"

一個黑客:

if (Request.UserAgent.Contains("IE"))
{
  // IE will accept URL encoding, but spaces don't need to be, and since they're so common..
  filename = filename.Replace("%", "%25").Replace(";", "%3B").Replace("#", "%23").Replace("&", "%26");
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM