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.
from __future__ import absolute_import
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):
"""
- Handle chunked transfer encoding.
- Do not include OS environment variables in each request's WSGI environment.
Seriously, what the fsck, python ?
"""
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)
STATUS_CREATED = _getStatus(httplib.CREATED)
STATUS_NO_CONTENT = _getStatus(httplib.NO_CONTENT)
MAX_BODY_LENGTH = 10 * 1024 * 1024 # 10 MB
MAX_CHUNKED_HEADER_LENGTH = 64 * 1024
class Application(object):
"""
......@@ -213,40 +212,13 @@ class Application(object):
Raises TooLarge if Content-Length if over MAX_BODY_LENGTH.
If Content-Length is not set, reads at most MAX_BODY_LENGTH bytes.
"""
# XXX: WSGI (...at least the reference implementation in python 2.7)
# has no way of sending "100 Continue". So ignore "Expect" header and let
# client timeout.
infile = environ['wsgi.input']
transfer_encoding = environ.get('HTTP_TRANSFER_ENCODING')
if transfer_encoding == 'chunked':
result = ''
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
try:
length = int(environ.get('CONTENT_LENGTH') or MAX_BODY_LENGTH)
except ValueError:
raise BadRequest('Invalid Content-Length')
if length > MAX_BODY_LENGTH:
raise TooLarge('Content-Length limit exceeded')
return environ['wsgi.input'].read(length)
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