]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.7] bpo-39389: gzip: fix compression level metadata (GH-18077) (GH-18101)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Wed, 4 Mar 2020 07:06:19 +0000 (23:06 -0800)
committerGitHub <noreply@github.com>
Wed, 4 Mar 2020 07:06:19 +0000 (02:06 -0500)
* bpo-39389: gzip: fix compression level metadata (GH-18077)

As described in RFC 1952, section 2.3.1, the XFL (eXtra FLags) byte of a
gzip member header should indicate whether the DEFLATE algorithm was
tuned for speed or compression ratio. Prior to this patch, archives
emitted by the `gzip` module always indicated maximum compression.
(cherry picked from commit eab3b3f1c60afecfb4db3c3619109684cb04bd60)

Co-authored-by: William Chargin <wchargin@gmail.com>
Lib/gzip.py
Lib/test/test_gzip.py
Misc/NEWS.d/next/Library/2020-01-20-00-56-01.bpo-39389.fEirIS.rst [new file with mode: 0644]

index ddc7bda1fecbbd25ef4f3db6175f4e59e897f142..e59b45481432720c8125934c972700616136a9de 100644 (file)
@@ -17,6 +17,11 @@ FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT = 1, 2, 4, 8, 16
 
 READ, WRITE = 1, 2
 
+_COMPRESS_LEVEL_FAST = 1
+_COMPRESS_LEVEL_TRADEOFF = 6
+_COMPRESS_LEVEL_BEST = 9
+
+
 def open(filename, mode="rb", compresslevel=9,
          encoding=None, errors=None, newline=None):
     """Open a gzip-compressed file in binary or text mode.
@@ -191,7 +196,7 @@ class GzipFile(_compression.BaseStream):
         self.fileobj = fileobj
 
         if self.mode == WRITE:
-            self._write_gzip_header()
+            self._write_gzip_header(compresslevel)
 
     @property
     def filename(self):
@@ -218,7 +223,7 @@ class GzipFile(_compression.BaseStream):
         self.bufsize = 0
         self.offset = 0  # Current file offset for seek(), tell(), etc
 
-    def _write_gzip_header(self):
+    def _write_gzip_header(self, compresslevel):
         self.fileobj.write(b'\037\213')             # magic header
         self.fileobj.write(b'\010')                 # compression method
         try:
@@ -239,7 +244,13 @@ class GzipFile(_compression.BaseStream):
         if mtime is None:
             mtime = time.time()
         write32u(self.fileobj, int(mtime))
-        self.fileobj.write(b'\002')
+        if compresslevel == _COMPRESS_LEVEL_BEST:
+            xfl = b'\002'
+        elif compresslevel == _COMPRESS_LEVEL_FAST:
+            xfl = b'\004'
+        else:
+            xfl = b'\000'
+        self.fileobj.write(xfl)
         self.fileobj.write(b'\377')
         if fname:
             self.fileobj.write(fname + b'\000')
index 17ecda2089f250940a60f64d5f31350a1cd27767..0251914d8bcabafc1216896ddc1e320a612cf517 100644 (file)
@@ -358,6 +358,26 @@ class TestGzip(BaseTest):
             isizeBytes = fRead.read(4)
             self.assertEqual(isizeBytes, struct.pack('<i', len(data1)))
 
+    def test_compresslevel_metadata(self):
+        # see RFC 1952: http://www.faqs.org/rfcs/rfc1952.html
+        # specifically, discussion of XFL in section 2.3.1
+        cases = [
+            ('fast', 1, b'\x04'),
+            ('best', 9, b'\x02'),
+            ('tradeoff', 6, b'\x00'),
+        ]
+        xflOffset = 8
+
+        for (name, level, expectedXflByte) in cases:
+            with self.subTest(name):
+                fWrite = gzip.GzipFile(self.filename, 'w', compresslevel=level)
+                with fWrite:
+                    fWrite.write(data1)
+                with open(self.filename, 'rb') as fRead:
+                    fRead.seek(xflOffset)
+                    xflByte = fRead.read(1)
+                    self.assertEqual(xflByte, expectedXflByte)
+
     def test_with_open(self):
         # GzipFile supports the context management protocol
         with gzip.GzipFile(self.filename, "wb") as f:
diff --git a/Misc/NEWS.d/next/Library/2020-01-20-00-56-01.bpo-39389.fEirIS.rst b/Misc/NEWS.d/next/Library/2020-01-20-00-56-01.bpo-39389.fEirIS.rst
new file mode 100644 (file)
index 0000000..d4c8050
--- /dev/null
@@ -0,0 +1,2 @@
+Write accurate compression level metadata in :mod:`gzip` archives, rather
+than always signaling maximum compression.