Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ jobs:
with:
python-version: 3.11
- name: Cache PyPI
uses: actions/cache@v6.1.0
uses: actions/cache@v5.0.5
with:
key: pip-lint-${{ hashFiles('requirements/*.txt') }}
path: ~/.cache/pip
Expand Down Expand Up @@ -158,7 +158,7 @@ jobs:
with:
submodules: true
- name: Cache llhttp generated files
uses: actions/cache@v6.1.0
uses: actions/cache@v5.0.5
id: cache
with:
key: llhttp-${{ hashFiles('vendor/llhttp/package*.json', 'vendor/llhttp/src/**/*') }}
Expand Down
1 change: 1 addition & 0 deletions CHANGES/12994.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed :py:class:`~aiohttp.http_parser.DeflateBuffer` to treat an empty ``feed_data`` call as a
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ Hans Adema
Harmon Y.
Harry Liu
Hiroshi Ogawa
Hrach Shahumyan
Hrishikesh Paranjape
Hu Bo
Hugh Young
Expand Down
2 changes: 1 addition & 1 deletion aiohttp/http_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1137,7 +1137,7 @@ def feed_data(self, chunk: bytes) -> bool:
# RFC1950
# bits 0..3 = CM = 0b1000 = 8 = "deflate"
# bits 4..7 = CINFO = 1..7 = windows size.
if (
if chunk and (
not self._started_decoding
and self.encoding == "deflate"
and chunk[0] & 0xF != 8
Expand Down
29 changes: 25 additions & 4 deletions tests/test_http_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -857,15 +857,15 @@ def test_request_chunked(parser: HttpRequestParser) -> None:


def test_te_header_non_ascii(parser: HttpRequestParser) -> None:
# = Kelvin sign, not valid ascii.
text = "GET /test HTTP/1.1\r\nHost: a\r\nTransfer-Encoding: chunKed\r\n\r\n"
# K = Kelvin sign, not valid ascii.
text = "GET /test HTTP/1.1\r\nHost: a\r\nTransfer-Encoding: chunKed\r\n\r\n"
with pytest.raises(http_exceptions.BadHttpMessage):
parser.feed_data(text.encode())


def test_upgrade_header_non_ascii(parser: HttpRequestParser) -> None:
# = Kelvin sign, not valid ascii.
text = "GET /test HTTP/1.1\r\nHost: a\r\nUpgrade: websocKet\r\n\r\n"
# K = Kelvin sign, not valid ascii.
text = "GET /test HTTP/1.1\r\nHost: a\r\nUpgrade: websocKet\r\n\r\n"
messages, upgrade, tail = parser.feed_data(text.encode())
assert not upgrade

Expand Down Expand Up @@ -2982,6 +2982,27 @@ async def test_feed_data(self, protocol: BaseProtocol) -> None:
dbuf.feed_data(b"xxxx")
assert [b"line"] == list(buf._buffer)

async def test_feed_data_empty(self, protocol: BaseProtocol) -> None:
"""feed_data(b"") must not raise even on a fresh deflate stream.

The chunked-transfer decoder calls feed_data(b"") to give a paused
decoder another chance to make progress. The CM-byte sniff in
feed_data would previously read chunk[0] on an empty chunk and raise
IndexError. Regression test for #12994.
"""
buf = aiohttp.StreamReader(
protocol, DEFAULT_CHUNK_SIZE, loop=asyncio.get_running_loop()
)
dbuf = DeflateBuffer(buf, "deflate")

# Should be a no-op, returning False (no more data).
assert dbuf.feed_data(b"") is False
# No bytes pushed to the downstream stream.
assert list(buf._buffer) == []
# Decoder was not switched to suppress_deflate_header by the empty
# chunk.
assert dbuf._started_decoding is False

async def test_feed_data_err(self, protocol: BaseProtocol) -> None:
buf = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())
dbuf = DeflateBuffer(buf, "deflate")
Expand Down
Loading