diff --git a/python_multipart/multipart.py b/python_multipart/multipart.py index 49cdd8e..ed990c7 100644 --- a/python_multipart/multipart.py +++ b/python_multipart/multipart.py @@ -714,7 +714,7 @@ def __init__(self, callbacks: OctetStreamCallbacks = {}, max_size: float = float self.callbacks = callbacks self._started = False - if not isinstance(max_size, Number) or max_size < 1: + if isinstance(max_size, bool) or not isinstance(max_size, Number) or max_size < 1: raise ValueError("max_size must be a positive number, not %r" % max_size) self.max_size: int | float = max_size self._current_size = 0 @@ -796,7 +796,7 @@ def __init__( self.callbacks = callbacks # Max-size stuff - if not isinstance(max_size, Number) or max_size < 1: + if isinstance(max_size, bool) or not isinstance(max_size, Number) or max_size < 1: raise ValueError("max_size must be a positive number, not %r" % max_size) self.max_size: int | float = max_size self._current_size = 0 @@ -1049,11 +1049,20 @@ def __init__( self.callbacks = callbacks - if not isinstance(max_size, Number) or max_size < 1: + if isinstance(max_size, bool) or not isinstance(max_size, Number) or max_size < 1: raise ValueError("max_size must be a positive number, not %r" % max_size) self.max_size = max_size self._current_size = 0 + # bool subclasses int; True would silently become count/size 1 + if isinstance(max_header_count, bool) or not isinstance(max_header_count, int): + raise ValueError( + "max_header_count must be an int, not %r" % max_header_count + ) + if isinstance(max_header_size, bool) or not isinstance(max_header_size, int): + raise ValueError( + "max_header_size must be an int, not %r" % max_header_size + ) self.max_header_count = max_header_count self._current_header_count = 0 diff --git a/tests/test_multipart.py b/tests/test_multipart.py index 949d70c..8d665c5 100644 --- a/tests/test_multipart.py +++ b/tests/test_multipart.py @@ -512,6 +512,12 @@ def test_invalid_max_size(self) -> None: with self.assertRaises(ValueError): p = QuerystringParser(max_size=-100) + def test_rejects_bool_max_size(self) -> None: + """bool is a Number; max_size=True must not silently become 1.""" + for value in (True, False): + with self.assertRaises(ValueError): + QuerystringParser(max_size=value) + def test_strict_parsing_pass(self) -> None: data = b"foo=bar&another=asdf" for first, last in split_all(data): @@ -639,6 +645,12 @@ def test_invalid_max_size(self) -> None: with self.assertRaises(ValueError): q = OctetStreamParser(max_size="foo") # type: ignore[arg-type] + def test_rejects_bool_max_size_octet(self) -> None: + """bool is a Number; max_size=True must not silently become 1.""" + for value in (True, False): + with self.assertRaises(ValueError): + OctetStreamParser(max_size=value) + class TestBase64Decoder(unittest.TestCase): # Note: base64('foobar') == 'Zm9vYmFy' @@ -1592,6 +1604,21 @@ def test_invalid_max_size_multipart(self) -> None: with self.assertRaises(ValueError): MultipartParser(b"bound", max_size="foo") # type: ignore[arg-type] + def test_rejects_bool_max_size_and_header_limits(self) -> None: + """bool subclasses Number/int; True must not silently become size/count 1.""" + for value in (True, False): + with self.assertRaises(ValueError): + MultipartParser(b"bound", max_size=value) + with self.assertRaises(ValueError): + MultipartParser(b"bound", max_header_count=value) + with self.assertRaises(ValueError): + MultipartParser(b"bound", max_header_size=value) + # valid ints still accepted + p = MultipartParser(b"bound", max_size=100, max_header_count=4, max_header_size=256) + assert p.max_size == 100 + assert p.max_header_count == 4 + assert p.max_header_size == 256 + def test_boundary_too_long(self) -> None: with self.assertRaisesRegex(FormParserError, "Boundary length 257 exceeds maximum of 256"): MultipartParser(b"x" * 257)