import os import wheel.install import wheel.archive import hashlib try: from StringIO import StringIO except ImportError: from io import BytesIO as StringIO import codecs import zipfile import pytest import shutil import tempfile from contextlib import contextmanager @contextmanager def environ(key, value): old_value = os.environ.get(key) try: os.environ[key] = value yield finally: if old_value is None: del os.environ[key] else: os.environ[key] = old_value @contextmanager def temporary_directory(): # tempfile.TemporaryDirectory doesn't exist in Python 2. tempdir = tempfile.mkdtemp() try: yield tempdir finally: shutil.rmtree(tempdir) @contextmanager def readable_zipfile(path): # zipfile.ZipFile() isn't a context manager under Python 2. zf = zipfile.ZipFile(path, 'r') try: yield zf finally: zf.close() def test_verifying_zipfile(): if not hasattr(zipfile.ZipExtFile, '_update_crc'): pytest.skip('No ZIP verification. Missing ZipExtFile._update_crc.') sio = StringIO() zf = zipfile.ZipFile(sio, 'w') zf.writestr("one", b"first file") zf.writestr("two", b"second file") zf.writestr("three", b"third file") zf.close() # In default mode, VerifyingZipFile checks the hash of any read file # mentioned with set_expected_hash(). Files not mentioned with # set_expected_hash() are not checked. vzf = wheel.install.VerifyingZipFile(sio, 'r') vzf.set_expected_hash("one", hashlib.sha256(b"first file").digest()) vzf.set_expected_hash("three", "blurble") vzf.open("one").read() vzf.open("two").read() try: vzf.open("three").read() except wheel.install.BadWheelFile: pass else: raise Exception("expected exception 'BadWheelFile()'") # In strict mode, VerifyingZipFile requires every read file to be # mentioned with set_expected_hash(). vzf.strict = True try: vzf.open("two").read() except wheel.install.BadWheelFile: pass else: raise Exception("expected exception 'BadWheelFile()'") vzf.set_expected_hash("two", None) vzf.open("two").read() def test_pop_zipfile(): sio = StringIO() zf = wheel.install.VerifyingZipFile(sio, 'w') zf.writestr("one", b"first file") zf.writestr("two", b"second file") zf.close() try: zf.pop() except RuntimeError: pass # already closed else: raise Exception("expected RuntimeError") zf = wheel.install.VerifyingZipFile(sio, 'a') zf.pop() zf.close() zf = wheel.install.VerifyingZipFile(sio, 'r') assert len(zf.infolist()) == 1 def test_zipfile_timestamp(): # An environment variable can be used to influence the timestamp on # TarInfo objects inside the zip. See issue #143. TemporaryDirectory is # not a context manager under Python 3. with temporary_directory() as tempdir: for filename in ('one', 'two', 'three'): path = os.path.join(tempdir, filename) with codecs.open(path, 'w', encoding='utf-8') as fp: fp.write(filename + '\n') zip_base_name = os.path.join(tempdir, 'dummy') # The earliest date representable in TarInfos, 1980-01-01 with environ('SOURCE_DATE_EPOCH', '315576060'): zip_filename = wheel.archive.make_wheelfile_inner( zip_base_name, tempdir) with readable_zipfile(zip_filename) as zf: for info in zf.infolist(): assert info.date_time[:3] == (1980, 1, 1) def test_zipfile_attributes(): # With the change from ZipFile.write() to .writestr(), we need to manually # set member attributes. with temporary_directory() as tempdir: files = (('foo', 0o644), ('bar', 0o755)) for filename, mode in files: path = os.path.join(tempdir, filename) with codecs.open(path, 'w', encoding='utf-8') as fp: fp.write(filename + '\n') os.chmod(path, mode) zip_base_name = os.path.join(tempdir, 'dummy') zip_filename = wheel.archive.make_wheelfile_inner( zip_base_name, tempdir) with readable_zipfile(zip_filename) as zf: for filename, mode in files: info = zf.getinfo(os.path.join(tempdir, filename)) assert info.external_attr == (mode | 0o100000) << 16 assert info.compress_type == zipfile.ZIP_DEFLATED