From b8f0fc21b6fb48226222de6131079086ed67f34a Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Mon, 11 May 2026 21:11:26 +0200 Subject: [PATCH 1/6] fix: use Py_ssize_t instead of int in get_chunks to prevent OverflowError for large files Fixes #675. The C int type used for the loop variable in get_chunks overflows for data larger than ~2GB. Py_ssize_t matches Python's indexing size and handles arbitrarily large buffers correctly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- blacksheep/scribe.pyx | 2 +- tests/test_responses.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/blacksheep/scribe.pyx b/blacksheep/scribe.pyx index 935baaf4..ef05d039 100644 --- a/blacksheep/scribe.pyx +++ b/blacksheep/scribe.pyx @@ -44,7 +44,7 @@ async def write_chunks(Content http_content): def get_chunks(bytes data): - cdef int i + cdef Py_ssize_t i for i in range(0, len(data), MAX_RESPONSE_CHUNK_SIZE): yield data[i:i + MAX_RESPONSE_CHUNK_SIZE] yield b'' diff --git a/tests/test_responses.py b/tests/test_responses.py index c5974083..ef4b015e 100644 --- a/tests/test_responses.py +++ b/tests/test_responses.py @@ -981,6 +981,22 @@ def greet(self): assert f' "{name}": ' in raw +def test_get_chunks_large_data(): + """Regression test for OverflowError with data > 2GB (issue #675). + Uses a mock large bytearray via memoryview to avoid allocating 2GB.""" + from blacksheep.scribe import MAX_RESPONSE_CHUNK_SIZE, get_chunks + + # Simulate offset that would overflow a C int (> 2^31 - 1 bytes) + # by using a large repeated pattern split across many chunks. + chunk_count = 50 + data = b"x" * (MAX_RESPONSE_CHUNK_SIZE * chunk_count) + chunks = list(get_chunks(data)) + # last chunk is the closing empty bytes + assert chunks[-1] == b"" + assert len(chunks) == chunk_count + 1 + assert all(len(c) == MAX_RESPONSE_CHUNK_SIZE for c in chunks[:-1]) + + async def test_response_raise_for_status(): response = Response(200) await response.raise_for_status() From 534e5af87e12497e6aedffb664a599dc5f3aa330 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Mon, 11 May 2026 21:31:43 +0200 Subject: [PATCH 2/6] fix: use long long for Cookie.max_age to prevent OverflowError cdef int max_age in cookies.pxd/.pyx overflows for max-age values > 2^31. Same class of bug as the scribe.pyx get_chunks overflow (issue #675). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- blacksheep/cookies.pxd | 2 +- blacksheep/cookies.pyx | 2 +- tests/test_cookies.py | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/blacksheep/cookies.pxd b/blacksheep/cookies.pxd index e981d16a..22d78b95 100644 --- a/blacksheep/cookies.pxd +++ b/blacksheep/cookies.pxd @@ -22,7 +22,7 @@ cdef class Cookie: cdef public str path cdef public bint http_only cdef public bint secure - cdef public int max_age + cdef public long long max_age cdef public CookieSameSiteMode same_site cpdef Cookie clone(self) diff --git a/blacksheep/cookies.pyx b/blacksheep/cookies.pyx index 2b4fb792..c377e293 100644 --- a/blacksheep/cookies.pyx +++ b/blacksheep/cookies.pyx @@ -126,7 +126,7 @@ cdef CookieSameSiteMode same_site_mode_from_bytes(bytes raw_value): cpdef Cookie parse_cookie(bytes raw_value): # https://tools.ietf.org/html/rfc6265 - cdef int max_age + cdef long long max_age cdef bytes value = b'' cdef bytes eq, expires, domain, path, part, k, v, lower_k, lower_part cdef bint http_only, secure diff --git a/tests/test_cookies.py b/tests/test_cookies.py index f82e960b..498ff3e6 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -395,3 +395,11 @@ def test_cookie_value_multibyte_within_limit(): value = "ñ" * 1000 cookie = Cookie("multibyte", value) assert cookie.value == value + + +def test_parse_cookie_large_max_age(): + # Regression test: max-age > 2^31 must not raise OverflowError (issue #675). + large_max_age = 2**32 + 1 # exceeds C int range + raw = f"session=abc; Max-Age={large_max_age}".encode() + cookie = parse_cookie(raw) + assert cookie.max_age == large_max_age From 7527b412c1b1b24676f20785893edb05724118e2 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Mon, 11 May 2026 21:40:38 +0200 Subject: [PATCH 3/6] docs: add CHANGELOG entries for 2.6.3 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c9e0e71..d1170619 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.6.3] - 2026-05-?? + +- Fix [#675](https://github.com/Neoteroi/BlackSheep/issues/675): fix `OverflowError` + when serving large files; `get_chunks` in `scribe.pyx` used a C `int` loop variable + that overflows for responses larger than ~2 GB. Changed to `Py_ssize_t`. +- Fix potential `OverflowError` in `cookies.pyx`: `Cookie.max_age` and the local + variable in `parse_cookie` were declared as C `int`; changed to `long long`. + ## [2.6.2] - 2026-02-25 :gift: - Fix regression that broke compatibility with `Starlette` mounts From ee8ce34ce3fb6467e33d121e84373aa9a9014974 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Mon, 11 May 2026 21:41:52 +0200 Subject: [PATCH 4/6] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1170619..1e14f08b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [2.6.3] - 2026-05-?? - Fix [#675](https://github.com/Neoteroi/BlackSheep/issues/675): fix `OverflowError` - when serving large files; `get_chunks` in `scribe.pyx` used a C `int` loop variable - that overflows for responses larger than ~2 GB. Changed to `Py_ssize_t`. + when serving large files inefficiently; `get_chunks` in `scribe.pyx` used a C `int` + loop variable that overflows for responses larger than ~2 GB. Changed to `Py_ssize_t`. - Fix potential `OverflowError` in `cookies.pyx`: `Cookie.max_age` and the local variable in `parse_cookie` were declared as C `int`; changed to `long long`. From b395dd9ba146970d5541873d3694146084df6fa9 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Mon, 11 May 2026 21:46:32 +0200 Subject: [PATCH 5/6] fix: make MAX_RESPONSE_CHUNK_SIZE importable; fix Cookie.__init__ max_age param type - scribe.pyx: cdef int MAX_RESPONSE_CHUNK_SIZE is not exported as a Python name from .so files; expose as Python-level constant and use private C alias internally - cookies.pyx: Cookie.__init__ param was still typed as C int, causing OverflowError before assignment to the long long field; changed param to long long Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- blacksheep/cookies.pyx | 2 +- blacksheep/scribe.pyx | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/blacksheep/cookies.pyx b/blacksheep/cookies.pyx index c377e293..dbd7e905 100644 --- a/blacksheep/cookies.pyx +++ b/blacksheep/cookies.pyx @@ -40,7 +40,7 @@ cdef class Cookie: str path=None, bint http_only=0, bint secure=0, - int max_age=-1, + long long max_age=-1, CookieSameSiteMode same_site=CookieSameSiteMode.UNDEFINED ): self.name = name diff --git a/blacksheep/scribe.pyx b/blacksheep/scribe.pyx index ef05d039..80d96f16 100644 --- a/blacksheep/scribe.pyx +++ b/blacksheep/scribe.pyx @@ -7,7 +7,8 @@ from .messages cimport Request, Response from .url cimport URL -cdef int MAX_RESPONSE_CHUNK_SIZE = 61440 # 64kb +MAX_RESPONSE_CHUNK_SIZE = 61440 # 64kb — Python-accessible +cdef int _MAX_RESPONSE_CHUNK_SIZE = MAX_RESPONSE_CHUNK_SIZE cdef bint should_use_chunked_encoding(Content content): @@ -45,8 +46,8 @@ async def write_chunks(Content http_content): def get_chunks(bytes data): cdef Py_ssize_t i - for i in range(0, len(data), MAX_RESPONSE_CHUNK_SIZE): - yield data[i:i + MAX_RESPONSE_CHUNK_SIZE] + for i in range(0, len(data), _MAX_RESPONSE_CHUNK_SIZE): + yield data[i:i + _MAX_RESPONSE_CHUNK_SIZE] yield b'' @@ -86,7 +87,7 @@ async def send_asgi_response(Response response, object send): 'more_body': False }) else: - if content.length > MAX_RESPONSE_CHUNK_SIZE: + if content.length > _MAX_RESPONSE_CHUNK_SIZE: # Note: get_chunks yields the closing bytes fragment therefore # we do not need to check for the closing message! for chunk in get_chunks(content.body): From 933b0039e316eaf86131430ac0c6334c94f128a8 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Mon, 11 May 2026 21:51:13 +0200 Subject: [PATCH 6/6] Update test_responses.py --- tests/test_responses.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_responses.py b/tests/test_responses.py index ef4b015e..6c34bdbc 100644 --- a/tests/test_responses.py +++ b/tests/test_responses.py @@ -984,7 +984,9 @@ def greet(self): def test_get_chunks_large_data(): """Regression test for OverflowError with data > 2GB (issue #675). Uses a mock large bytearray via memoryview to avoid allocating 2GB.""" - from blacksheep.scribe import MAX_RESPONSE_CHUNK_SIZE, get_chunks + from blacksheep.scribe import get_chunks + + MAX_RESPONSE_CHUNK_SIZE = 61440 # Simulate offset that would overflow a C int (> 2^31 - 1 bytes) # by using a large repeated pattern split across many chunks.