Skip to content

Commit

Permalink
Fix tarfile file-like objects used as data (#6747)
Browse files Browse the repository at this point in the history
(cherry picked from commit 7681235)
  • Loading branch information
ReallyReivax authored and Dreamsorcerer committed Aug 30, 2024
1 parent 8173932 commit c5ef909
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGES/6732.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed handling of some file-like objects (e.g. ``tarfile.extractfile()``) which raise ``AttributeError`` instead of ``OSError`` when ``fileno`` fails for streaming payload data -- by :user:`ReallyReivax`.
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ William Grzybowski
William S.
Wilson Ong
wouter bolsterlee
Xavier Halloran
Xiang Li
Yang Zhou
Yannick Koechlin
Expand Down
4 changes: 3 additions & 1 deletion aiohttp/payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,9 +401,11 @@ class BufferedReaderPayload(IOBasePayload):
def size(self) -> Optional[int]:
try:
return os.fstat(self._value.fileno()).st_size - self._value.tell()
except OSError:
except (OSError, AttributeError):
# data.fileno() is not supported, e.g.
# io.BufferedReader(io.BytesIO(b'data'))
# For some file-like objects (e.g. tarfile), the fileno() attribute may
# not exist at all, and will instead raise an AttributeError.
return None

def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str:
Expand Down
57 changes: 57 additions & 0 deletions tests/test_client_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import socket
import ssl
import sys
import tarfile
import time
import zipfile
from typing import Any, AsyncIterator, Type
from unittest import mock

Expand Down Expand Up @@ -511,6 +513,61 @@ async def handler(request):
assert 200 == resp.status


async def test_post_data_zipfile_filelike(aiohttp_client: AiohttpClient) -> None:
data = b"This is a zip file payload text file."

async def handler(request: web.Request) -> web.Response:
val = await request.read()
assert data == val, "Transmitted zipfile member failed to match original data."
return web.Response()

app = web.Application()
app.router.add_route("POST", "/", handler)
client = await aiohttp_client(app)

buf = io.BytesIO()
with zipfile.ZipFile(file=buf, mode="w") as zf:
with zf.open("payload1.txt", mode="w") as zip_filelike_writing:
zip_filelike_writing.write(data)

buf.seek(0)
zf = zipfile.ZipFile(file=buf, mode="r")
resp = await client.post("/", data=zf.open("payload1.txt"))
assert 200 == resp.status


async def test_post_data_tarfile_filelike(aiohttp_client: AiohttpClient) -> None:
data = b"This is a tar file payload text file."

async def handler(request: web.Request) -> web.Response:
val = await request.read()
assert data == val, "Transmitted tarfile member failed to match original data."
return web.Response()

app = web.Application()
app.router.add_route("POST", "/", handler)
client = await aiohttp_client(app)

buf = io.BytesIO()
with tarfile.open(fileobj=buf, mode="w") as tf:
ti = tarfile.TarInfo(name="payload1.txt")
ti.size = len(data)
tf.addfile(tarinfo=ti, fileobj=io.BytesIO(data))

# Random-access tarfile.
buf.seek(0)
tf = tarfile.open(fileobj=buf, mode="r:")
resp = await client.post("/", data=tf.extractfile("payload1.txt"))
assert 200 == resp.status

# Streaming tarfile.
buf.seek(0)
tf = tarfile.open(fileobj=buf, mode="r|")
for entry in tf:
resp = await client.post("/", data=tf.extractfile(entry))
assert 200 == resp.status


async def test_ssl_client(
aiohttp_server,
ssl_ctx,
Expand Down

0 comments on commit c5ef909

Please sign in to comment.