Commit acc11f04 authored by Vincent Pelletier's avatar Vincent Pelletier

http_wsgibase, wsgi: Move request transfer encoding handling out of wsgi.

As per WSGI specs, transfer encoding (and other hop-by-hop headers) must
not be processed by WSGI applications.
parent 74e34f58
...@@ -23,9 +23,77 @@ Separate from .http because of different-licensed code in the middle. ...@@ -23,9 +23,77 @@ Separate from .http because of different-licensed code in the middle.
from __future__ import absolute_import from __future__ import absolute_import
from wsgiref.simple_server import ServerHandler from wsgiref.simple_server import ServerHandler
class ProxyFile(object):
"""
Passes any non-overridden calls to the actual_file object.
"""
def __init__(self, actual_file):
self._actual_file = actual_file
def __getattr__(self, name):
return getattr(self._actual_file, name)
MAX_CHUNKED_HEADER_LENGTH = 64 * 1024
class ChunkedFile(ProxyFile):
"""
Implement chunked-encoding.
"""
_at_eof = False
def __init__(self, actual_file):
super(ChunkedFile, self).__init__(actual_file)
self._recv_buf = ''
def read(self, length=None):
result = self._recv_buf
if not self._at_eof:
readline = self.readline
read = self.__getattr__('read')
while length is None or len(result) < length:
chunk_header = readline(MAX_CHUNKED_HEADER_LENGTH + 1)
if len(chunk_header) > MAX_CHUNKED_HEADER_LENGTH:
raise ValueError('Chunked encoding header too long')
try:
chunk_length = int(chunk_header.split(' ', 1)[0], 16)
except ValueError:
raise ValueError('Invalid chunked encoding header')
if not chunk_length:
trailer = readline(MAX_CHUNKED_HEADER_LENGTH + 1)
if len(trailer) > MAX_CHUNKED_HEADER_LENGTH:
raise ValueError('Chunked encoding trailer too long')
self._at_eof = True
break
result += read(chunk_length)
if read(2) != '\r\n':
raise ValueError('Invalid chunked encoding separator')
if length is None:
self._recv_buf = ''
return result
self._recv_buf = result[length:]
return result[:length]
class CleanServerHandler(ServerHandler): class CleanServerHandler(ServerHandler):
""" """
- Handle chunked transfer encoding.
- Do not include OS environment variables in each request's WSGI environment. - Do not include OS environment variables in each request's WSGI environment.
Seriously, what the fsck, python ? Seriously, what the fsck, python ?
""" """
os_environ = {} os_environ = {}
def setup_environ(self):
ServerHandler.setup_environ(self)
environ = self.environ
try:
request_major, request_minor = environ[
'SERVER_PROTOCOL'
].upper().split('/', 1)[1].split('.', 1)
request_version = (int(request_major), int(request_minor))
except (KeyError, ValueError):
request_version = (0, 9)
if request_version > (1, 0):
if environ.get('HTTP_TRANSFER_ENCODING', '').lower() == 'chunked':
# XXX: does not support multiple encodings specified at once
# (ex: chunked + gzip).
# We handle this, hide it from Application
del environ['HTTP_TRANSFER_ENCODING']
environ['wsgi.input'] = ChunkedFile(environ['wsgi.input'])
...@@ -98,7 +98,6 @@ STATUS_OK = _getStatus(httplib.OK) ...@@ -98,7 +98,6 @@ STATUS_OK = _getStatus(httplib.OK)
STATUS_CREATED = _getStatus(httplib.CREATED) STATUS_CREATED = _getStatus(httplib.CREATED)
STATUS_NO_CONTENT = _getStatus(httplib.NO_CONTENT) STATUS_NO_CONTENT = _getStatus(httplib.NO_CONTENT)
MAX_BODY_LENGTH = 10 * 1024 * 1024 # 10 MB MAX_BODY_LENGTH = 10 * 1024 * 1024 # 10 MB
MAX_CHUNKED_HEADER_LENGTH = 64 * 1024
class Application(object): class Application(object):
""" """
...@@ -213,40 +212,13 @@ class Application(object): ...@@ -213,40 +212,13 @@ class Application(object):
Raises TooLarge if Content-Length if over MAX_BODY_LENGTH. Raises TooLarge if Content-Length if over MAX_BODY_LENGTH.
If Content-Length is not set, reads at most MAX_BODY_LENGTH bytes. If Content-Length is not set, reads at most MAX_BODY_LENGTH bytes.
""" """
# XXX: WSGI (...at least the reference implementation in python 2.7) try:
# has no way of sending "100 Continue". So ignore "Expect" header and let length = int(environ.get('CONTENT_LENGTH') or MAX_BODY_LENGTH)
# client timeout. except ValueError:
infile = environ['wsgi.input'] raise BadRequest('Invalid Content-Length')
transfer_encoding = environ.get('HTTP_TRANSFER_ENCODING') if length > MAX_BODY_LENGTH:
if transfer_encoding == 'chunked': raise TooLarge('Content-Length limit exceeded')
result = '' return environ['wsgi.input'].read(length)
while True:
chunk_header = infile.readline(MAX_CHUNKED_HEADER_LENGTH + 1)
if len(chunk_header) > MAX_CHUNKED_HEADER_LENGTH:
raise BadRequest('Chunked encoding header too long')
try:
length = int(chunk_header.split(' ', 1)[0], 16)
except ValueError:
raise BadRequest('Invalid chunked encoding header')
if not length:
trailer = infile.readline(MAX_CHUNKED_HEADER_LENGTH + 1)
if len(trailer) > MAX_CHUNKED_HEADER_LENGTH:
raise BadRequest('Chunked encoding trailer too long')
break
if len(result) + length > MAX_BODY_LENGTH:
raise TooLarge('Content-Length limit exceeded')
result += infile.read(length)
if infile.read(2) != '\r\n':
raise BadRequest('Invalid chunked encoding separator')
else: # Assume identity
try:
length = int(environ.get('CONTENT_LENGTH', MAX_BODY_LENGTH))
except ValueError:
raise BadRequest('Invalid Content-Length')
if length > MAX_BODY_LENGTH:
raise TooLarge('Content-Length limit exceeded')
result = infile.read(length)
return result
def _authenticate(self, environ, header_list): def _authenticate(self, environ, header_list):
""" """
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment