Skip to content

--compressed fails on multi-member (concatenated) gzip responses #22156

Description

@HwangRock

I did this

I was fetching large compressed log archives over HTTP with curl --compressed.
Some of these are made by concatenating several gzip files, which is a common shape for rotated logs and what pigz or a plain cat a.gz b.gz produce.
Per RFC 1952 section 2.2 that is still one valid gzip stream (gunzip and zcat decode it fine), but curl stops partway through.

curl decodes only the first gzip member and then fails the transfer with CURLE_WRITE_ERROR (23), dropping the rest of the data.

Minimal reproduction, no real server needed:

import zlib
data = b"HTTP/1.1 200 OK\r\nContent-Encoding: gzip\r\nConnection: close\r\n\r\n"
def member(s):
    c = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16)
    return c.compress(s) + c.flush()
data += member(b"foo") + member(b"bar")   # two concatenated gzip members
open("foobar.bin", "wb").write(data)
# terminal 1
while true; do nc -l 127.0.0.1 8000 < foobar.bin; done
# terminal 2
curl -sv --compressed -o out http://127.0.0.1:8000/

out ends up with foo and curl exits with code 23. gunzip < foobar.bin
returns foobar.

I expected the following

out to contain foobar and the transfer to succeed, the same way gunzip, zcat
and gzip.decompress() in Python handle it.

Notes

This came up before in #3796, which went stale without a conclusion.
That thread weighed two options: silently ignore everything after the first member (what some browsers do), or decode every member. The argument against silently ignoring was data integrity, since curl is usually an automated tool and quietly losing data
is easy to miss.

Decoding all members keeps that integrity and matches the reference tools.
The spot is process_trailer() in lib/content_encoding.c: once zlib returns Z_STREAM_END for the first member, the remaining bytes (the next member, starting with the gzip magic 1f 8b) are taken as unexpected trailing data and rejected. The usual zlib idiom is to call inflateReset() when Z_STREAM_END is hit with input still pending and continue.

I have a patch and a test and can send a PR if this direction looks right.

curl/libcurl version

Reproduced with curl 8.7.1 (zlib/1.2.12). The relevant code in lib/content_encoding.c is unchanged on current master.

operating system

macOS, but it is not platform specific.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions