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:
@@ -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()
|
||||
|
@@ -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")
|
||||
|
Reference in New Issue
Block a user