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 | |
| 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')
| -rw-r--r-- | django/http/response.py | 61 | ||||
| -rw-r--r-- | django/views/static.py | 3 |
2 files changed, 53 insertions, 11 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'] diff --git a/django/views/static.py b/django/views/static.py index b691e57e6c..90bad8db7e 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -5,7 +5,6 @@ during development, and SHOULD NOT be used in a production setting. import mimetypes import posixpath import re -import stat from pathlib import Path from django.http import ( @@ -50,8 +49,6 @@ def serve(request, path, document_root=None, show_indexes=False): content_type = content_type or 'application/octet-stream' response = FileResponse(fullpath.open('rb'), content_type=content_type) response["Last-Modified"] = http_date(statobj.st_mtime) - if stat.S_ISREG(statobj.st_mode): - response["Content-Length"] = statobj.st_size if encoding: response["Content-Encoding"] = encoding return response |
