diff options
| author | Claude Paroz <claude@2xlibre.net> | 2018-05-15 18:12:11 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-05-15 18:12:11 +0200 |
| commit | a177f854c34718e473bcd0a2dc6c4fd935c8e327 (patch) | |
| tree | d15e436c26edfd2037972c48095b4bcd2ad48505 /django/http | |
| parent | 2dcc5d629a6439b5547cdd6e67815cabf608fcd4 (diff) | |
Fixed #16470 -- Allowed FileResponse to auto-set some Content headers.
Thanks Simon Charette, Jon Dufresne, and Tim Graham for the reviews.
Diffstat (limited to 'django/http')
| -rw-r--r-- | django/http/response.py | 61 |
1 files changed, 53 insertions, 8 deletions
diff --git a/django/http/response.py b/django/http/response.py index 96c0cae597..266c6efb73 100644 --- a/django/http/response.py +++ b/django/http/response.py @@ -1,11 +1,13 @@ import datetime import json +import mimetypes +import os import re import sys import time from email.header import Header from http.client import responses -from urllib.parse import urlparse +from urllib.parse import quote, urlparse from django.conf import settings from django.core import signals, signing @@ -391,17 +393,60 @@ class FileResponse(StreamingHttpResponse): """ block_size = 4096 + def __init__(self, *args, as_attachment=False, filename='', **kwargs): + self.as_attachment = as_attachment + self.filename = filename + super().__init__(*args, **kwargs) + def _set_streaming_content(self, value): - if hasattr(value, 'read'): - self.file_to_stream = value - filelike = value - if hasattr(filelike, 'close'): - self._closable_objects.append(filelike) - value = iter(lambda: filelike.read(self.block_size), b'') - else: + if not hasattr(value, 'read'): self.file_to_stream = None + return super()._set_streaming_content(value) + + self.file_to_stream = filelike = value + if hasattr(filelike, 'close'): + self._closable_objects.append(filelike) + value = iter(lambda: filelike.read(self.block_size), b'') + self.set_headers(filelike) super()._set_streaming_content(value) + def set_headers(self, filelike): + """ + Set some common response headers (Content-Length, Content-Type, and + Content-Disposition) based on the `filelike` response content. + """ + encoding_map = { + 'bzip2': 'application/x-bzip', + 'gzip': 'application/gzip', + 'xz': 'application/x-xz', + } + filename = getattr(filelike, 'name', None) + filename = filename if (isinstance(filename, str) and filename) else self.filename + if os.path.isabs(filename): + self['Content-Length'] = os.path.getsize(filelike.name) + elif hasattr(filelike, 'getbuffer'): + self['Content-Length'] = filelike.getbuffer().nbytes + + if self.get('Content-Type', '').startswith(settings.DEFAULT_CONTENT_TYPE): + if filename: + content_type, encoding = mimetypes.guess_type(filename) + # Encoding isn't set to prevent browsers from automatically + # uncompressing files. + content_type = encoding_map.get(encoding, content_type) + self['Content-Type'] = content_type or 'application/octet-stream' + else: + self['Content-Type'] = 'application/octet-stream' + + if self.as_attachment: + filename = self.filename or os.path.basename(filename) + if filename: + try: + filename.encode('ascii') + file_expr = 'filename="{}"'.format(filename) + except UnicodeEncodeError: + file_expr = "filename*=utf-8''{}".format(quote(filename)) + self['Content-Disposition'] = 'attachment; {}'.format(file_expr) + class HttpResponseRedirectBase(HttpResponse): allowed_schemes = ['http', 'https', 'ftp'] |
