2012-05-06 01:47:03 +08:00
|
|
|
from io import BytesIO
|
2007-09-28 01:01:34 +08:00
|
|
|
|
2013-09-07 22:25:51 +08:00
|
|
|
from django.conf import settings
|
2006-05-02 09:31:56 +08:00
|
|
|
from django.core import signals
|
2008-07-21 15:57:10 +08:00
|
|
|
from django.core.handlers import base
|
2017-02-25 14:48:20 +08:00
|
|
|
from django.http import HttpRequest, QueryDict, parse_cookie
|
2015-12-30 23:51:16 +08:00
|
|
|
from django.urls import set_script_prefix
|
2017-03-04 22:47:49 +08:00
|
|
|
from django.utils.encoding import repercent_broken_unicode
|
2016-08-12 21:31:18 +08:00
|
|
|
from django.utils.functional import cached_property
|
2019-10-26 22:42:32 +08:00
|
|
|
from django.utils.regex_helper import _lazy_re_compile
|
2010-10-04 23:12:39 +08:00
|
|
|
|
2019-10-26 22:42:32 +08:00
|
|
|
_slashes_re = _lazy_re_compile(br'/+')
|
2015-10-23 20:53:32 +08:00
|
|
|
|
2005-07-23 02:12:05 +08:00
|
|
|
|
2017-01-19 15:39:46 +08:00
|
|
|
class LimitedStream:
|
2017-01-26 03:02:33 +08:00
|
|
|
"""Wrap another stream to disallow reading it past a number of bytes."""
|
2010-10-30 00:39:25 +08:00
|
|
|
def __init__(self, stream, limit, buf_size=64 * 1024 * 1024):
|
|
|
|
self.stream = stream
|
|
|
|
self.remaining = limit
|
2012-05-19 23:43:34 +08:00
|
|
|
self.buffer = b''
|
2010-10-30 00:39:25 +08:00
|
|
|
self.buf_size = buf_size
|
|
|
|
|
|
|
|
def _read_limited(self, size=None):
|
|
|
|
if size is None or size > self.remaining:
|
|
|
|
size = self.remaining
|
|
|
|
if size == 0:
|
2012-05-19 23:43:34 +08:00
|
|
|
return b''
|
2010-10-30 00:39:25 +08:00
|
|
|
result = self.stream.read(size)
|
|
|
|
self.remaining -= len(result)
|
|
|
|
return result
|
|
|
|
|
|
|
|
def read(self, size=None):
|
|
|
|
if size is None:
|
|
|
|
result = self.buffer + self._read_limited()
|
2012-05-19 23:43:34 +08:00
|
|
|
self.buffer = b''
|
2010-10-30 00:39:25 +08:00
|
|
|
elif size < len(self.buffer):
|
|
|
|
result = self.buffer[:size]
|
|
|
|
self.buffer = self.buffer[size:]
|
2013-11-03 05:02:56 +08:00
|
|
|
else: # size >= len(self.buffer)
|
2010-10-30 00:39:25 +08:00
|
|
|
result = self.buffer + self._read_limited(size - len(self.buffer))
|
2012-05-19 23:43:34 +08:00
|
|
|
self.buffer = b''
|
2010-10-30 00:39:25 +08:00
|
|
|
return result
|
|
|
|
|
|
|
|
def readline(self, size=None):
|
2012-05-19 23:43:34 +08:00
|
|
|
while b'\n' not in self.buffer and \
|
2011-01-16 15:31:35 +08:00
|
|
|
(size is None or len(self.buffer) < size):
|
2010-10-30 00:39:25 +08:00
|
|
|
if size:
|
2011-01-16 15:31:35 +08:00
|
|
|
# since size is not None here, len(self.buffer) < size
|
2010-10-30 00:39:25 +08:00
|
|
|
chunk = self._read_limited(size - len(self.buffer))
|
|
|
|
else:
|
|
|
|
chunk = self._read_limited()
|
|
|
|
if not chunk:
|
|
|
|
break
|
|
|
|
self.buffer += chunk
|
2012-05-06 01:47:03 +08:00
|
|
|
sio = BytesIO(self.buffer)
|
2010-10-30 00:39:25 +08:00
|
|
|
if size:
|
|
|
|
line = sio.readline(size)
|
|
|
|
else:
|
|
|
|
line = sio.readline()
|
|
|
|
self.buffer = sio.read()
|
|
|
|
return line
|
2006-09-23 21:53:02 +08:00
|
|
|
|
2011-10-22 12:30:10 +08:00
|
|
|
|
2017-02-25 14:48:20 +08:00
|
|
|
class WSGIRequest(HttpRequest):
|
2005-07-18 14:30:26 +08:00
|
|
|
def __init__(self, environ):
|
2013-09-07 22:25:51 +08:00
|
|
|
script_name = get_script_name(environ)
|
2017-11-06 23:23:29 +08:00
|
|
|
# If PATH_INFO is empty (e.g. accessing the SCRIPT_NAME URL without a
|
|
|
|
# trailing slash), operate as if '/' was requested.
|
|
|
|
path_info = get_path_info(environ) or '/'
|
2005-07-18 14:30:26 +08:00
|
|
|
self.environ = environ
|
2008-07-21 15:57:10 +08:00
|
|
|
self.path_info = path_info
|
2013-11-04 07:34:11 +08:00
|
|
|
# be careful to only replace the first slash in the path because of
|
|
|
|
# http://test/something and http://test//something being different as
|
2018-09-26 14:48:47 +08:00
|
|
|
# stated in https://www.ietf.org/rfc/rfc2396.txt
|
2013-11-04 07:34:11 +08:00
|
|
|
self.path = '%s/%s' % (script_name.rstrip('/'),
|
|
|
|
path_info.replace('/', '', 1))
|
2006-09-28 09:56:02 +08:00
|
|
|
self.META = environ
|
2008-07-21 15:57:10 +08:00
|
|
|
self.META['PATH_INFO'] = path_info
|
|
|
|
self.META['SCRIPT_NAME'] = script_name
|
2006-06-20 11:48:31 +08:00
|
|
|
self.method = environ['REQUEST_METHOD'].upper()
|
2019-06-13 16:11:41 +08:00
|
|
|
# Set content_type, content_params, and encoding.
|
|
|
|
self._set_content_type_params(environ)
|
2011-06-28 18:17:56 +08:00
|
|
|
try:
|
2013-09-07 23:25:16 +08:00
|
|
|
content_length = int(environ.get('CONTENT_LENGTH'))
|
2011-06-28 18:17:56 +08:00
|
|
|
except (ValueError, TypeError):
|
|
|
|
content_length = 0
|
|
|
|
self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
|
2010-10-30 00:39:25 +08:00
|
|
|
self._read_started = False
|
2013-06-26 15:36:25 +08:00
|
|
|
self.resolver_match = None
|
2005-07-18 14:30:26 +08:00
|
|
|
|
2013-10-09 02:30:29 +08:00
|
|
|
def _get_scheme(self):
|
|
|
|
return self.environ.get('wsgi.url_scheme')
|
2006-07-22 00:20:22 +08:00
|
|
|
|
2013-12-24 18:57:57 +08:00
|
|
|
@cached_property
|
|
|
|
def GET(self):
|
|
|
|
# The WSGI spec says 'QUERY_STRING' may be absent.
|
|
|
|
raw_query_string = get_bytes_from_wsgi(self.environ, 'QUERY_STRING', '')
|
2017-02-25 14:48:20 +08:00
|
|
|
return QueryDict(raw_query_string, encoding=self._encoding)
|
2005-07-18 14:30:26 +08:00
|
|
|
|
|
|
|
def _get_post(self):
|
|
|
|
if not hasattr(self, '_post'):
|
|
|
|
self._load_post_and_files()
|
|
|
|
return self._post
|
|
|
|
|
|
|
|
def _set_post(self, post):
|
|
|
|
self._post = post
|
|
|
|
|
2013-12-24 18:57:57 +08:00
|
|
|
@cached_property
|
|
|
|
def COOKIES(self):
|
|
|
|
raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '')
|
2017-02-25 14:48:20 +08:00
|
|
|
return parse_cookie(raw_cookie)
|
2005-07-18 14:30:26 +08:00
|
|
|
|
2016-08-26 08:06:22 +08:00
|
|
|
@property
|
|
|
|
def FILES(self):
|
2005-07-18 14:30:26 +08:00
|
|
|
if not hasattr(self, '_files'):
|
|
|
|
self._load_post_and_files()
|
|
|
|
return self._files
|
|
|
|
|
|
|
|
POST = property(_get_post, _set_post)
|
|
|
|
|
2011-10-22 12:30:10 +08:00
|
|
|
|
2008-07-21 15:57:10 +08:00
|
|
|
class WSGIHandler(base.BaseHandler):
|
2007-10-20 15:42:34 +08:00
|
|
|
request_class = WSGIRequest
|
2007-08-12 18:24:05 +08:00
|
|
|
|
2016-04-03 21:43:51 +08:00
|
|
|
def __init__(self, *args, **kwargs):
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(*args, **kwargs)
|
2016-04-03 21:43:51 +08:00
|
|
|
self.load_middleware()
|
2005-07-18 14:30:26 +08:00
|
|
|
|
2016-04-03 21:43:51 +08:00
|
|
|
def __call__(self, environ, start_response):
|
2013-09-07 22:25:51 +08:00
|
|
|
set_script_prefix(get_script_name(environ))
|
2013-11-21 09:03:02 +08:00
|
|
|
signals.request_started.send(sender=self.__class__, environ=environ)
|
2016-08-12 21:31:18 +08:00
|
|
|
request = self.request_class(environ)
|
|
|
|
response = self.get_response(request)
|
2012-12-30 22:19:22 +08:00
|
|
|
|
|
|
|
response._handler_class = self.__class__
|
2006-02-20 12:36:17 +08:00
|
|
|
|
2016-04-27 01:43:34 +08:00
|
|
|
status = '%d %s' % (response.status_code, response.reason_phrase)
|
2018-09-28 21:57:12 +08:00
|
|
|
response_headers = [
|
|
|
|
*response.items(),
|
|
|
|
*(('Set-Cookie', c.output(header='')) for c in response.cookies.values()),
|
|
|
|
]
|
2017-01-12 06:17:25 +08:00
|
|
|
start_response(status, response_headers)
|
2015-01-04 01:06:24 +08:00
|
|
|
if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
|
2020-02-07 19:55:59 +08:00
|
|
|
# If `wsgi.file_wrapper` is used the WSGI server does not call
|
|
|
|
# .close on the response, but on the file wrapper. Patch it to use
|
|
|
|
# response.close instead which takes care of closing all files.
|
|
|
|
response.file_to_stream.close = response.close
|
2019-07-25 01:06:01 +08:00
|
|
|
response = environ['wsgi.file_wrapper'](response.file_to_stream, response.block_size)
|
2006-09-22 20:32:00 +08:00
|
|
|
return response
|
2013-09-07 22:25:51 +08:00
|
|
|
|
|
|
|
|
|
|
|
def get_path_info(environ):
|
2017-01-26 03:02:33 +08:00
|
|
|
"""Return the HTTP request's PATH_INFO as a string."""
|
2013-09-07 23:00:50 +08:00
|
|
|
path_info = get_bytes_from_wsgi(environ, 'PATH_INFO', '/')
|
|
|
|
|
2017-02-08 01:05:47 +08:00
|
|
|
return repercent_broken_unicode(path_info).decode()
|
2013-09-07 22:25:51 +08:00
|
|
|
|
|
|
|
|
|
|
|
def get_script_name(environ):
|
|
|
|
"""
|
2017-01-26 03:02:33 +08:00
|
|
|
Return the equivalent of the HTTP request's SCRIPT_NAME environment
|
|
|
|
variable. If Apache mod_rewrite is used, return what would have been
|
2013-09-07 22:25:51 +08:00
|
|
|
the script name prior to any rewriting (so it's the script name as seen
|
|
|
|
from the client's perspective), unless the FORCE_SCRIPT_NAME setting is
|
|
|
|
set (to anything).
|
|
|
|
"""
|
|
|
|
if settings.FORCE_SCRIPT_NAME is not None:
|
2017-03-04 22:47:49 +08:00
|
|
|
return settings.FORCE_SCRIPT_NAME
|
2013-09-07 22:25:51 +08:00
|
|
|
|
|
|
|
# If Apache's mod_rewrite had a whack at the URL, Apache set either
|
|
|
|
# SCRIPT_URL or REDIRECT_URL to the full resource URL before applying any
|
|
|
|
# rewrites. Unfortunately not every Web server (lighttpd!) passes this
|
|
|
|
# information through all the time, so FORCE_SCRIPT_NAME, above, is still
|
|
|
|
# needed.
|
2018-01-04 07:52:12 +08:00
|
|
|
script_url = get_bytes_from_wsgi(environ, 'SCRIPT_URL', '') or get_bytes_from_wsgi(environ, 'REDIRECT_URL', '')
|
2013-09-07 23:00:50 +08:00
|
|
|
|
2013-09-07 22:25:51 +08:00
|
|
|
if script_url:
|
2015-10-24 17:14:17 +08:00
|
|
|
if b'//' in script_url:
|
|
|
|
# mod_wsgi squashes multiple successive slashes in PATH_INFO,
|
|
|
|
# do the same with script_url before manipulating paths (#17133).
|
|
|
|
script_url = _slashes_re.sub(b'/', script_url)
|
2013-09-07 23:00:50 +08:00
|
|
|
path_info = get_bytes_from_wsgi(environ, 'PATH_INFO', '')
|
2015-03-08 22:06:23 +08:00
|
|
|
script_name = script_url[:-len(path_info)] if path_info else script_url
|
2013-09-07 22:25:51 +08:00
|
|
|
else:
|
2013-09-07 23:00:50 +08:00
|
|
|
script_name = get_bytes_from_wsgi(environ, 'SCRIPT_NAME', '')
|
|
|
|
|
2017-02-08 01:05:47 +08:00
|
|
|
return script_name.decode()
|
2013-09-07 23:00:50 +08:00
|
|
|
|
|
|
|
|
|
|
|
def get_bytes_from_wsgi(environ, key, default):
|
|
|
|
"""
|
|
|
|
Get a value from the WSGI environ dictionary as bytes.
|
|
|
|
|
2017-01-20 17:20:53 +08:00
|
|
|
key and default should be strings.
|
2013-09-07 23:00:50 +08:00
|
|
|
"""
|
2017-01-20 17:20:53 +08:00
|
|
|
value = environ.get(key, default)
|
2016-12-01 18:38:01 +08:00
|
|
|
# Non-ASCII values in the WSGI environ are arbitrarily decoded with
|
|
|
|
# ISO-8859-1. This is wrong for Django websites where UTF-8 is the default.
|
|
|
|
# Re-encode to recover the original bytestring.
|
2017-02-08 01:05:47 +08:00
|
|
|
return value.encode('iso-8859-1')
|
2013-09-07 23:25:43 +08:00
|
|
|
|
|
|
|
|
|
|
|
def get_str_from_wsgi(environ, key, default):
|
|
|
|
"""
|
2014-07-22 20:25:22 +08:00
|
|
|
Get a value from the WSGI environ dictionary as str.
|
2013-09-07 23:25:43 +08:00
|
|
|
|
2016-12-01 18:38:01 +08:00
|
|
|
key and default should be str objects.
|
2013-09-07 23:25:43 +08:00
|
|
|
"""
|
2014-07-22 20:25:22 +08:00
|
|
|
value = get_bytes_from_wsgi(environ, key, default)
|
2017-02-08 01:05:47 +08:00
|
|
|
return value.decode(errors='replace')
|