diff options
| author | django-bot <ops@djangoproject.com> | 2022-02-03 20:24:19 +0100 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2022-02-07 20:37:05 +0100 |
| commit | 9c19aff7c7561e3a82978a272ecdaad40dda5c00 (patch) | |
| tree | f0506b668a013d0063e5fba3dbf4863b466713ba /django/http | |
| parent | f68fa8b45dfac545cfc4111d4e52804c86db68d3 (diff) | |
Refs #33476 -- Reformatted code with Black.
Diffstat (limited to 'django/http')
| -rw-r--r-- | django/http/__init__.py | 53 | ||||
| -rw-r--r-- | django/http/cookie.py | 8 | ||||
| -rw-r--r-- | django/http/multipartparser.py | 180 | ||||
| -rw-r--r-- | django/http/request.py | 246 | ||||
| -rw-r--r-- | django/http/response.py | 276 |
5 files changed, 462 insertions, 301 deletions
diff --git a/django/http/__init__.py b/django/http/__init__.py index 491239bf8a..4c997154d9 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -1,21 +1,48 @@ from django.http.cookie import SimpleCookie, parse_cookie from django.http.request import ( - HttpRequest, QueryDict, RawPostDataException, UnreadablePostError, + HttpRequest, + QueryDict, + RawPostDataException, + UnreadablePostError, ) from django.http.response import ( - BadHeaderError, FileResponse, Http404, HttpResponse, - HttpResponseBadRequest, HttpResponseForbidden, HttpResponseGone, - HttpResponseNotAllowed, HttpResponseNotFound, HttpResponseNotModified, - HttpResponsePermanentRedirect, HttpResponseRedirect, - HttpResponseServerError, JsonResponse, StreamingHttpResponse, + BadHeaderError, + FileResponse, + Http404, + HttpResponse, + HttpResponseBadRequest, + HttpResponseForbidden, + HttpResponseGone, + HttpResponseNotAllowed, + HttpResponseNotFound, + HttpResponseNotModified, + HttpResponsePermanentRedirect, + HttpResponseRedirect, + HttpResponseServerError, + JsonResponse, + StreamingHttpResponse, ) __all__ = [ - 'SimpleCookie', 'parse_cookie', 'HttpRequest', 'QueryDict', - 'RawPostDataException', 'UnreadablePostError', - 'HttpResponse', 'StreamingHttpResponse', 'HttpResponseRedirect', - 'HttpResponsePermanentRedirect', 'HttpResponseNotModified', - 'HttpResponseBadRequest', 'HttpResponseForbidden', 'HttpResponseNotFound', - 'HttpResponseNotAllowed', 'HttpResponseGone', 'HttpResponseServerError', - 'Http404', 'BadHeaderError', 'JsonResponse', 'FileResponse', + "SimpleCookie", + "parse_cookie", + "HttpRequest", + "QueryDict", + "RawPostDataException", + "UnreadablePostError", + "HttpResponse", + "StreamingHttpResponse", + "HttpResponseRedirect", + "HttpResponsePermanentRedirect", + "HttpResponseNotModified", + "HttpResponseBadRequest", + "HttpResponseForbidden", + "HttpResponseNotFound", + "HttpResponseNotAllowed", + "HttpResponseGone", + "HttpResponseServerError", + "Http404", + "BadHeaderError", + "JsonResponse", + "FileResponse", ] diff --git a/django/http/cookie.py b/django/http/cookie.py index b94d2b0386..dce0cfdf25 100644 --- a/django/http/cookie.py +++ b/django/http/cookie.py @@ -9,13 +9,13 @@ def parse_cookie(cookie): Return a dictionary parsed from a `Cookie:` header string. """ cookiedict = {} - for chunk in cookie.split(';'): - if '=' in chunk: - key, val = chunk.split('=', 1) + for chunk in cookie.split(";"): + if "=" in chunk: + key, val = chunk.split("=", 1) else: # Assume an empty name per # https://bugzilla.mozilla.org/show_bug.cgi?id=169091 - key, val = '', chunk + key, val = "", chunk key, val = key.strip(), val.strip() if key or val: # unquote using Python's algorithm. diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py index ef0b339d1b..13ed2fa4cd 100644 --- a/django/http/multipartparser.py +++ b/django/http/multipartparser.py @@ -13,15 +13,15 @@ from urllib.parse import unquote from django.conf import settings from django.core.exceptions import ( - RequestDataTooBig, SuspiciousMultipartForm, TooManyFieldsSent, -) -from django.core.files.uploadhandler import ( - SkipFile, StopFutureHandlers, StopUpload, + RequestDataTooBig, + SuspiciousMultipartForm, + TooManyFieldsSent, ) +from django.core.files.uploadhandler import SkipFile, StopFutureHandlers, StopUpload from django.utils.datastructures import MultiValueDict from django.utils.encoding import force_str -__all__ = ('MultiPartParser', 'MultiPartParserError', 'InputStreamExhausted') +__all__ = ("MultiPartParser", "MultiPartParserError", "InputStreamExhausted") class MultiPartParserError(Exception): @@ -32,6 +32,7 @@ class InputStreamExhausted(Exception): """ No more reads are allowed from this device. """ + pass @@ -47,6 +48,7 @@ class MultiPartParser: ``MultiValueDict.parse()`` reads the input stream in ``chunk_size`` chunks and returns a tuple of ``(MultiValueDict(POST), MultiValueDict(FILES))``. """ + def __init__(self, META, input_data, upload_handlers, encoding=None): """ Initialize the MultiPartParser object. @@ -62,23 +64,28 @@ class MultiPartParser: The encoding with which to treat the incoming data. """ # Content-Type should contain multipart and the boundary information. - content_type = META.get('CONTENT_TYPE', '') - if not content_type.startswith('multipart/'): - raise MultiPartParserError('Invalid Content-Type: %s' % content_type) + content_type = META.get("CONTENT_TYPE", "") + if not content_type.startswith("multipart/"): + raise MultiPartParserError("Invalid Content-Type: %s" % content_type) # Parse the header to get the boundary to split the parts. try: - ctypes, opts = parse_header(content_type.encode('ascii')) + ctypes, opts = parse_header(content_type.encode("ascii")) except UnicodeEncodeError: - raise MultiPartParserError('Invalid non-ASCII Content-Type in multipart: %s' % force_str(content_type)) - boundary = opts.get('boundary') + raise MultiPartParserError( + "Invalid non-ASCII Content-Type in multipart: %s" + % force_str(content_type) + ) + boundary = opts.get("boundary") if not boundary or not cgi.valid_boundary(boundary): - raise MultiPartParserError('Invalid boundary in multipart: %s' % force_str(boundary)) + raise MultiPartParserError( + "Invalid boundary in multipart: %s" % force_str(boundary) + ) # Content-Length should contain the length of the body we are about # to receive. try: - content_length = int(META.get('CONTENT_LENGTH', 0)) + content_length = int(META.get("CONTENT_LENGTH", 0)) except (ValueError, TypeError): content_length = 0 @@ -87,14 +94,14 @@ class MultiPartParser: raise MultiPartParserError("Invalid content length: %r" % content_length) if isinstance(boundary, str): - boundary = boundary.encode('ascii') + boundary = boundary.encode("ascii") self._boundary = boundary self._input_data = input_data # For compatibility with low-level network APIs (with 32-bit integers), # the chunk size should be < 2^31, but still divisible by 4. possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size] - self._chunk_size = min([2 ** 31 - 4] + possible_sizes) + self._chunk_size = min([2**31 - 4] + possible_sizes) self._meta = META self._encoding = encoding or settings.DEFAULT_CHARSET @@ -163,32 +170,36 @@ class MultiPartParser: uploaded_file = True try: - disposition = meta_data['content-disposition'][1] - field_name = disposition['name'].strip() + disposition = meta_data["content-disposition"][1] + field_name = disposition["name"].strip() except (KeyError, IndexError, AttributeError): continue - transfer_encoding = meta_data.get('content-transfer-encoding') + transfer_encoding = meta_data.get("content-transfer-encoding") if transfer_encoding is not None: transfer_encoding = transfer_encoding[0].strip() - field_name = force_str(field_name, encoding, errors='replace') + field_name = force_str(field_name, encoding, errors="replace") if item_type == FIELD: # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FIELDS. num_post_keys += 1 - if (settings.DATA_UPLOAD_MAX_NUMBER_FIELDS is not None and - settings.DATA_UPLOAD_MAX_NUMBER_FIELDS < num_post_keys): + if ( + settings.DATA_UPLOAD_MAX_NUMBER_FIELDS is not None + and settings.DATA_UPLOAD_MAX_NUMBER_FIELDS < num_post_keys + ): raise TooManyFieldsSent( - 'The number of GET/POST parameters exceeded ' - 'settings.DATA_UPLOAD_MAX_NUMBER_FIELDS.' + "The number of GET/POST parameters exceeded " + "settings.DATA_UPLOAD_MAX_NUMBER_FIELDS." ) # Avoid reading more than DATA_UPLOAD_MAX_MEMORY_SIZE. if settings.DATA_UPLOAD_MAX_MEMORY_SIZE is not None: - read_size = settings.DATA_UPLOAD_MAX_MEMORY_SIZE - num_bytes_read + read_size = ( + settings.DATA_UPLOAD_MAX_MEMORY_SIZE - num_bytes_read + ) # This is a post field, we can just set it in the post - if transfer_encoding == 'base64': + if transfer_encoding == "base64": raw_data = field_stream.read(size=read_size) num_bytes_read += len(raw_data) try: @@ -202,26 +213,34 @@ class MultiPartParser: # Add two here to make the check consistent with the # x-www-form-urlencoded check that includes '&='. num_bytes_read += len(field_name) + 2 - if (settings.DATA_UPLOAD_MAX_MEMORY_SIZE is not None and - num_bytes_read > settings.DATA_UPLOAD_MAX_MEMORY_SIZE): - raise RequestDataTooBig('Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE.') + if ( + settings.DATA_UPLOAD_MAX_MEMORY_SIZE is not None + and num_bytes_read > settings.DATA_UPLOAD_MAX_MEMORY_SIZE + ): + raise RequestDataTooBig( + "Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE." + ) - self._post.appendlist(field_name, force_str(data, encoding, errors='replace')) + self._post.appendlist( + field_name, force_str(data, encoding, errors="replace") + ) elif item_type == FILE: # This is a file, use the handler... - file_name = disposition.get('filename') + file_name = disposition.get("filename") if file_name: - file_name = force_str(file_name, encoding, errors='replace') + file_name = force_str(file_name, encoding, errors="replace") file_name = self.sanitize_file_name(file_name) if not file_name: continue - content_type, content_type_extra = meta_data.get('content-type', ('', {})) + content_type, content_type_extra = meta_data.get( + "content-type", ("", {}) + ) content_type = content_type.strip() - charset = content_type_extra.get('charset') + charset = content_type_extra.get("charset") try: - content_length = int(meta_data.get('content-length')[0]) + content_length = int(meta_data.get("content-length")[0]) except (IndexError, TypeError, ValueError): content_length = None @@ -231,14 +250,18 @@ class MultiPartParser: for handler in handlers: try: handler.new_file( - field_name, file_name, content_type, - content_length, charset, content_type_extra, + field_name, + file_name, + content_type, + content_length, + charset, + content_type_extra, ) except StopFutureHandlers: break for chunk in field_stream: - if transfer_encoding == 'base64': + if transfer_encoding == "base64": # We only special-case base64 transfer encoding # We should always decode base64 chunks by multiple of 4, # ignoring whitespace. @@ -257,7 +280,9 @@ class MultiPartParser: chunk = base64.b64decode(stripped_chunk) except Exception as exc: # Since this is only a chunk, any error is an unfixable error. - raise MultiPartParserError("Could not decode base64 data.") from exc + raise MultiPartParserError( + "Could not decode base64 data." + ) from exc for i, handler in enumerate(handlers): chunk_length = len(chunk) @@ -303,7 +328,10 @@ class MultiPartParser: file_obj = handler.file_complete(counters[i]) if file_obj: # If it returns a file object, then set the files dict. - self._files.appendlist(force_str(old_field_name, self._encoding, errors='replace'), file_obj) + self._files.appendlist( + force_str(old_field_name, self._encoding, errors="replace"), + file_obj, + ) break def sanitize_file_name(self, file_name): @@ -320,12 +348,12 @@ class MultiPartParser: resulting filename should still be considered as untrusted user input. """ file_name = html.unescape(file_name) - file_name = file_name.rsplit('/')[-1] - file_name = file_name.rsplit('\\')[-1] + file_name = file_name.rsplit("/")[-1] + file_name = file_name.rsplit("\\")[-1] # Remove non-printable characters. - file_name = ''.join([char for char in file_name if char.isprintable()]) + file_name = "".join([char for char in file_name if char.isprintable()]) - if file_name in {'', '.', '..'}: + if file_name in {"", ".", ".."}: return None return file_name @@ -336,7 +364,7 @@ class MultiPartParser: # FIXME: this currently assumes that upload handlers store the file as 'file' # We should document that... (Maybe add handler.free_file to complement new_file) for handler in self._upload_handlers: - if hasattr(handler, 'file'): + if hasattr(handler, "file"): handler.file.close() @@ -348,6 +376,7 @@ class LazyStream: LazyStream object will support iteration, reading, and keeping a "look-back" variable in case you need to "unget" some bytes. """ + def __init__(self, producer, length=None): """ Every LazyStream must have a producer when instantiated. @@ -357,7 +386,7 @@ class LazyStream: """ self._producer = producer self._empty = False - self._leftover = b'' + self._leftover = b"" self.length = length self.position = 0 self._remaining = length @@ -371,14 +400,14 @@ class LazyStream: remaining = self._remaining if size is None else size # do the whole thing in one shot if no limit was provided. if remaining is None: - yield b''.join(self) + yield b"".join(self) return # otherwise do some bookkeeping to return exactly enough # of the stream and stashing any extra content we get from # the producer while remaining != 0: - assert remaining > 0, 'remaining bytes to read should never go negative' + assert remaining > 0, "remaining bytes to read should never go negative" try: chunk = next(self) @@ -390,7 +419,7 @@ class LazyStream: remaining -= len(emitting) yield emitting - return b''.join(parts()) + return b"".join(parts()) def __next__(self): """ @@ -401,7 +430,7 @@ class LazyStream: """ if self._leftover: output = self._leftover - self._leftover = b'' + self._leftover = b"" else: output = next(self._producer) self._unget_history = [] @@ -442,10 +471,13 @@ class LazyStream: maliciously-malformed MIME request. """ self._unget_history = [num_bytes] + self._unget_history[:49] - number_equal = len([ - current_number for current_number in self._unget_history - if current_number == num_bytes - ]) + number_equal = len( + [ + current_number + for current_number in self._unget_history + if current_number == num_bytes + ] + ) if number_equal > 40: raise SuspiciousMultipartForm( @@ -460,6 +492,7 @@ class ChunkIter: An iterable that will yield chunks of data. Given a file-like object as the constructor, yield chunks of read operations from that object. """ + def __init__(self, flo, chunk_size=64 * 1024): self.flo = flo self.chunk_size = chunk_size @@ -482,6 +515,7 @@ class InterBoundaryIter: """ A Producer that will iterate over boundaries. """ + def __init__(self, stream, boundary): self._stream = stream self._boundary = boundary @@ -548,7 +582,7 @@ class BoundaryIter: if not chunks: raise StopIteration() - chunk = b''.join(chunks) + chunk = b"".join(chunks) boundary = self._find_boundary(chunk) if boundary: @@ -584,10 +618,10 @@ class BoundaryIter: next = index + len(self._boundary) # backup over CRLF last = max(0, end - 1) - if data[last:last + 1] == b'\n': + if data[last : last + 1] == b"\n": end -= 1 last = max(0, end - 1) - if data[last:last + 1] == b'\r': + if data[last : last + 1] == b"\r": end -= 1 return end, next @@ -613,12 +647,12 @@ def parse_boundary_stream(stream, max_header_size): # 'find' returns the top of these four bytes, so we'll # need to munch them later to prevent them from polluting # the payload. - header_end = chunk.find(b'\r\n\r\n') + header_end = chunk.find(b"\r\n\r\n") def _parse_header(line): main_value_pair, params = parse_header(line) try: - name, value = main_value_pair.split(':', 1) + name, value = main_value_pair.split(":", 1) except ValueError: raise ValueError("Invalid header: %r" % line) return name, (value, params) @@ -633,13 +667,13 @@ def parse_boundary_stream(stream, max_header_size): # here we place any excess chunk back onto the stream, as # well as throwing away the CRLFCRLF bytes from above. - stream.unget(chunk[header_end + 4:]) + stream.unget(chunk[header_end + 4 :]) TYPE = RAW outdict = {} # Eliminate blank lines - for line in header.split(b'\r\n'): + for line in header.split(b"\r\n"): # This terminology ("main value" and "dictionary of # parameters") is from the Python docs. try: @@ -647,9 +681,9 @@ def parse_boundary_stream(stream, max_header_size): except ValueError: continue - if name == 'content-disposition': + if name == "content-disposition": TYPE = FIELD - if params.get('filename'): + if params.get("filename"): TYPE = FILE outdict[name] = value, params @@ -663,7 +697,7 @@ def parse_boundary_stream(stream, max_header_size): class Parser: def __init__(self, stream, boundary): self._stream = stream - self._separator = b'--' + boundary + self._separator = b"--" + boundary def __iter__(self): boundarystream = InterBoundaryIter(self._stream, self._separator) @@ -679,24 +713,24 @@ def parse_header(line): Input (line): bytes, output: str for key/name, bytes for values which will be decoded later. """ - plist = _parse_header_params(b';' + line) - key = plist.pop(0).lower().decode('ascii') + plist = _parse_header_params(b";" + line) + key = plist.pop(0).lower().decode("ascii") pdict = {} for p in plist: - i = p.find(b'=') + i = p.find(b"=") if i >= 0: has_encoding = False - name = p[:i].strip().lower().decode('ascii') - if name.endswith('*'): + name = p[:i].strip().lower().decode("ascii") + if name.endswith("*"): # Lang/encoding embedded in the value (like "filename*=UTF-8''file.ext") # https://tools.ietf.org/html/rfc2231#section-4 name = name[:-1] if p.count(b"'") == 2: has_encoding = True - value = p[i + 1:].strip() + value = p[i + 1 :].strip() if len(value) >= 2 and value[:1] == value[-1:] == b'"': value = value[1:-1] - value = value.replace(b'\\\\', b'\\').replace(b'\\"', b'"') + value = value.replace(b"\\\\", b"\\").replace(b'\\"', b'"') if has_encoding: encoding, lang, value = value.split(b"'") value = unquote(value.decode(), encoding=encoding.decode()) @@ -706,11 +740,11 @@ def parse_header(line): def _parse_header_params(s): plist = [] - while s[:1] == b';': + while s[:1] == b";": s = s[1:] - end = s.find(b';') + end = s.find(b";") while end > 0 and s.count(b'"', 0, end) % 2: - end = s.find(b';', end + 1) + end = s.find(b";", end + 1) if end < 0: end = len(s) f = s[:end] diff --git a/django/http/request.py b/django/http/request.py index 5971203261..d975aadf25 100644 --- a/django/http/request.py +++ b/django/http/request.py @@ -8,12 +8,17 @@ from urllib.parse import parse_qsl, quote, urlencode, urljoin, urlsplit from django.conf import settings from django.core import signing from django.core.exceptions import ( - DisallowedHost, ImproperlyConfigured, RequestDataTooBig, TooManyFieldsSent, + DisallowedHost, + ImproperlyConfigured, + RequestDataTooBig, + TooManyFieldsSent, ) from django.core.files import uploadhandler from django.http.multipartparser import MultiPartParser, MultiPartParserError from django.utils.datastructures import ( - CaseInsensitiveMapping, ImmutableList, MultiValueDict, + CaseInsensitiveMapping, + ImmutableList, + MultiValueDict, ) from django.utils.encoding import escape_uri_path, iri_to_uri from django.utils.functional import cached_property @@ -23,7 +28,9 @@ from django.utils.regex_helper import _lazy_re_compile from .multipartparser import parse_header RAISE_ERROR = object() -host_validation_re = _lazy_re_compile(r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9\.:]+\])(:[0-9]+)?$") +host_validation_re = _lazy_re_compile( + r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9\.:]+\])(:[0-9]+)?$" +) class UnreadablePostError(OSError): @@ -36,6 +43,7 @@ class RawPostDataException(Exception): multipart/* POST data if it has been accessed via POST, FILES, etc.. """ + pass @@ -57,8 +65,8 @@ class HttpRequest: self.META = {} self.FILES = MultiValueDict() - self.path = '' - self.path_info = '' + self.path = "" + self.path_info = "" self.method = None self.resolver_match = None self.content_type = None @@ -66,8 +74,12 @@ class HttpRequest: def __repr__(self): if self.method is None or not self.get_full_path(): - return '<%s>' % self.__class__.__name__ - return '<%s: %s %r>' % (self.__class__.__name__, self.method, self.get_full_path()) + return "<%s>" % self.__class__.__name__ + return "<%s: %s %r>" % ( + self.__class__.__name__, + self.method, + self.get_full_path(), + ) @cached_property def headers(self): @@ -76,24 +88,25 @@ class HttpRequest: @cached_property def accepted_types(self): """Return a list of MediaType instances.""" - return parse_accept_header(self.headers.get('Accept', '*/*')) + return parse_accept_header(self.headers.get("Accept", "*/*")) def accepts(self, media_type): return any( - accepted_type.match(media_type) - for accepted_type in self.accepted_types + accepted_type.match(media_type) for accepted_type in self.accepted_types ) def _set_content_type_params(self, meta): """Set content_type, content_params, and encoding.""" - self.content_type, self.content_params = cgi.parse_header(meta.get('CONTENT_TYPE', '')) - if 'charset' in self.content_params: + self.content_type, self.content_params = cgi.parse_header( + meta.get("CONTENT_TYPE", "") + ) + if "charset" in self.content_params: try: - codecs.lookup(self.content_params['charset']) + codecs.lookup(self.content_params["charset"]) except LookupError: pass else: - self.encoding = self.content_params['charset'] + self.encoding = self.content_params["charset"] def _get_raw_host(self): """ @@ -101,17 +114,16 @@ class HttpRequest: allowed hosts protection, so may return an insecure host. """ # We try three options, in order of decreasing preference. - if settings.USE_X_FORWARDED_HOST and ( - 'HTTP_X_FORWARDED_HOST' in self.META): - host = self.META['HTTP_X_FORWARDED_HOST'] - elif 'HTTP_HOST' in self.META: - host = self.META['HTTP_HOST'] + if settings.USE_X_FORWARDED_HOST and ("HTTP_X_FORWARDED_HOST" in self.META): + host = self.META["HTTP_X_FORWARDED_HOST"] + elif "HTTP_HOST" in self.META: + host = self.META["HTTP_HOST"] else: # Reconstruct the host using the algorithm from PEP 333. - host = self.META['SERVER_NAME'] + host = self.META["SERVER_NAME"] server_port = self.get_port() - if server_port != ('443' if self.is_secure() else '80'): - host = '%s:%s' % (host, server_port) + if server_port != ("443" if self.is_secure() else "80"): + host = "%s:%s" % (host, server_port) return host def get_host(self): @@ -121,7 +133,7 @@ class HttpRequest: # Allow variants of localhost if ALLOWED_HOSTS is empty and DEBUG=True. allowed_hosts = settings.ALLOWED_HOSTS if settings.DEBUG and not allowed_hosts: - allowed_hosts = ['.localhost', '127.0.0.1', '[::1]'] + allowed_hosts = [".localhost", "127.0.0.1", "[::1]"] domain, port = split_domain_port(host) if domain and validate_host(domain, allowed_hosts): @@ -131,15 +143,17 @@ class HttpRequest: if domain: msg += " You may need to add %r to ALLOWED_HOSTS." % domain else: - msg += " The domain name provided is not valid according to RFC 1034/1035." + msg += ( + " The domain name provided is not valid according to RFC 1034/1035." + ) raise DisallowedHost(msg) def get_port(self): """Return the port number for the request as a string.""" - if settings.USE_X_FORWARDED_PORT and 'HTTP_X_FORWARDED_PORT' in self.META: - port = self.META['HTTP_X_FORWARDED_PORT'] + if settings.USE_X_FORWARDED_PORT and "HTTP_X_FORWARDED_PORT" in self.META: + port = self.META["HTTP_X_FORWARDED_PORT"] else: - port = self.META['SERVER_PORT'] + port = self.META["SERVER_PORT"] return str(port) def get_full_path(self, force_append_slash=False): @@ -151,13 +165,15 @@ class HttpRequest: def _get_full_path(self, path, force_append_slash): # RFC 3986 requires query string arguments to be in the ASCII range. # Rather than crash if this doesn't happen, we encode defensively. - return '%s%s%s' % ( + return "%s%s%s" % ( escape_uri_path(path), - '/' if force_append_slash and not path.endswith('/') else '', - ('?' + iri_to_uri(self.META.get('QUERY_STRING', ''))) if self.META.get('QUERY_STRING', '') else '' + "/" if force_append_slash and not path.endswith("/") else "", + ("?" + iri_to_uri(self.META.get("QUERY_STRING", ""))) + if self.META.get("QUERY_STRING", "") + else "", ) - def get_signed_cookie(self, key, default=RAISE_ERROR, salt='', max_age=None): + def get_signed_cookie(self, key, default=RAISE_ERROR, salt="", max_age=None): """ Attempt to return a signed cookie. If the signature fails or the cookie has expired, raise an exception, unless the `default` argument @@ -172,7 +188,8 @@ class HttpRequest: raise try: value = signing.get_cookie_signer(salt=key + salt).unsign( - cookie_value, max_age=max_age) + cookie_value, max_age=max_age + ) except signing.BadSignature: if default is not RAISE_ERROR: return default @@ -192,7 +209,7 @@ class HttpRequest: if location is None: # Make it an absolute url (but schemeless and domainless) for the # edge case that the path starts with '//'. - location = '//%s' % self.get_full_path() + location = "//%s" % self.get_full_path() else: # Coerce lazy locations. location = str(location) @@ -201,12 +218,17 @@ class HttpRequest: # Handle the simple, most common case. If the location is absolute # and a scheme or host (netloc) isn't provided, skip an expensive # urljoin() as long as no path segments are '.' or '..'. - if (bits.path.startswith('/') and not bits.scheme and not bits.netloc and - '/./' not in bits.path and '/../' not in bits.path): + if ( + bits.path.startswith("/") + and not bits.scheme + and not bits.netloc + and "/./" not in bits.path + and "/../" not in bits.path + ): # If location starts with '//' but has no netloc, reuse the # schema and netloc from the current request. Strip the double # slashes and continue as if it wasn't specified. - if location.startswith('//'): + if location.startswith("//"): location = location[2:] location = self._current_scheme_host + location else: @@ -218,14 +240,14 @@ class HttpRequest: @cached_property def _current_scheme_host(self): - return '{}://{}'.format(self.scheme, self.get_host()) + return "{}://{}".format(self.scheme, self.get_host()) def _get_scheme(self): """ Hook for subclasses like WSGIRequest to implement. Return 'http' by default. """ - return 'http' + return "http" @property def scheme(self): @@ -234,15 +256,15 @@ class HttpRequest: header, secure_value = settings.SECURE_PROXY_SSL_HEADER except ValueError: raise ImproperlyConfigured( - 'The SECURE_PROXY_SSL_HEADER setting must be a tuple containing two values.' + "The SECURE_PROXY_SSL_HEADER setting must be a tuple containing two values." ) header_value = self.META.get(header) if header_value is not None: - return 'https' if header_value == secure_value else 'http' + return "https" if header_value == secure_value else "http" return self._get_scheme() def is_secure(self): - return self.scheme == 'https' + return self.scheme == "https" @property def encoding(self): @@ -256,14 +278,16 @@ class HttpRequest: next access (so that it is decoded correctly). """ self._encoding = val - if hasattr(self, 'GET'): + if hasattr(self, "GET"): del self.GET - if hasattr(self, '_post'): + if hasattr(self, "_post"): del self._post def _initialize_handlers(self): - self._upload_handlers = [uploadhandler.load_handler(handler, self) - for handler in settings.FILE_UPLOAD_HANDLERS] + self._upload_handlers = [ + uploadhandler.load_handler(handler, self) + for handler in settings.FILE_UPLOAD_HANDLERS + ] @property def upload_handlers(self): @@ -274,29 +298,38 @@ class HttpRequest: @upload_handlers.setter def upload_handlers(self, upload_handlers): - if hasattr(self, '_files'): - raise AttributeError("You cannot set the upload handlers after the upload has been processed.") + if hasattr(self, "_files"): + raise AttributeError( + "You cannot set the upload handlers after the upload has been processed." + ) self._upload_handlers = upload_handlers def parse_file_upload(self, META, post_data): """Return a tuple of (POST QueryDict, FILES MultiValueDict).""" self.upload_handlers = ImmutableList( self.upload_handlers, - warning="You cannot alter upload handlers after the upload has been processed." + warning="You cannot alter upload handlers after the upload has been processed.", ) parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding) return parser.parse() @property def body(self): - if not hasattr(self, '_body'): + if not hasattr(self, "_body"): if self._read_started: - raise RawPostDataException("You cannot access body after reading from request's data stream") + raise RawPostDataException( + "You cannot access body after reading from request's data stream" + ) # Limit the maximum request data size that will be handled in-memory. - if (settings.DATA_UPLOAD_MAX_MEMORY_SIZE is not None and - int(self.META.get('CONTENT_LENGTH') or 0) > settings.DATA_UPLOAD_MAX_MEMORY_SIZE): - raise RequestDataTooBig('Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE.') + if ( + settings.DATA_UPLOAD_MAX_MEMORY_SIZE is not None + and int(self.META.get("CONTENT_LENGTH") or 0) + > settings.DATA_UPLOAD_MAX_MEMORY_SIZE + ): + raise RequestDataTooBig( + "Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE." + ) try: self._body = self.read() @@ -311,15 +344,18 @@ class HttpRequest: def _load_post_and_files(self): """Populate self._post and self._files if the content-type is a form type""" - if self.method != 'POST': - self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict() + if self.method != "POST": + self._post, self._files = ( + QueryDict(encoding=self._encoding), + MultiValueDict(), + ) return - if self._read_started and not hasattr(self, '_body'): + if self._read_started and not hasattr(self, "_body"): self._mark_post_parse_error() return - if self.content_type == 'multipart/form-data': - if hasattr(self, '_body'): + if self.content_type == "multipart/form-data": + if hasattr(self, "_body"): # Use already read data data = BytesIO(self._body) else: @@ -333,13 +369,19 @@ class HttpRequest: # attempts to parse POST data again. self._mark_post_parse_error() raise - elif self.content_type == 'application/x-www-form-urlencoded': - self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict() + elif self.content_type == "application/x-www-form-urlencoded": + self._post, self._files = ( + QueryDict(self.body, encoding=self._encoding), + MultiValueDict(), + ) else: - self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict() + self._post, self._files = ( + QueryDict(encoding=self._encoding), + MultiValueDict(), + ) def close(self): - if hasattr(self, '_files'): + if hasattr(self, "_files"): for f in chain.from_iterable(list_[1] for list_ in self._files.lists()): f.close() @@ -366,16 +408,16 @@ class HttpRequest: raise UnreadablePostError(*e.args) from e def __iter__(self): - return iter(self.readline, b'') + return iter(self.readline, b"") def readlines(self): return list(self) class HttpHeaders(CaseInsensitiveMapping): - HTTP_PREFIX = 'HTTP_' + HTTP_PREFIX = "HTTP_" # PEP 333 gives two headers which aren't prepended with HTTP_. - UNPREFIXED_HEADERS = {'CONTENT_TYPE', 'CONTENT_LENGTH'} + UNPREFIXED_HEADERS = {"CONTENT_TYPE", "CONTENT_LENGTH"} def __init__(self, environ): headers = {} @@ -387,15 +429,15 @@ class HttpHeaders(CaseInsensitiveMapping): def __getitem__(self, key): """Allow header lookup using underscores in place of hyphens.""" - return super().__getitem__(key.replace('_', '-')) + return super().__getitem__(key.replace("_", "-")) @classmethod def parse_header_name(cls, header): if header.startswith(cls.HTTP_PREFIX): - header = header[len(cls.HTTP_PREFIX):] + header = header[len(cls.HTTP_PREFIX) :] elif header not in cls.UNPREFIXED_HEADERS: return None - return header.replace('_', '-').title() + return header.replace("_", "-").title() class QueryDict(MultiValueDict): @@ -421,11 +463,11 @@ class QueryDict(MultiValueDict): def __init__(self, query_string=None, mutable=False, encoding=None): super().__init__() self.encoding = encoding or settings.DEFAULT_CHARSET - query_string = query_string or '' + query_string = query_string or "" parse_qsl_kwargs = { - 'keep_blank_values': True, - 'encoding': self.encoding, - 'max_num_fields': settings.DATA_UPLOAD_MAX_NUMBER_FIELDS, + "keep_blank_values": True, + "encoding": self.encoding, + "max_num_fields": settings.DATA_UPLOAD_MAX_NUMBER_FIELDS, } if isinstance(query_string, bytes): # query_string normally contains URL-encoded data, a subset of ASCII. @@ -433,7 +475,7 @@ class QueryDict(MultiValueDict): query_string = query_string.decode(self.encoding) except UnicodeDecodeError: # ... but some user agents are misbehaving :-( - query_string = query_string.decode('iso-8859-1') + query_string = query_string.decode("iso-8859-1") try: for key, value in parse_qsl(query_string, **parse_qsl_kwargs): self.appendlist(key, value) @@ -443,18 +485,18 @@ class QueryDict(MultiValueDict): # the exception was raised by exceeding the value of max_num_fields # instead of fragile checks of exception message strings. raise TooManyFieldsSent( - 'The number of GET/POST parameters exceeded ' - 'settings.DATA_UPLOAD_MAX_NUMBER_FIELDS.' + "The number of GET/POST parameters exceeded " + "settings.DATA_UPLOAD_MAX_NUMBER_FIELDS." ) from e self._mutable = mutable @classmethod - def fromkeys(cls, iterable, value='', mutable=False, encoding=None): + def fromkeys(cls, iterable, value="", mutable=False, encoding=None): """ Return a new QueryDict with keys (may be repeated) from an iterable and values from value. """ - q = cls('', mutable=True, encoding=encoding) + q = cls("", mutable=True, encoding=encoding) for key in iterable: q.appendlist(key, value) if not mutable: @@ -486,13 +528,13 @@ class QueryDict(MultiValueDict): super().__delitem__(key) def __copy__(self): - result = self.__class__('', mutable=True, encoding=self.encoding) + result = self.__class__("", mutable=True, encoding=self.encoding) for key, value in self.lists(): result.setlist(key, value) return result def __deepcopy__(self, memo): - result = self.__class__('', mutable=True, encoding=self.encoding) + result = self.__class__("", mutable=True, encoding=self.encoding) memo[id(self)] = result for key, value in self.lists(): result.setlist(copy.deepcopy(key, memo), copy.deepcopy(value, memo)) @@ -554,48 +596,50 @@ class QueryDict(MultiValueDict): safe = safe.encode(self.encoding) def encode(k, v): - return '%s=%s' % ((quote(k, safe), quote(v, safe))) + return "%s=%s" % ((quote(k, safe), quote(v, safe))) + else: + def encode(k, v): return urlencode({k: v}) + for k, list_ in self.lists(): output.extend( encode(k.encode(self.encoding), str(v).encode(self.encoding)) for v in list_ ) - return '&'.join(output) + return "&".join(output) class MediaType: def __init__(self, media_type_raw_line): full_type, self.params = parse_header( - media_type_raw_line.encode('ascii') if media_type_raw_line else b'' + media_type_raw_line.encode("ascii") if media_type_raw_line else b"" ) - self.main_type, _, self.sub_type = full_type.partition('/') + self.main_type, _, self.sub_type = full_type.partition("/") def __str__(self): - params_str = ''.join( - '; %s=%s' % (k, v.decode('ascii')) - for k, v in self.params.items() + params_str = "".join( + "; %s=%s" % (k, v.decode("ascii")) for k, v in self.params.items() ) - return '%s%s%s' % ( + return "%s%s%s" % ( self.main_type, - ('/%s' % self.sub_type) if self.sub_type else '', + ("/%s" % self.sub_type) if self.sub_type else "", params_str, ) def __repr__(self): - return '<%s: %s>' % (self.__class__.__qualname__, self) + return "<%s: %s>" % (self.__class__.__qualname__, self) @property def is_all_types(self): - return self.main_type == '*' and self.sub_type == '*' + return self.main_type == "*" and self.sub_type == "*" def match(self, other): if self.is_all_types: return True other = MediaType(other) - if self.main_type == other.main_type and self.sub_type in {'*', other.sub_type}: + if self.main_type == other.main_type and self.sub_type in {"*", other.sub_type}: return True return False @@ -612,7 +656,7 @@ def bytes_to_text(s, encoding): Return any non-bytes objects without change. """ if isinstance(s, bytes): - return str(s, encoding, 'replace') + return str(s, encoding, "replace") else: return s @@ -627,15 +671,15 @@ def split_domain_port(host): host = host.lower() if not host_validation_re.match(host): - return '', '' + return "", "" - if host[-1] == ']': + if host[-1] == "]": # It's an IPv6 address without a port. - return host, '' - bits = host.rsplit(':', 1) - domain, port = bits if len(bits) == 2 else (bits[0], '') + return host, "" + bits = host.rsplit(":", 1) + domain, port = bits if len(bits) == 2 else (bits[0], "") # Remove a trailing dot (if present) from the domain. - domain = domain[:-1] if domain.endswith('.') else domain + domain = domain[:-1] if domain.endswith(".") else domain return domain, port @@ -654,8 +698,10 @@ def validate_host(host, allowed_hosts): Return ``True`` for a valid host, ``False`` otherwise. """ - return any(pattern == '*' or is_same_domain(host, pattern) for pattern in allowed_hosts) + return any( + pattern == "*" or is_same_domain(host, pattern) for pattern in allowed_hosts + ) def parse_accept_header(header): - return [MediaType(token) for token in header.split(',') if token.strip()] + return [MediaType(token) for token in header.split(",") if token.strip()] diff --git a/django/http/response.py b/django/http/response.py index 847d824191..e40f2d169b 100644 --- a/django/http/response.py +++ b/django/http/response.py @@ -21,7 +21,9 @@ from django.utils.encoding import iri_to_uri from django.utils.http import http_date from django.utils.regex_helper import _lazy_re_compile -_charset_from_content_type_re = _lazy_re_compile(r';\s*charset=(?P<charset>[^\s;]+)', re.I) +_charset_from_content_type_re = _lazy_re_compile( + r";\s*charset=(?P<charset>[^\s;]+)", re.I +) class ResponseHeaders(CaseInsensitiveMapping): @@ -42,11 +44,12 @@ class ResponseHeaders(CaseInsensitiveMapping): """ if not isinstance(value, (bytes, str)): value = str(value) - if ( - (isinstance(value, bytes) and (b'\n' in value or b'\r' in value)) or - (isinstance(value, str) and ('\n' in value or '\r' in value)) + if (isinstance(value, bytes) and (b"\n" in value or b"\r" in value)) or ( + isinstance(value, str) and ("\n" in value or "\r" in value) ): - raise BadHeaderError("Header values can't contain newlines (got %r)" % value) + raise BadHeaderError( + "Header values can't contain newlines (got %r)" % value + ) try: if isinstance(value, str): # Ensure string is valid in given charset @@ -56,9 +59,9 @@ class ResponseHeaders(CaseInsensitiveMapping): value = value.decode(charset) except UnicodeError as e: if mime_encode: - value = Header(value, 'utf-8', maxlinelen=sys.maxsize).encode() + value = Header(value, "utf-8", maxlinelen=sys.maxsize).encode() else: - e.reason += ', HTTP response headers must be in %s format' % charset + e.reason += ", HTTP response headers must be in %s format" % charset raise return value @@ -66,8 +69,8 @@ class ResponseHeaders(CaseInsensitiveMapping): self.pop(key) def __setitem__(self, key, value): - key = self._convert_to_charset(key, 'ascii') - value = self._convert_to_charset(value, 'latin-1', mime_encode=True) + key = self._convert_to_charset(key, "ascii") + value = self._convert_to_charset(value, "latin-1", mime_encode=True) self._store[key.lower()] = (key, value) def pop(self, key, default=None): @@ -92,18 +95,20 @@ class HttpResponseBase: status_code = 200 - def __init__(self, content_type=None, status=None, reason=None, charset=None, headers=None): + def __init__( + self, content_type=None, status=None, reason=None, charset=None, headers=None + ): self.headers = ResponseHeaders(headers or {}) self._charset = charset - if content_type and 'Content-Type' in self.headers: + if content_type and "Content-Type" in self.headers: raise ValueError( "'headers' must not contain 'Content-Type' when the " "'content_type' parameter is provided." ) - if 'Content-Type' not in self.headers: + if "Content-Type" not in self.headers: if content_type is None: - content_type = 'text/html; charset=%s' % self.charset - self.headers['Content-Type'] = content_type + content_type = "text/html; charset=%s" % self.charset + self.headers["Content-Type"] = content_type self._resource_closers = [] # This parameter is set by the handler. It's necessary to preserve the # historical behavior of request_finished. @@ -114,10 +119,10 @@ class HttpResponseBase: try: self.status_code = int(status) except (ValueError, TypeError): - raise TypeError('HTTP status code must be an integer.') + raise TypeError("HTTP status code must be an integer.") if not 100 <= self.status_code <= 599: - raise ValueError('HTTP status code must be an integer from 100 to 599.') + raise ValueError("HTTP status code must be an integer from 100 to 599.") self._reason_phrase = reason @property @@ -126,7 +131,7 @@ class HttpResponseBase: return self._reason_phrase # Leave self._reason_phrase unset in order to use the default # reason phrase for status code. - return responses.get(self.status_code, 'Unknown Status Code') + return responses.get(self.status_code, "Unknown Status Code") @reason_phrase.setter def reason_phrase(self, value): @@ -136,11 +141,11 @@ class HttpResponseBase: def charset(self): if self._charset is not None: return self._charset - content_type = self.get('Content-Type', '') + content_type = self.get("Content-Type", "") matched = _charset_from_content_type_re.search(content_type) if matched: # Extract the charset and strip its double quotes - return matched['charset'].replace('"', '') + return matched["charset"].replace('"', "") return settings.DEFAULT_CHARSET @charset.setter @@ -149,16 +154,22 @@ class HttpResponseBase: def serialize_headers(self): """HTTP headers as a bytestring.""" - return b'\r\n'.join([ - key.encode('ascii') + b': ' + value.encode('latin-1') - for key, value in self.headers.items() - ]) + return b"\r\n".join( + [ + key.encode("ascii") + b": " + value.encode("latin-1") + for key, value in self.headers.items() + ] + ) __bytes__ = serialize_headers @property def _content_type_for_repr(self): - return ', "%s"' % self.headers['Content-Type'] if 'Content-Type' in self.headers else '' + return ( + ', "%s"' % self.headers["Content-Type"] + if "Content-Type" in self.headers + else "" + ) def __setitem__(self, header, value): self.headers[header] = value @@ -181,8 +192,18 @@ class HttpResponseBase: def get(self, header, alternate=None): return self.headers.get(header, alternate) - def set_cookie(self, key, value='', max_age=None, expires=None, path='/', - domain=None, secure=False, httponly=False, samesite=None): + def set_cookie( + self, + key, + value="", + max_age=None, + expires=None, + path="/", + domain=None, + secure=False, + httponly=False, + samesite=None, + ): """ Set a cookie. @@ -206,47 +227,51 @@ class HttpResponseBase: expires = None max_age = max(0, delta.days * 86400 + delta.seconds) else: - self.cookies[key]['expires'] = expires + self.cookies[key]["expires"] = expires else: - self.cookies[key]['expires'] = '' + self.cookies[key]["expires"] = "" if max_age is not None: - self.cookies[key]['max-age'] = int(max_age) + self.cookies[key]["max-age"] = int(max_age) # IE requires expires, so set it if hasn't been already. if not expires: - self.cookies[key]['expires'] = http_date(time.time() + max_age) + self.cookies[key]["expires"] = http_date(time.time() + max_age) if path is not None: - self.cookies[key]['path'] = path + self.cookies[key]["path"] = path if domain is not None: - self.cookies[key]['domain'] = domain + self.cookies[key]["domain"] = domain if secure: - self.cookies[key]['secure'] = True + self.cookies[key]["secure"] = True if httponly: - self.cookies[key]['httponly'] = True + self.cookies[key]["httponly"] = True if samesite: - if samesite.lower() not in ('lax', 'none', 'strict'): + if samesite.lower() not in ("lax", "none", "strict"): raise ValueError('samesite must be "lax", "none", or "strict".') - self.cookies[key]['samesite'] = samesite + self.cookies[key]["samesite"] = samesite def setdefault(self, key, value): """Set a header unless it has already been set.""" self.headers.setdefault(key, value) - def set_signed_cookie(self, key, value, salt='', **kwargs): + def set_signed_cookie(self, key, value, salt="", **kwargs): value = signing.get_cookie_signer(salt=key + salt).sign(value) return self.set_cookie(key, value, **kwargs) - def delete_cookie(self, key, path='/', domain=None, samesite=None): + def delete_cookie(self, key, path="/", domain=None, samesite=None): # Browsers can ignore the Set-Cookie header if the cookie doesn't use # the secure flag and: # - the cookie name starts with "__Host-" or "__Secure-", or # - the samesite is "none". - secure = ( - key.startswith(('__Secure-', '__Host-')) or - (samesite and samesite.lower() == 'none') + secure = key.startswith(("__Secure-", "__Host-")) or ( + samesite and samesite.lower() == "none" ) self.set_cookie( - key, max_age=0, path=path, domain=domain, secure=secure, - expires='Thu, 01 Jan 1970 00:00:00 GMT', samesite=samesite, + key, + max_age=0, + path=path, + domain=domain, + secure=secure, + expires="Thu, 01 Jan 1970 00:00:00 GMT", + samesite=samesite, ) # Common methods used by subclasses @@ -284,13 +309,15 @@ class HttpResponseBase: signals.request_finished.send(sender=self._handler_class) def write(self, content): - raise OSError('This %s instance is not writable' % self.__class__.__name__) + raise OSError("This %s instance is not writable" % self.__class__.__name__) def flush(self): pass def tell(self): - raise OSError('This %s instance cannot tell its position' % self.__class__.__name__) + raise OSError( + "This %s instance cannot tell its position" % self.__class__.__name__ + ) # These methods partially implement a stream-like object interface. # See https://docs.python.org/library/io.html#io.IOBase @@ -305,7 +332,7 @@ class HttpResponseBase: return False def writelines(self, lines): - raise OSError('This %s instance is not writable' % self.__class__.__name__) + raise OSError("This %s instance is not writable" % self.__class__.__name__) class HttpResponse(HttpResponseBase): @@ -317,37 +344,36 @@ class HttpResponse(HttpResponseBase): streaming = False - def __init__(self, content=b'', *args, **kwargs): + def __init__(self, content=b"", *args, **kwargs): super().__init__(*args, **kwargs) # Content is a bytestring. See the `content` property methods. self.content = content def __repr__(self): - return '<%(cls)s status_code=%(status_code)d%(content_type)s>' % { - 'cls': self.__class__.__name__, - 'status_code': self.status_code, - 'content_type': self._content_type_for_repr, + return "<%(cls)s status_code=%(status_code)d%(content_type)s>" % { + "cls": self.__class__.__name__, + "status_code": self.status_code, + "content_type": self._content_type_for_repr, } def serialize(self): """Full HTTP message, including headers, as a bytestring.""" - return self.serialize_headers() + b'\r\n\r\n' + self.content + return self.serialize_headers() + b"\r\n\r\n" + self.content __bytes__ = serialize @property def content(self): - return b''.join(self._container) + return b"".join(self._container) @content.setter def content(self, value): # Consume iterators upon assignment to allow repeated iteration. - if ( - hasattr(value, '__iter__') and - not isinstance(value, (bytes, memoryview, str)) + if hasattr(value, "__iter__") and not isinstance( + value, (bytes, memoryview, str) ): - content = b''.join(self.make_bytes(chunk) for chunk in value) - if hasattr(value, 'close'): + content = b"".join(self.make_bytes(chunk) for chunk in value) + if hasattr(value, "close"): try: value.close() except Exception: @@ -395,10 +421,10 @@ class StreamingHttpResponse(HttpResponseBase): self.streaming_content = streaming_content def __repr__(self): - return '<%(cls)s status_code=%(status_code)d%(content_type)s>' % { - 'cls': self.__class__.__qualname__, - 'status_code': self.status_code, - 'content_type': self._content_type_for_repr, + return "<%(cls)s status_code=%(status_code)d%(content_type)s>" % { + "cls": self.__class__.__qualname__, + "status_code": self.status_code, + "content_type": self._content_type_for_repr, } @property @@ -419,37 +445,40 @@ class StreamingHttpResponse(HttpResponseBase): def _set_streaming_content(self, value): # Ensure we can never iterate on "value" more than once. self._iterator = iter(value) - if hasattr(value, 'close'): + if hasattr(value, "close"): self._resource_closers.append(value.close) def __iter__(self): return self.streaming_content def getvalue(self): - return b''.join(self.streaming_content) + return b"".join(self.streaming_content) class FileResponse(StreamingHttpResponse): """ A streaming HTTP response class optimized for files. """ + block_size = 4096 - def __init__(self, *args, as_attachment=False, filename='', **kwargs): + def __init__(self, *args, as_attachment=False, filename="", **kwargs): self.as_attachment = as_attachment self.filename = filename - self._no_explicit_content_type = 'content_type' not in kwargs or kwargs['content_type'] is None + self._no_explicit_content_type = ( + "content_type" not in kwargs or kwargs["content_type"] is None + ) super().__init__(*args, **kwargs) def _set_streaming_content(self, value): - if not hasattr(value, 'read'): + 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'): + if hasattr(filelike, "close"): self._resource_closers.append(filelike.close) - value = iter(lambda: filelike.read(self.block_size), b'') + value = iter(lambda: filelike.read(self.block_size), b"") self.set_headers(filelike) super()._set_streaming_content(value) @@ -458,22 +487,30 @@ class FileResponse(StreamingHttpResponse): Set some common response headers (Content-Length, Content-Type, and Content-Disposition) based on the `filelike` response content. """ - 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'): + 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 + 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 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() + 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) + self.headers["Content-Length"] = sum( + iter(lambda: len(filelike.read(self.block_size)), 0) + ) + filelike.seek(-int(self.headers["Content-Length"]), io.SEEK_END) filename = os.path.basename(self.filename or filename) if self._no_explicit_content_type: @@ -482,45 +519,54 @@ class FileResponse(StreamingHttpResponse): # Encoding isn't set to prevent browsers from automatically # uncompressing files. content_type = { - 'bzip2': 'application/x-bzip', - 'gzip': 'application/gzip', - 'xz': 'application/x-xz', + "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' + self.headers["Content-Type"] = ( + content_type or "application/octet-stream" + ) else: - self.headers['Content-Type'] = 'application/octet-stream' + self.headers["Content-Type"] = "application/octet-stream" if filename: - disposition = 'attachment' if self.as_attachment else 'inline' + disposition = "attachment" if self.as_attachment else "inline" try: - filename.encode('ascii') + filename.encode("ascii") file_expr = 'filename="{}"'.format(filename) except UnicodeEncodeError: file_expr = "filename*=utf-8''{}".format(quote(filename)) - self.headers['Content-Disposition'] = '{}; {}'.format(disposition, file_expr) + self.headers["Content-Disposition"] = "{}; {}".format( + disposition, file_expr + ) elif self.as_attachment: - self.headers['Content-Disposition'] = 'attachment' + self.headers["Content-Disposition"] = "attachment" class HttpResponseRedirectBase(HttpResponse): - allowed_schemes = ['http', 'https', 'ftp'] + allowed_schemes = ["http", "https", "ftp"] def __init__(self, redirect_to, *args, **kwargs): super().__init__(*args, **kwargs) - self['Location'] = iri_to_uri(redirect_to) + self["Location"] = iri_to_uri(redirect_to) parsed = urlparse(str(redirect_to)) if parsed.scheme and parsed.scheme not in self.allowed_schemes: - raise DisallowedRedirect("Unsafe redirect to URL with protocol '%s'" % parsed.scheme) + raise DisallowedRedirect( + "Unsafe redirect to URL with protocol '%s'" % parsed.scheme + ) - url = property(lambda self: self['Location']) + url = property(lambda self: self["Location"]) def __repr__(self): - return '<%(cls)s status_code=%(status_code)d%(content_type)s, url="%(url)s">' % { - 'cls': self.__class__.__name__, - 'status_code': self.status_code, - 'content_type': self._content_type_for_repr, - 'url': self.url, - } + return ( + '<%(cls)s status_code=%(status_code)d%(content_type)s, url="%(url)s">' + % { + "cls": self.__class__.__name__, + "status_code": self.status_code, + "content_type": self._content_type_for_repr, + "url": self.url, + } + ) class HttpResponseRedirect(HttpResponseRedirectBase): @@ -536,12 +582,14 @@ class HttpResponseNotModified(HttpResponse): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - del self['content-type'] + del self["content-type"] @HttpResponse.content.setter def content(self, value): if value: - raise AttributeError("You cannot set content to a 304 (Not Modified) response") + raise AttributeError( + "You cannot set content to a 304 (Not Modified) response" + ) self._container = [] @@ -562,14 +610,14 @@ class HttpResponseNotAllowed(HttpResponse): def __init__(self, permitted_methods, *args, **kwargs): super().__init__(*args, **kwargs) - self['Allow'] = ', '.join(permitted_methods) + self["Allow"] = ", ".join(permitted_methods) def __repr__(self): - return '<%(cls)s [%(methods)s] status_code=%(status_code)d%(content_type)s>' % { - 'cls': self.__class__.__name__, - 'status_code': self.status_code, - 'content_type': self._content_type_for_repr, - 'methods': self['Allow'], + return "<%(cls)s [%(methods)s] status_code=%(status_code)d%(content_type)s>" % { + "cls": self.__class__.__name__, + "status_code": self.status_code, + "content_type": self._content_type_for_repr, + "methods": self["Allow"], } @@ -599,15 +647,21 @@ class JsonResponse(HttpResponse): :param json_dumps_params: A dictionary of kwargs passed to json.dumps(). """ - def __init__(self, data, encoder=DjangoJSONEncoder, safe=True, - json_dumps_params=None, **kwargs): + def __init__( + self, + data, + encoder=DjangoJSONEncoder, + safe=True, + json_dumps_params=None, + **kwargs, + ): if safe and not isinstance(data, dict): raise TypeError( - 'In order to allow non-dict objects to be serialized set the ' - 'safe parameter to False.' + "In order to allow non-dict objects to be serialized set the " + "safe parameter to False." ) if json_dumps_params is None: json_dumps_params = {} - kwargs.setdefault('content_type', 'application/json') + kwargs.setdefault("content_type", "application/json") data = json.dumps(data, cls=encoder, **json_dumps_params) super().__init__(content=data, **kwargs) |
