aio-libs/aiohttp · error · ContentEncodingError

Can not decode content-encoding

Error message

Can not decode content-encoding: %s

What it means

Raised as ContentEncodingError when the decompressor fails on a body chunk — i.e. decompressor.decompress_sync raises any Exception. DeflateBuffer.feed_data wraps the call in try/except Exception and re-raises this generic message with the encoding name. It signals corrupt, truncated, or non-conformant compressed payload data for gzip/deflate/br/zstd.

Solutions

  1. Verify the server sends a correct, complete stream for the declared Content-Encoding.
  2. Check intermediaries (proxies, firewalls, AV) that may alter body bytes.
  3. If you intentionally want raw bytes, disable auto-decompress: ClientSession(..., auto_decompress=False).
  4. Re-fetch to rule out transient corruption; if reproducible, dump the raw bytes and validate with a standalone decompressor.
  5. Confirm the Accept-Encoding you sent matches what the server actually supports.

Example fix

# before (server mislabels encoding)
return web.Response(body=gzip_data, headers={'Content-Encoding': 'deflate'})

# after (correct header)
return web.Response(body=gzip_data, headers={'Content-Encoding': 'gzip'})

# or, client-side, disable auto-decompress to inspect raw bytes
session = aiohttp.ClientSession(auto_decompress=False)
Defensive patterns

Strategy: try-catch

Validate before calling

import zlib, gzip

def validate_gzip_stream(raw: bytes) -> bool:
    try:
        zlib.decompress(raw, 16 + zlib.MAX_WBITS)
        return True
    except zlib.error:
        return False

Try / catch

from aiohttp.http_exceptions import ContentEncodingError
import aiohttp

async def get_with_fallback(url):
    try:
        async with aiohttp.ClientSession() as s:
            async with s.get(url) as r:
                return await r.read()
    except ContentEncodingError:
        # retry with auto_decompress disabled to inspect raw bytes
        async with aiohttp.ClientSession(auto_decompress=False) as s:
            async with s.get(url) as r:
                return await r.read()  # caller decompresses manually

Prevention

When it happens

Trigger: The body bytes fed to the decompressor do not form a valid stream for the declared Content-Encoding (gzip/deflate/br/zstd). Examples: truncated gzip stream, wrong algorithm vs header, corrupted bytes, a server sending raw deflate while declaring gzip, or a mid-stream encoding switch.

Common situations: Corrupt response body from a buggy server; a proxy mangling bytes; partial body due to connection drop then resumption; mislabelled Content-Encoding (server says gzip but sends raw bytes); CDN compression mismatch; downloaded file served with wrong encoding header.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/dff2f9c814d57b10. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/http_parser.py:1200

            # RFC1950
            # bits 0..3 = CM = 0b1000 = 8 = "deflate"
            # bits 4..7 = CINFO = 1..7 = windows size.
            if self.encoding == "deflate" and chunk[0] & 0xF != 8:
                # Change the decoder to decompress incorrectly compressed data
                # Actually we should issue a warning about non-RFC-compliant data.
                self.decompressor = ZLibDecompressor(
                    encoding=self.encoding, suppress_deflate_header=True
                )
            self._started_decoding = True

        low_water = self.out._low_water
        max_length = (
            0 if low_water >= sys.maxsize else max(self._max_decompress_size, low_water)
        )
        try:
            chunk = self.decompressor.decompress_sync(chunk, max_length=max_length)
        except Exception:
            raise ContentEncodingError(
                "Can not decode content-encoding: %s" % self.encoding
            )

        if chunk:
            self.out.feed_data(chunk)
        return self.decompressor.data_available

    def feed_eof(self) -> None:
        chunk = self.decompressor.flush()
        # This should never contain data as we defer the call until exhausting
        # the decompression. If .flush() is returning data, this may indicate a
        # zip bomb vulnerability as it will decompress all remaining data at once.
        assert not chunk

        if self.size > 0:
            # decompressor is not brotli unless encoding is "br"
            if self.encoding == "deflate" and not self.decompressor.eof:  # type: ignore[union-attr]
                raise ContentEncodingError("deflate")

View on GitHub (pinned to d041d4d0fd)