Conversation
The FNAME field of the gzip header is meant to hold the name the file is
expected to have after decompression, but gzip and tarfile only stripped
a literal ".gz" suffix. An archive created as "spam.tgz" therefore
recorded "spam.tgz", so decompressors that honor FNAME wrote out a tar
file still named ".tgz".
Match gunzip instead: compare the suffix ignoring case, and replace
".tgz" and ".taz" with ".tar" rather than leaving them in place. This
covers both code paths, GzipFile._write_gzip_header (mode "w:gz") and
tarfile._Stream._init_write_gz (mode "w|gz").
Only the suffix is matched ignoring case; the rest of the name keeps the
case it was given. gunzip behaves the same way: get_suffix() lowercases
only the trailing bytes it compares, and make_ofname() calls strlwr() on
the suffix alone, so "SPAM.TGZ" becomes "SPAM.tar". Both branches are
tested with an upper-case stem.
Deliberately left out, being separate user-visible changes rather than
part of deriving the FNAME field: the remaining suffixes gunzip knows
(".z", "-gz", "-z" and "_z"), and the "python -m gzip -d" CLI, which
still accepts only a literal ".gz" argument. ".tz" is not added because
it is not a gunzip suffix at all, unlike ".taz".
Documentation build overview
|
| # and ".taz" into ".tar" instead of just stripping them. | ||
| if fname[-3:].lower() == b'.gz': | ||
| fname = fname[:-3] | ||
| elif fname[-4:].lower() in (b'.tgz', b'.taz'): |
Member
There was a problem hiding this comment.
.taz is for .tar.Z, but Python's gzip module does not support the .Z compression.
The reading side is now inconsistent with what we write:
$ python -m gzip -d spam.tgz
filename doesn't end in .gz: 'spam.tgz'
main() tests arg[-3:] != ".gz", so it rejects both spam.tgz and spam.GZ, which gunzip accepts. Could you fix it here?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #88661.
An archive created as
spam.tgzrecordsspam.tgzas the name to decompress to, sogunzipon it produces a file calledspam.tgz. Measured onmain, readingFLGand the NUL-terminatedFNAMEstraight out of the RFC 1952 header:maintarfile.open("test.tgz", "w:gz")b'test.tgz'b'test.tar'tarfile.open("s.tgz", "w|gz")b's.tgz'b's.tar'tarfile.open("TEST.TGZ", "w:gz")b'TEST.TGZ'b'TEST.tar'tarfile.open("test.tAr.Gz", "w:gz")b'test.tAr.Gz'b'test.tAr'gzip.open("data.tgz", "wb")b'data.tgz'b'data.tar'gzip.open("DATA.GZ", "wb")b'DATA.GZ'b'DATA'Two independent code paths carry the same defect:
GzipFile._write_gzip_header(used bygzip.openand bytarfile'sw:gz) and_Stream._init_write_gz(w|gz). Both are fixed identically.Following @serhiy-storchaka's direction
So the suffix is matched case-insensitively, and
.tgzbecomes.tarrather than being stripped. Matching the case-insensitivity is a wider change than the issue title suggests —gzip.open("DATA.GZ", "wb")now recordsDATAinstead ofDATA.GZ— so I am calling that out rather than letting it ride along.The stem keeps its case and the suffix becomes lowercase
.tar, which is what GNUmake_ofname()does:.tazis included, which the issue does not mention. It sits in the sameifas.tgzinmake_ofname(), so implementing only half of the criterion Serhiy cited seemed worse than implementing it. The stdlib already treats the two alike —mimetypes.py:462has'.tgz': '.tar.gz', '.taz': '.tar.gz'..tzis not included: GNU handles it through the genericknown_suffixes[]stripping, not the.tarspecial case.The logic is duplicated rather than shared.
_Stream.__init__imports onlyzlib;gzipis pulled in lazily and only byTarFile.gzopen, so a shared helper ingzip.pywould add agzipimport to thew|gzpath for four lines. The duplication predates this change; both copies now say the same thing.The comparison slices the original and lowercases only the tail rather than using
fname.lower().endswith(...), becausestr.lower()can change length ('İ') and the slice index would then be wrong.Not in scope, but noticed
python -m gzip -d(Lib/gzip.py:736) doesif arg[-3:] != ".gz": sys.exit(...), so the CLI that describes itself as "act likegunzip" is still case-sensitive and refuses.tgzoutright. That is a different behaviour — it rejects the file rather than misnaming a header field — so I left it alone. Happy to do it as a follow-up if you want the module internally consistent.Verification
Run on a build of
main(3.16.0a0):Reverting only
Lib/gzip.pyandLib/tarfile.pytomainwhile keeping the tests:run=863 failures=26, from two test methods (test_fname,test_metadata_name_suffix) whose subtests cover each suffix and case combination.A mutation lowercasing the stem as well as the suffix —
fname[:-4].lower() + b'.tar'— fails 2 tests, so the case-preserving half is pinned rather than incidental.mainonly; I am not proposing a backport, since the change alters bytes in generated files.Misc/NEWS.d/next/Library/entry added, plus a note under Porting to Python 3.16 rather than under Improved Modules, since the section is explicitly for "changes that may require changes to your code" and byte-for-byte comparisons of generated archives will shift.Thanks to @miikka for laying out the options in the thread, including the case that
GzipFilecannot know whether a.tgzreally is a tarball — which is why this follows the file name likegunzipdoes rather than inspecting content.