Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-6584: Add a BadGzipFile exception to the gzip module #13022

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions Doc/library/gzip.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ The module defines the following items:
.. versionchanged:: 3.6
Accepts a :term:`path-like object`.

.. exception:: BadGzipFile

An exception raised for invalid gzip files. It inherits :exc:`OSError`.
:exc:`EOFError` and :exc:`zlib.error` can also be raised for invalid gzip
files.

.. versionadded:: 3.8

.. class:: GzipFile(filename=None, mode=None, compresslevel=9, fileobj=None, mtime=None)

Constructor for the :class:`GzipFile` class, which simulates most of the
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,11 @@ gzip
Added the *mtime* parameter to :func:`gzip.compress` for reproducible output.
(Contributed by Guo Ci Teo in :issue:`34898`.)

A :exc:`~gzip.BadGzipFile` exception is now raised instead of :exc:`OSError`
for certain types of invalid or corrupt gzip files.
(Contributed by Filip Gruszczyński, Michele Orrù, and Zackery Spytz in
:issue:`6584`.)


idlelib and IDLE
----------------
Expand Down
17 changes: 11 additions & 6 deletions Lib/gzip.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import io
import _compression

__all__ = ["GzipFile", "open", "compress", "decompress"]
__all__ = ["BadGzipFile", "GzipFile", "open", "compress", "decompress"]

FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT = 1, 2, 4, 8, 16

Expand Down Expand Up @@ -112,6 +112,11 @@ def seek(self, off):
def seekable(self):
return True # Allows fast-forwarding even in unseekable streams


class BadGzipFile(OSError):
"""Exception raised in some cases for invalid gzip files."""


class GzipFile(_compression.BaseStream):
"""The GzipFile class simulates most of the methods of a file object with
the exception of the truncate() method.
Expand Down Expand Up @@ -413,12 +418,12 @@ def _read_gzip_header(self):
return False

if magic != b'\037\213':
raise OSError('Not a gzipped file (%r)' % magic)
raise BadGzipFile('Not a gzipped file (%r)' % magic)

(method, flag,
self._last_mtime) = struct.unpack("<BBIxx", self._read_exact(8))
if method != 8:
raise OSError('Unknown compression method')
raise BadGzipFile('Unknown compression method')

if flag & FEXTRA:
# Read & discard the extra field, if present
Expand Down Expand Up @@ -502,10 +507,10 @@ def _read_eof(self):
# stored is the true file size mod 2**32.
crc32, isize = struct.unpack("<II", self._read_exact(8))
if crc32 != self._crc:
raise OSError("CRC check failed %s != %s" % (hex(crc32),
hex(self._crc)))
raise BadGzipFile("CRC check failed %s != %s" % (hex(crc32),
hex(self._crc)))
elif isize != (self._stream_size & 0xffffffff):
raise OSError("Incorrect length of data produced")
raise BadGzipFile("Incorrect length of data produced")

# Gzip files can be padded with zeroes and still have archives.
# Consume all zero bytes and set the file position to the first
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_gzip.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,15 @@ def test_zero_padded_file(self):
d = f.read()
self.assertEqual(d, data1 * 50, "Incorrect data in file")

def test_gzip_BadGzipFile_exception(self):
self.assertTrue(issubclass(gzip.BadGzipFile, OSError))

def test_bad_gzip_file(self):
with open(self.filename, 'wb') as file:
file.write(data1 * 50)
with gzip.GzipFile(self.filename, 'r') as file:
self.assertRaises(gzip.BadGzipFile, file.readlines)

def test_non_seekable_file(self):
uncompressed = data1 * 50
buf = UnseekableIO()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a :exc:`~gzip.BadGzipFile` exception to the :mod:`gzip` module.