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.
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
pigzor a plaincat a.gz b.gzproduce.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:
outends up withfooand curl exits with code 23.gunzip < foobar.binreturns
foobar.I expected the following
outto containfoobarand the transfer to succeed, the same way gunzip, zcatand
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()inlib/content_encoding.c: once zlib returnsZ_STREAM_ENDfor the first member, the remaining bytes (the next member, starting with the gzip magic1f 8b) are taken as unexpected trailing data and rejected. The usual zlib idiom is to callinflateReset()whenZ_STREAM_ENDis 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.cis unchanged on current master.operating system
macOS, but it is not platform specific.