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))
|
BROKEN_SOCK = set((errno.EPIPE, errno.ECONNRESET))
|
||||||
|
|
||||||
|
|
||||||
|
class ChunkReadError(ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# special flag return value for apps
|
# special flag return value for apps
|
||||||
class _AlreadyHandled(object):
|
class _AlreadyHandled(object):
|
||||||
|
|
||||||
@@ -176,7 +180,10 @@ class Input(object):
|
|||||||
if use_readline and data[-1] == "\n":
|
if use_readline and data[-1] == "\n":
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
self.chunk_length = int(rfile.readline().split(b";", 1)[0], 16)
|
self.chunk_length = int(rfile.readline().split(b";", 1)[0], 16)
|
||||||
|
except ValueError as err:
|
||||||
|
raise ChunkReadError(err)
|
||||||
self.position = 0
|
self.position = 0
|
||||||
if self.chunk_length == 0:
|
if self.chunk_length == 0:
|
||||||
rfile.readline()
|
rfile.readline()
|
||||||
|
@@ -1319,6 +1319,70 @@ class TestHttpd(_TestBase):
|
|||||||
self.assertEqual(read_content.wait(), b'ok')
|
self.assertEqual(read_content.wait(), b'ok')
|
||||||
assert blew_up[0]
|
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 test_exceptions_close_connection(self):
|
||||||
def wsgi_app(environ, start_response):
|
def wsgi_app(environ, start_response):
|
||||||
raise RuntimeError("intentional error")
|
raise RuntimeError("intentional error")
|
||||||
|
Reference in New Issue
Block a user