Commit d1fbca1f authored by Vincent Pelletier's avatar Vincent Pelletier

wsgi: Add support for "Transfer-Encoding: chunked".

Needed by curl with --upload-file .
Curl also asks us to return "100 Continue" responses, but WSGI (as of
python 2.7 reference implementation) does not allow that. Gah.
parent cdcf1a20
...@@ -1298,12 +1298,14 @@ class CaucaseTest(unittest.TestCase): ...@@ -1298,12 +1298,14 @@ class CaucaseTest(unittest.TestCase):
'REQUEST_METHOD': 'PUT', 'REQUEST_METHOD': 'PUT',
'CONTENT_TYPE': 'application/json', 'CONTENT_TYPE': 'application/json',
'CONTENT_LENGTH': 'a', 'CONTENT_LENGTH': 'a',
'wsgi.input': StringIO(),
})[0], 400) })[0], 400)
self.assertEqual(request({ self.assertEqual(request({
'PATH_INFO': '/cau/crt/renew', 'PATH_INFO': '/cau/crt/renew',
'REQUEST_METHOD': 'PUT', 'REQUEST_METHOD': 'PUT',
'CONTENT_TYPE': 'application/json', 'CONTENT_TYPE': 'application/json',
'CONTENT_LENGTH': str(10 * 1024 * 1024 + 1), 'CONTENT_LENGTH': str(wsgi.MAX_BODY_LENGTH + 1),
'wsgi.input': StringIO(),
})[0], 413) })[0], 413)
self.assertEqual(request({ self.assertEqual(request({
'PATH_INFO': '/cau/crt/renew', 'PATH_INFO': '/cau/crt/renew',
...@@ -1317,6 +1319,43 @@ class CaucaseTest(unittest.TestCase): ...@@ -1317,6 +1319,43 @@ class CaucaseTest(unittest.TestCase):
'CONTENT_TYPE': 'application/json', 'CONTENT_TYPE': 'application/json',
'wsgi.input': StringIO('{"digest": null}'), 'wsgi.input': StringIO('{"digest": null}'),
})[0], UNAUTHORISED_STATUS) })[0], UNAUTHORISED_STATUS)
self.assertEqual(request({
'PATH_INFO': '/cau/crt/revoke',
'REQUEST_METHOD': 'PUT',
'CONTENT_TYPE': 'application/json',
'HTTP_TRANSFER_ENCODING': 'chunked',
'wsgi.input': StringIO(
'a\r\n' # lower-case hex
'{"digest":\r\n'
'5 extension must be ignored\r\n'
'null,\r\n'
'A\r\n' # upper-case hex
'"foo":"a",\r\n'
'f\r\n'
'"payload":"{}"}\r\n'
'0\r\n'
'tail must also be ignored\r\n',
),
})[0], UNAUTHORISED_STATUS)
self.assertEqual(request({
'PATH_INFO': '/cau/crt/revoke',
'REQUEST_METHOD': 'PUT',
'CONTENT_TYPE': 'application/json',
'HTTP_TRANSFER_ENCODING': 'chunked',
'wsgi.input': StringIO(
'%x\r\n' % (wsgi.MAX_BODY_LENGTH + 1, ),
),
})[0], 413)
self.assertEqual(request({
'PATH_INFO': '/cau/crt/revoke',
'REQUEST_METHOD': 'PUT',
'CONTENT_TYPE': 'application/json',
'HTTP_TRANSFER_ENCODING': 'chunked',
'wsgi.input': StringIO(
'a\r\n'
'{"digest":\r\r' # bad separator
),
})[0], 400)
self.assertEqual(request({ self.assertEqual(request({
'PATH_INFO': '/cau/crt/a', 'PATH_INFO': '/cau/crt/a',
'REQUEST_METHOD': 'PUT', 'REQUEST_METHOD': 'PUT',
......
...@@ -94,6 +94,7 @@ STATUS_OK = _getStatus(httplib.OK) ...@@ -94,6 +94,7 @@ 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):
""" """
...@@ -194,13 +195,40 @@ class Application(object): ...@@ -194,13 +195,40 @@ 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.
""" """
try: # XXX: WSGI (...at least the reference implementation in python 2.7)
length = int(environ.get('CONTENT_LENGTH', MAX_BODY_LENGTH)) # has no way of sending "100 Continue". So ignore "Expect" header and let
except ValueError: # client timeout.
raise BadRequest('Invalid Content-Length') infile = environ['wsgi.input']
if length > MAX_BODY_LENGTH: transfer_encoding = environ.get('HTTP_TRANSFER_ENCODING')
raise TooLarge('Content-Length limit exceeded') if transfer_encoding == 'chunked':
return environ['wsgi.input'].read(length) 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
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