summaryrefslogtreecommitdiff
path: root/django/http
diff options
context:
space:
mode:
authorClaude Paroz <claude@2xlibre.net>2018-05-15 18:12:11 +0200
committerGitHub <noreply@github.com>2018-05-15 18:12:11 +0200
commita177f854c34718e473bcd0a2dc6c4fd935c8e327 (patch)
treed15e436c26edfd2037972c48095b4bcd2ad48505 /django/http
parent2dcc5d629a6439b5547cdd6e67815cabf608fcd4 (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.py61
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']