@@ -2454,6 +2454,74 @@ class LzmaBoundedDecompressTests(AbstractBoundedDecompressTests,
24542454 compression = zipfile .ZIP_LZMA
24552455
24562456
2457+
2458+ class MonkeypatchedDecompressorTests (unittest .TestCase ):
2459+ # Some third-party projects monkey-patch _get_decompressor() to add
2460+ # additional compression schemes. This can break at any time as the
2461+ # internal compressor objects change.
2462+ # To protect users, we try to keep this case working.
2463+ # See also: GH-156002 and GH-113767.
2464+ COMPRESSION = 99
2465+
2466+ class Compressor :
2467+ """Compressor with only the original BZ2Compressor API"""
2468+ def compress (self , data ):
2469+ return data .swapcase ()
2470+
2471+ def flush (self ):
2472+ return b''
2473+
2474+ class Decompressor :
2475+ """Decompressor with only the 3.3+ BZ2Decompressor API"""
2476+ eof = False
2477+
2478+ def decompress (self , data ):
2479+ return data .swapcase ()
2480+
2481+ def setUp (self ):
2482+ orig_check_compression = zipfile ._check_compression
2483+ orig_get_compressor = zipfile ._get_compressor
2484+ orig_get_decompressor = zipfile ._get_decompressor
2485+
2486+ def check_compression (compression ):
2487+ if compression != self .COMPRESSION :
2488+ orig_check_compression (compression )
2489+
2490+ def get_compressor (compress_type , compresslevel = None ):
2491+ if compress_type == self .COMPRESSION :
2492+ return self .Compressor ()
2493+ return orig_get_compressor (compress_type , compresslevel )
2494+
2495+ def get_decompressor (compress_type ):
2496+ if compress_type == self .COMPRESSION :
2497+ return self .Decompressor ()
2498+ return orig_get_decompressor (compress_type )
2499+
2500+ self .enterContext (mock .patch .object (
2501+ zipfile , '_check_compression' , check_compression ))
2502+ self .enterContext (mock .patch .object (
2503+ zipfile , '_get_compressor' , get_compressor ))
2504+ self .enterContext (mock .patch .object (
2505+ zipfile , '_get_decompressor' , get_decompressor ))
2506+
2507+ def test_roundtrip_monkeypatched_decompressor (self ):
2508+ data = bytes (range (256 )) * 8
2509+ buf = io .BytesIO ()
2510+ with zipfile .ZipFile (buf , "w" , compression = self .COMPRESSION ) as zf :
2511+ zf .writestr ("member" , data )
2512+ self .assertIn (data .swapcase (), buf .getvalue ())
2513+ with zipfile .ZipFile (io .BytesIO (buf .getvalue ())) as zf :
2514+ self .assertEqual (zf .read ("member" ), data )
2515+ with zf .open ("member" ) as f :
2516+ self .assertEqual (f .read (100 ), data [:100 ])
2517+ self .assertEqual (f .read1 (100 ), data [100 :200 ])
2518+ f .seek (- 100 , os .SEEK_END )
2519+ self .assertEqual (f .read (), data [- 100 :])
2520+ # Rewinding past the read buffer re-creates the decompressor.
2521+ f .seek (0 )
2522+ self .assertEqual (f .read (), data )
2523+
2524+
24572525class AbstractBadCrcTests :
24582526 def test_testzip_with_bad_crc (self ):
24592527 """Tests that files with bad CRCs return their name from testzip."""
0 commit comments