wsgi: better error for chunk read failures

When a client is sending a chunked request body and disconnects
between chunks, wsgi.Input.read() raises a ValueError. That's fine if
you can remember to wrap every single call to read/readline in a
try/except, but it's bad for a higher-level error handler because so
many things raise ValueError.

This adds in a new error class wsgi.ChunkReadError and raises that
instead. It inherits from ValueError so anyone catching ValueError
should continue to work.
This commit is contained in:
Samuel Merritt
2015-08-10 13:49:26 -05:00
parent 32305974e2
commit c3ce3eef0b
2 changed files with 72 additions and 1 deletions

View File

@@ -50,6 +50,10 @@ BAD_SOCK = set((errno.EBADF, 10053))
BROKEN_SOCK = set((errno.EPIPE, errno.ECONNRESET))
class ChunkReadError(ValueError):
pass
# special flag return value for apps
class _AlreadyHandled(object):
@@ -176,7 +180,10 @@ class Input(object):
if use_readline and data[-1] == "\n":
break
else:
self.chunk_length = int(rfile.readline().split(b";", 1)[0], 16)
try:
self.chunk_length = int(rfile.readline().split(b";", 1)[0], 16)
except ValueError as err:
raise ChunkReadError(err)
self.position = 0
if self.chunk_length == 0:
rfile.readline()

View File

@@ -1319,6 +1319,70 @@ class TestHttpd(_TestBase):
self.assertEqual(read_content.wait(), b'ok')
assert blew_up[0]
def test_aborted_chunked_post_between_chunks(self):
read_content = event.Event()
blew_up = [False]
def chunk_reader(env, start_response):
try:
content = env['wsgi.input'].read(1024)
except wsgi.ChunkReadError:
blew_up[0] = True
content = b'ok'
except Exception as err:
blew_up[0] = True
content = b'wrong exception: ' + str(err).encode()
read_content.send(content)
start_response('200 OK', [('Content-Type', 'text/plain')])
return [content]
self.site.application = chunk_reader
expected_body = 'A' * 0xdb
data = "\r\n".join(['PUT /somefile HTTP/1.0',
'Transfer-Encoding: chunked',
'',
'db',
expected_body])
# start PUT-ing some chunked data but close prematurely
sock = eventlet.connect(('127.0.0.1', self.port))
sock.sendall(data.encode())
sock.close()
# the test passes if we successfully get here, and read all the data
# in spite of the early close
self.assertEqual(read_content.wait(), b'ok')
assert blew_up[0]
def test_aborted_chunked_post_bad_chunks(self):
read_content = event.Event()
blew_up = [False]
def chunk_reader(env, start_response):
try:
content = env['wsgi.input'].read(1024)
except wsgi.ChunkReadError:
blew_up[0] = True
content = b'ok'
except Exception as err:
blew_up[0] = True
content = b'wrong exception: ' + str(err).encode()
read_content.send(content)
start_response('200 OK', [('Content-Type', 'text/plain')])
return [content]
self.site.application = chunk_reader
expected_body = 'look here is some data for you'
data = "\r\n".join(['PUT /somefile HTTP/1.0',
'Transfer-Encoding: chunked',
'',
'cats',
expected_body])
# start PUT-ing some garbage
sock = eventlet.connect(('127.0.0.1', self.port))
sock.sendall(data.encode())
sock.close()
# the test passes if we successfully get here, and read all the data
# in spite of the early close
self.assertEqual(read_content.wait(), b'ok')
assert blew_up[0]
def test_exceptions_close_connection(self):
def wsgi_app(environ, start_response):
raise RuntimeError("intentional error")