Skip to content

Commit 851ca37

Browse files
committed
Fix PyPy crash on invalid UTF-8 in multipart transport logs
1 parent 8611f81 commit 851ca37

File tree

2 files changed

+40
-3
lines changed

2 files changed

+40
-3
lines changed

gql/transport/http_multipart_transport.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ async def _parse_multipart_part(
259259
body = body.strip()
260260

261261
if log.isEnabledFor(logging.DEBUG):
262-
log.debug("<<< %s", body or "(empty body, skipping)")
262+
log.debug("<<< %s", ascii(body or "(empty body, skipping)"))
263263

264264
if not body:
265265
return None
@@ -305,9 +305,13 @@ async def _parse_multipart_part(
305305
)
306306
except json.JSONDecodeError as e:
307307
log.warning(
308-
f"Failed to parse JSON: {e}, body: {body[:100] if body else ''}"
308+
f"Failed to parse JSON: {ascii(e)}, "
309+
f"body: {ascii(body[:100]) if body else ''}"
309310
)
310311
return None
312+
except UnicodeDecodeError as e:
313+
log.warning(f"Failed to decode part: {ascii(e)}")
314+
return None
311315

312316
async def execute(
313317
self,

tests/test_http_multipart_transport.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ async def handler(request):
8080
response.enable_chunked_encoding()
8181
await response.prepare(request)
8282
for part in parts:
83-
await response.write(part.encode())
83+
if isinstance(part, str):
84+
await response.write(part.encode())
85+
else:
86+
await response.write(part)
8487
await asyncio.sleep(0) # force the chunk to be written
8588
await response.write_eof()
8689
return response
@@ -696,3 +699,33 @@ async def test_http_multipart_with_content_length_headers(multipart_server):
696699
assert len(results) == 2
697700
assert results[0]["book"]["title"] == "Book 1"
698701
assert results[1]["book"]["title"] == "Book 2"
702+
703+
704+
@pytest.mark.asyncio
705+
async def test_http_multipart_actually_invalid_utf8(multipart_server):
706+
"""Test handling of ACTUAL invalid UTF-8 bytes in multipart response."""
707+
from gql.transport.http_multipart_transport import HTTPMultipartTransport
708+
709+
# \\x80 is an invalid start byte in UTF-8
710+
parts = [
711+
(
712+
b"--graphql\r\n"
713+
b"Content-Type: application/json; charset=utf-8\r\n"
714+
b"\r\n"
715+
b"\x80\x81\r\n"
716+
),
717+
b"--graphql--\r\n",
718+
]
719+
720+
server = await multipart_server(parts)
721+
url = server.make_url("/")
722+
transport = HTTPMultipartTransport(url=url)
723+
724+
async with Client(transport=transport) as session:
725+
query = gql(subscription_str)
726+
results = []
727+
async for result in session.subscribe(query):
728+
results.append(result)
729+
730+
# Should skip invalid part and not crash
731+
assert len(results) == 0

0 commit comments

Comments
 (0)