summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2013-11-21 06:52:02 -0500
committerChris McDonough <chrism@plope.com>2013-11-21 06:52:02 -0500
commit34aa289c34e54f830c8434ff3ca237103b4ae651 (patch)
tree86c6dbf4f9a8f64d1df5b6519cfbee8a262d9229
parent992dd544e819e4ccf400a0fe4ac6d92b5204fd09 (diff)
downloadwaitress-34aa289c34e54f830c8434ff3ca237103b4ae651.tar.gz
- Fix some cases where the creation of extremely large output buffers (greater
than 2GB, suspected to be buffers added via ``wsgi.file_wrapper``) might cause an OverflowError on Python 2. See https://github.com/Pylons/waitress/issues/47. See #47
-rw-r--r--CHANGES.txt8
-rw-r--r--waitress/buffers.py16
-rw-r--r--waitress/channel.py9
-rw-r--r--waitress/tests/test_buffers.py35
-rw-r--r--waitress/tests/test_channel.py33
5 files changed, 95 insertions, 6 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 8718f1b..cb2c7b9 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,11 @@
+Next release
+------------
+
+- Fix some cases where the creation of extremely large output buffers (greater
+ than 2GB, suspected to be buffers added via ``wsgi.file_wrapper``) might
+ cause an OverflowError on Python 2. See
+ https://github.com/Pylons/waitress/issues/47.
+
0.8.7 (2013-08-29)
------------------
diff --git a/waitress/buffers.py b/waitress/buffers.py
index bb5a530..0009444 100644
--- a/waitress/buffers.py
+++ b/waitress/buffers.py
@@ -186,8 +186,8 @@ class OverflowableBuffer(object):
"""
This buffer implementation has four stages:
- No data
- - String-based buffer
- - StringIO-based buffer
+ - Bytes-based buffer
+ - BytesIO-based buffer
- Temporary file storage
The first two stages are fastest for simple transfers.
"""
@@ -203,11 +203,15 @@ class OverflowableBuffer(object):
def __len__(self):
buf = self.buf
if buf is not None:
+ # use buf.__len__ rather than len(buf) FBO of not getting
+ # OverflowError on Python 2
return buf.__len__()
else:
return self.strbuf.__len__()
def __nonzero__(self):
+ # use self.__len__ rather than len(self) FBO of not getting
+ # OverflowError on Python 2
return self.__len__() > 0
__bool__ = __nonzero__ # py3
@@ -241,7 +245,9 @@ class OverflowableBuffer(object):
return
buf = self._create_buffer()
buf.append(s)
- sz = len(buf)
+ # use buf.__len__ rather than len(buf) FBO of not getting
+ # OverflowError on Python 2
+ sz = buf.__len__()
if not self.overflowed:
if sz >= self.overflow:
self._set_large_buffer()
@@ -278,7 +284,9 @@ class OverflowableBuffer(object):
return
buf.prune()
if self.overflowed:
- sz = len(buf)
+ # use buf.__len__ rather than len(buf) FBO of not getting
+ # OverflowError on Python 2
+ sz = buf.__len__()
if sz < self.overflow:
# Revert to a faster buffer.
self._set_small_buffer()
diff --git a/waitress/channel.py b/waitress/channel.py
index 5d16c74..11ac140 100644
--- a/waitress/channel.py
+++ b/waitress/channel.py
@@ -89,7 +89,10 @@ class HTTPChannel(logging_dispatcher, object):
return False
def total_outbufs_len(self):
- return sum([len(b) for b in self.outbufs]) # genexpr == more funccalls
+ # genexpr == more funccalls
+ # use b.__len__ rather than len(b) FBO of not getting OverflowError
+ # on Python 2
+ return sum([b.__len__() for b in self.outbufs])
def writable(self):
# if there's data in the out buffer or we've been instructed to close
@@ -233,7 +236,9 @@ class HTTPChannel(logging_dispatcher, object):
while True:
outbuf = self.outbufs[0]
- outbuflen = len(outbuf)
+ # use outbuf.__len__ rather than len(outbuf) FBO of not getting
+ # OverflowError on Python 2
+ outbuflen = outbuf.__len__()
if outbuflen <= 0:
# self.outbufs[-1] must always be a writable outbuf
if len(self.outbufs) > 1:
diff --git a/waitress/tests/test_buffers.py b/waitress/tests/test_buffers.py
index b4ed7eb..10d58f9 100644
--- a/waitress/tests/test_buffers.py
+++ b/waitress/tests/test_buffers.py
@@ -284,6 +284,17 @@ class TestOverflowableBuffer(unittest.TestCase):
self.assertEqual(inst.buf.get(100), b'x' * 5)
self.assertEqual(inst.strbuf, b'')
+ def test_append_with_len_more_than_max_int(self):
+ import sys
+ inst = self._makeOne()
+ inst.overflowed = True
+ buf = DummyBuffer(length=sys.maxint)
+ inst.buf = buf
+ result = inst.append(b'x')
+ # we don't want this to throw an OverflowError on Python 2 (see
+ # https://github.com/Pylons/waitress/issues/47)
+ self.assertEqual(result, None)
+
def test_append_buf_None_not_longer_than_srtbuf_limit(self):
inst = self._makeOne()
inst.strbuf = b'x' * 5
@@ -373,6 +384,17 @@ class TestOverflowableBuffer(unittest.TestCase):
inst.prune()
self.assertNotEqual(inst.buf, buf)
+ def test_prune_with_buflen_more_than_max_int(self):
+ import sys
+ inst = self._makeOne()
+ inst.overflowed = True
+ buf = DummyBuffer(length=sys.maxint+1)
+ inst.buf = buf
+ result = inst.prune()
+ # we don't want this to throw an OverflowError on Python 2 (see
+ # https://github.com/Pylons/waitress/issues/47)
+ self.assertEqual(result, None)
+
def test_getfile_buf_None(self):
inst = self._makeOne()
f = inst.getfile()
@@ -417,3 +439,16 @@ class Filelike(KindaFilelike):
def tell(self):
v = self.tellresults.pop(0)
return v
+
+class DummyBuffer(object):
+ def __init__(self, length=0):
+ self.length = length
+
+ def __len__(self):
+ return self.length
+
+ def append(self, s):
+ self.length = self.length + len(s)
+
+ def prune(self):
+ pass
diff --git a/waitress/tests/test_channel.py b/waitress/tests/test_channel.py
index 33286c2..85c4d1f 100644
--- a/waitress/tests/test_channel.py
+++ b/waitress/tests/test_channel.py
@@ -22,6 +22,18 @@ class TestHTTPChannel(unittest.TestCase):
self.assertEqual(inst.addr, '127.0.0.1')
self.assertEqual(map[100], inst)
+ def test_total_outbufs_len_an_outbuf_size_gt_sys_maxint(self):
+ import sys
+ inst, _, map = self._makeOneWithMap()
+ class DummyHugeBuffer(object):
+ def __len__(self):
+ return sys.maxint + 1
+ inst.outbufs = [DummyHugeBuffer()]
+ result = inst.total_outbufs_len()
+ # we are testing that this method does not raise an OverflowError
+ # (see https://github.com/Pylons/waitress/issues/47)
+ self.assertEqual(result, sys.maxint+1)
+
def test_writable_something_in_outbuf(self):
inst, sock, map = self._makeOneWithMap()
inst.outbufs[0].append(b'abc')
@@ -256,6 +268,27 @@ class TestHTTPChannel(unittest.TestCase):
self.assertEqual(inst.outbufs, [buffer])
self.assertEqual(len(inst.logger.exceptions), 1)
+ def test__flush_some_outbuf_len_gt_sys_maxint(self):
+ import sys
+ inst, sock, map = self._makeOneWithMap()
+ class DummyHugeOutbuffer(object):
+ def __init__(self):
+ self.length = sys.maxint + 1
+ def __len__(self):
+ return self.length
+ def get(self, numbytes):
+ self.length = 0
+ return b'123'
+ def skip(self, *args):
+ pass
+ buf = DummyHugeOutbuffer()
+ inst.outbufs = [buf]
+ inst.send = lambda *arg: 0
+ result = inst._flush_some()
+ # we are testing that _flush_some doesn't raise an OverflowError
+ # when one of its outbufs has a __len__ that returns gt sys.maxint
+ self.assertEqual(result, False)
+
def test_handle_close(self):
inst, sock, map = self._makeOneWithMap()
inst.handle_close()