summaryrefslogtreecommitdiff
path: root/django/http
diff options
context:
space:
mode:
authorPiotr Kunicki <piotr.kunicki@o2.pl>2021-10-14 11:41:10 +0200
committerCarlton Gibson <carlton.gibson@noumenal.es>2021-10-14 15:05:04 +0200
commitdc724c5bf9d3b8d59c9571aa751c3cd001cdeced (patch)
tree8f3d1079a843438a6a2f95a40a30a1528259ec5d /django/http
parent3ac476439751334138edcbb9abd73f783afcec4c (diff)
Fixed #30509 -- Made FileResponse better handle buffers and non-zero file offsets.
Diffstat (limited to 'django/http')
-rw-r--r--django/http/response.py39
1 files changed, 25 insertions, 14 deletions
diff --git a/django/http/response.py b/django/http/response.py
index ab7158f7e0..68b7a8f285 100644
--- a/django/http/response.py
+++ b/django/http/response.py
@@ -1,4 +1,5 @@
import datetime
+import io
import json
import mimetypes
import os
@@ -437,6 +438,7 @@ class FileResponse(StreamingHttpResponse):
def __init__(self, *args, as_attachment=False, filename='', **kwargs):
self.as_attachment = as_attachment
self.filename = filename
+ self._no_default_content_type_set = 'content_type' not in kwargs or kwargs['content_type'] is None
super().__init__(*args, **kwargs)
def _set_streaming_content(self, value):
@@ -456,29 +458,38 @@ class FileResponse(StreamingHttpResponse):
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.headers['Content-Length'] = os.path.getsize(filelike.name)
- elif hasattr(filelike, 'getbuffer'):
- self.headers['Content-Length'] = filelike.getbuffer().nbytes
+ filename = getattr(filelike, 'name', '')
+ filename = filename if isinstance(filename, str) else ''
+ seekable = hasattr(filelike, 'seek') and (not hasattr(filelike, 'seekable') or filelike.seekable())
+ if hasattr(filelike, 'tell'):
+ if seekable:
+ initial_position = filelike.tell()
+ filelike.seek(0, io.SEEK_END)
+ self.headers['Content-Length'] = filelike.tell() - initial_position
+ filelike.seek(initial_position)
+ elif hasattr(filelike, 'getbuffer'):
+ self.headers['Content-Length'] = filelike.getbuffer().nbytes - filelike.tell()
+ elif os.path.exists(filename):
+ self.headers['Content-Length'] = os.path.getsize(filename) - filelike.tell()
+ elif seekable:
+ self.headers['Content-Length'] = sum(iter(lambda: len(filelike.read(self.block_size)), 0))
+ filelike.seek(-int(self.headers['Content-Length']), io.SEEK_END)
- if self.headers.get('Content-Type', '').startswith('text/html'):
+ filename = os.path.basename(self.filename or filename)
+ if self._no_default_content_type_set:
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)
+ content_type = {
+ 'bzip2': 'application/x-bzip',
+ 'gzip': 'application/gzip',
+ 'xz': 'application/x-xz',
+ }.get(encoding, content_type)
self.headers['Content-Type'] = content_type or 'application/octet-stream'
else:
self.headers['Content-Type'] = 'application/octet-stream'
- filename = self.filename or os.path.basename(filename)
if filename:
disposition = 'attachment' if self.as_attachment else 'inline'
try: