18
18
import warnings
19
19
from base64 import urlsafe_b64encode
20
20
from email .parser import Parser
21
+ from zipfile import ZipFile
21
22
22
23
from pip ._vendor import pkg_resources
23
24
from pip ._vendor .distlib .scripts import ScriptMaker
24
25
from pip ._vendor .distlib .util import get_export_entry
25
26
from pip ._vendor .packaging .utils import canonicalize_name
26
- from pip ._vendor .six import StringIO , ensure_str
27
+ from pip ._vendor .six import PY2 , StringIO , ensure_str
27
28
28
29
from pip ._internal .exceptions import InstallationError , UnsupportedWheel
29
30
from pip ._internal .locations import get_major_minor_version
43
44
44
45
InstalledCSVRow = Tuple [str , ...]
45
46
47
+ if PY2 :
48
+ from zipfile import BadZipfile as BadZipFile
49
+ else :
50
+ from zipfile import BadZipFile
51
+
46
52
47
53
VERSION_COMPATIBLE = (1 , 0 )
48
54
@@ -286,6 +292,7 @@ def make(self, specification, options=None):
286
292
def install_unpacked_wheel (
287
293
name , # type: str
288
294
wheeldir , # type: str
295
+ wheel_zip , # type: ZipFile
289
296
scheme , # type: Scheme
290
297
req_description , # type: str
291
298
pycompile = True , # type: bool
@@ -296,6 +303,7 @@ def install_unpacked_wheel(
296
303
297
304
:param name: Name of the project to install
298
305
:param wheeldir: Base directory of the unpacked wheel
306
+ :param wheel_zip: open ZipFile for wheel being installed
299
307
:param scheme: Distutils scheme dictating the install directories
300
308
:param req_description: String used in place of the requirement, for
301
309
logging
@@ -313,16 +321,7 @@ def install_unpacked_wheel(
313
321
314
322
source = wheeldir .rstrip (os .path .sep ) + os .path .sep
315
323
316
- try :
317
- info_dir = wheel_dist_info_dir (source , name )
318
- metadata = wheel_metadata (source , info_dir )
319
- version = wheel_version (metadata )
320
- except UnsupportedWheel as e :
321
- raise UnsupportedWheel (
322
- "{} has an invalid wheel, {}" .format (name , str (e ))
323
- )
324
-
325
- check_compatibility (version , name )
324
+ info_dir , metadata = parse_wheel (wheel_zip , name )
326
325
327
326
if wheel_root_is_purelib (metadata ):
328
327
lib_dir = scheme .purelib
@@ -612,26 +611,50 @@ def install_wheel(
612
611
# type: (...) -> None
613
612
with TempDirectory (
614
613
path = _temp_dir_for_testing , kind = "unpacked-wheel"
615
- ) as unpacked_dir :
614
+ ) as unpacked_dir , ZipFile ( wheel_path , allowZip64 = True ) as z :
616
615
unpack_file (wheel_path , unpacked_dir .path )
617
616
install_unpacked_wheel (
618
617
name = name ,
619
618
wheeldir = unpacked_dir .path ,
619
+ wheel_zip = z ,
620
620
scheme = scheme ,
621
621
req_description = req_description ,
622
622
pycompile = pycompile ,
623
623
warn_script_location = warn_script_location ,
624
624
)
625
625
626
626
627
+ def parse_wheel (wheel_zip , name ):
628
+ # type: (ZipFile, str) -> Tuple[str, Message]
629
+ """Extract information from the provided wheel, ensuring it meets basic
630
+ standards.
631
+
632
+ Returns the name of the .dist-info directory and the parsed WHEEL metadata.
633
+ """
634
+ try :
635
+ info_dir = wheel_dist_info_dir (wheel_zip , name )
636
+ metadata = wheel_metadata (wheel_zip , info_dir )
637
+ version = wheel_version (metadata )
638
+ except UnsupportedWheel as e :
639
+ raise UnsupportedWheel (
640
+ "{} has an invalid wheel, {}" .format (name , str (e ))
641
+ )
642
+
643
+ check_compatibility (version , name )
644
+
645
+ return info_dir , metadata
646
+
647
+
627
648
def wheel_dist_info_dir (source , name ):
628
- # type: (str , str) -> str
649
+ # type: (ZipFile , str) -> str
629
650
"""Returns the name of the contained .dist-info directory.
630
651
631
652
Raises AssertionError or UnsupportedWheel if not found, >1 found, or
632
653
it doesn't match the provided name.
633
654
"""
634
- subdirs = os .listdir (source )
655
+ # Zip file path separators must be /
656
+ subdirs = list (set (p .split ("/" )[0 ] for p in source .namelist ()))
657
+
635
658
info_dirs = [s for s in subdirs if s .endswith ('.dist-info' )]
636
659
637
660
if not info_dirs :
@@ -655,19 +678,26 @@ def wheel_dist_info_dir(source, name):
655
678
)
656
679
)
657
680
658
- return info_dir
681
+ # Zip file paths can be unicode or str depending on the zip entry flags,
682
+ # so normalize it.
683
+ return ensure_str (info_dir )
659
684
660
685
661
686
def wheel_metadata (source , dist_info_dir ):
662
- # type: (str , str) -> Message
687
+ # type: (ZipFile , str) -> Message
663
688
"""Return the WHEEL metadata of an extracted wheel, if possible.
664
689
Otherwise, raise UnsupportedWheel.
665
690
"""
666
691
try :
667
- with open (os .path .join (source , dist_info_dir , "WHEEL" ), "rb" ) as f :
668
- wheel_text = ensure_str (f .read ())
669
- except (IOError , OSError ) as e :
692
+ # Zip file path separators must be /
693
+ wheel_contents = source .read ("{}/WHEEL" .format (dist_info_dir ))
694
+ # BadZipFile for general corruption, KeyError for missing entry,
695
+ # and RuntimeError for password-protected files
696
+ except (BadZipFile , KeyError , RuntimeError ) as e :
670
697
raise UnsupportedWheel ("could not read WHEEL file: {!r}" .format (e ))
698
+
699
+ try :
700
+ wheel_text = ensure_str (wheel_contents )
671
701
except UnicodeDecodeError as e :
672
702
raise UnsupportedWheel ("error decoding WHEEL: {!r}" .format (e ))
673
703
0 commit comments