diff options
Diffstat (limited to 'packages/meshbay-node/tests/test_linkpreview.py')
| -rw-r--r-- | packages/meshbay-node/tests/test_linkpreview.py | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_linkpreview.py b/packages/meshbay-node/tests/test_linkpreview.py index a14b173..9fca186 100644 --- a/packages/meshbay-node/tests/test_linkpreview.py +++ b/packages/meshbay-node/tests/test_linkpreview.py @@ -193,3 +193,83 @@ async def test_fetch_image_refuses_a_decompression_bomb(resolves_public, monkeyp return httpx.Response(200, headers={"content-type": "image/png"}, content=bomb) async with _client(handler) as c: assert await linkpreview.fetch_image("https://example.com/x.png", client=c) is None + + +# ── Size caps bind while reading, not after ───────────────────────────────── +# +# `client.get` read and decoded the whole body before the caps looked at it, so +# a link posted in chat could make the node hold any amount of data. These +# count what the server actually had to hand over. + +_CHUNK = 64 * 1024 + + +def _endless(counter, head=b""): + async def body(): + if head: + counter["sent"] += len(head) + yield head + for _ in range(2000): # 125 MiB if nobody stops + counter["sent"] += _CHUNK + yield b"x" * _CHUNK + return body() + + +async def test_a_huge_page_is_not_read_past_the_cap(resolves_public): + counter = {"sent": 0} + head = b"<html><head><title>T</title></head><body>" + + def handler(request): + return httpx.Response(200, headers={"content-type": "text/html"}, + content=_endless(counter, head)) + async with _client(handler) as c: + meta = await linkpreview.fetch_preview("https://example.com/", client=c) + assert meta is not None and meta["title"] == "T" + assert counter["sent"] <= linkpreview._MAX_HTML_BYTES + 2 * _CHUNK + + +async def test_a_huge_image_is_refused_without_being_read(resolves_public): + counter = {"sent": 0} + + def handler(request): + return httpx.Response(200, headers={"content-type": "image/png"}, + content=_endless(counter)) + async with _client(handler) as c: + assert await linkpreview.fetch_image("https://example.com/x.png", client=c) is None + assert counter["sent"] <= linkpreview._MAX_IMAGE_BYTES + 2 * _CHUNK + + +async def test_a_compressed_body_is_capped_after_decoding(resolves_public): + import zlib + comp = zlib.compressobj(9, zlib.DEFLATED, 31) # gzip + counter = {"inflated": 0} + + async def body(): + yield comp.compress(b"<html><head><title>T</title></head><body>") + for _ in range(2000): # 125 MiB inflated + counter["inflated"] += _CHUNK + # Flushed per chunk, so what is counted is what went on the wire. + yield comp.compress(b"x" * _CHUNK) + comp.flush(zlib.Z_SYNC_FLUSH) + yield comp.flush() + + def handler(request): + return httpx.Response(200, headers={"content-type": "text/html", + "content-encoding": "gzip"}, + content=body()) + async with _client(handler) as c: + meta = await linkpreview.fetch_preview("https://example.com/", client=c) + assert meta is not None + assert counter["inflated"] < 50 * _CHUNK # stopped early, not at 125 MiB + + +async def test_a_declared_oversized_image_is_not_read(resolves_public): + counter = {"sent": 0} + + def handler(request): + return httpx.Response( + 200, headers={"content-type": "image/png", + "content-length": str(linkpreview._MAX_IMAGE_BYTES + 1)}, + content=_endless(counter)) + async with _client(handler) as c: + assert await linkpreview.fetch_image("https://example.com/x.png", client=c) is None + assert counter["sent"] <= _CHUNK |