@@ -2234,6 +2234,74 @@ class LzmaBoundedDecompressTests(AbstractBoundedDecompressTests,
22342234 compression = zipfile .ZIP_LZMA
22352235
22362236
2237+
2238+ class MonkeypatchedDecompressorTests (unittest .TestCase ):
2239+ # Some third-party projects monkey-patch _get_decompressor() to add
2240+ # additional compression schemes. This can break at any time as the
2241+ # internal compressor objects change.
2242+ # To protect users, we try to keep this case working.
2243+ # See also: GH-156002 and GH-113767.
2244+ COMPRESSION = 99
2245+
2246+ class Compressor :
2247+ """Compressor with only the original BZ2Compressor API"""
2248+ def compress (self , data ):
2249+ return data .swapcase ()
2250+
2251+ def flush (self ):
2252+ return b''
2253+
2254+ class Decompressor :
2255+ """Decompressor with only the 3.3+ BZ2Decompressor API"""
2256+ eof = False
2257+
2258+ def decompress (self , data ):
2259+ return data .swapcase ()
2260+
2261+ def setUp (self ):
2262+ orig_check_compression = zipfile ._check_compression
2263+ orig_get_compressor = zipfile ._get_compressor
2264+ orig_get_decompressor = zipfile ._get_decompressor
2265+
2266+ def check_compression (compression ):
2267+ if compression != self .COMPRESSION :
2268+ orig_check_compression (compression )
2269+
2270+ def get_compressor (compress_type , compresslevel = None ):
2271+ if compress_type == self .COMPRESSION :
2272+ return self .Compressor ()
2273+ return orig_get_compressor (compress_type , compresslevel )
2274+
2275+ def get_decompressor (compress_type ):
2276+ if compress_type == self .COMPRESSION :
2277+ return self .Decompressor ()
2278+ return orig_get_decompressor (compress_type )
2279+
2280+ self .enterContext (mock .patch .object (
2281+ zipfile , '_check_compression' , check_compression ))
2282+ self .enterContext (mock .patch .object (
2283+ zipfile , '_get_compressor' , get_compressor ))
2284+ self .enterContext (mock .patch .object (
2285+ zipfile , '_get_decompressor' , get_decompressor ))
2286+
2287+ def test_roundtrip_monkeypatched_decompressor (self ):
2288+ data = bytes (range (256 )) * 8
2289+ buf = io .BytesIO ()
2290+ with zipfile .ZipFile (buf , "w" , compression = self .COMPRESSION ) as zf :
2291+ zf .writestr ("member" , data )
2292+ self .assertIn (data .swapcase (), buf .getvalue ())
2293+ with zipfile .ZipFile (io .BytesIO (buf .getvalue ())) as zf :
2294+ self .assertEqual (zf .read ("member" ), data )
2295+ with zf .open ("member" ) as f :
2296+ self .assertEqual (f .read (100 ), data [:100 ])
2297+ self .assertEqual (f .read1 (100 ), data [100 :200 ])
2298+ f .seek (- 100 , os .SEEK_END )
2299+ self .assertEqual (f .read (), data [- 100 :])
2300+ # Rewinding past the read buffer re-creates the decompressor.
2301+ f .seek (0 )
2302+ self .assertEqual (f .read (), data )
2303+
2304+
22372305class AbstractBadCrcTests :
22382306 def test_testzip_with_bad_crc (self ):
22392307 """Tests that files with bad CRCs return their name from testzip."""
0 commit comments