(root)/
Python-3.12.0/
Lib/
zipimport.py
       1  """zipimport provides support for importing Python modules from Zip archives.
       2  
       3  This module exports three objects:
       4  - zipimporter: a class; its constructor takes a path to a Zip archive.
       5  - ZipImportError: exception raised by zipimporter objects. It's a
       6    subclass of ImportError, so it can be caught as ImportError, too.
       7  - _zip_directory_cache: a dict, mapping archive paths to zip directory
       8    info dicts, as used in zipimporter._files.
       9  
      10  It is usually not needed to use the zipimport module explicitly; it is
      11  used by the builtin import mechanism for sys.path items that are paths
      12  to Zip archives.
      13  """
      14  
      15  #from importlib import _bootstrap_external
      16  #from importlib import _bootstrap  # for _verbose_message
      17  import _frozen_importlib_external as _bootstrap_external
      18  from _frozen_importlib_external import _unpack_uint16, _unpack_uint32
      19  import _frozen_importlib as _bootstrap  # for _verbose_message
      20  import _imp  # for check_hash_based_pycs
      21  import _io  # for open
      22  import marshal  # for loads
      23  import sys  # for modules
      24  import time  # for mktime
      25  import _warnings  # For warn()
      26  
      27  __all__ = ['ZipImportError', 'zipimporter']
      28  
      29  
      30  path_sep = _bootstrap_external.path_sep
      31  alt_path_sep = _bootstrap_external.path_separators[1:]
      32  
      33  
      34  class ESC[4;38;5;81mZipImportError(ESC[4;38;5;149mImportError):
      35      pass
      36  
      37  # _read_directory() cache
      38  _zip_directory_cache = {}
      39  
      40  _module_type = type(sys)
      41  
      42  END_CENTRAL_DIR_SIZE = 22
      43  STRING_END_ARCHIVE = b'PK\x05\x06'
      44  MAX_COMMENT_LEN = (1 << 16) - 1
      45  
      46  class ESC[4;38;5;81mzipimporter(ESC[4;38;5;149m_bootstrap_externalESC[4;38;5;149m.ESC[4;38;5;149m_LoaderBasics):
      47      """zipimporter(archivepath) -> zipimporter object
      48  
      49      Create a new zipimporter instance. 'archivepath' must be a path to
      50      a zipfile, or to a specific path inside a zipfile. For example, it can be
      51      '/tmp/myimport.zip', or '/tmp/myimport.zip/mydirectory', if mydirectory is a
      52      valid directory inside the archive.
      53  
      54      'ZipImportError is raised if 'archivepath' doesn't point to a valid Zip
      55      archive.
      56  
      57      The 'archive' attribute of zipimporter objects contains the name of the
      58      zipfile targeted.
      59      """
      60  
      61      # Split the "subdirectory" from the Zip archive path, lookup a matching
      62      # entry in sys.path_importer_cache, fetch the file directory from there
      63      # if found, or else read it from the archive.
      64      def __init__(self, path):
      65          if not isinstance(path, str):
      66              raise TypeError(f"expected str, not {type(path)!r}")
      67          if not path:
      68              raise ZipImportError('archive path is empty', path=path)
      69          if alt_path_sep:
      70              path = path.replace(alt_path_sep, path_sep)
      71  
      72          prefix = []
      73          while True:
      74              try:
      75                  st = _bootstrap_external._path_stat(path)
      76              except (OSError, ValueError):
      77                  # On Windows a ValueError is raised for too long paths.
      78                  # Back up one path element.
      79                  dirname, basename = _bootstrap_external._path_split(path)
      80                  if dirname == path:
      81                      raise ZipImportError('not a Zip file', path=path)
      82                  path = dirname
      83                  prefix.append(basename)
      84              else:
      85                  # it exists
      86                  if (st.st_mode & 0o170000) != 0o100000:  # stat.S_ISREG
      87                      # it's a not file
      88                      raise ZipImportError('not a Zip file', path=path)
      89                  break
      90  
      91          try:
      92              files = _zip_directory_cache[path]
      93          except KeyError:
      94              files = _read_directory(path)
      95              _zip_directory_cache[path] = files
      96          self._files = files
      97          self.archive = path
      98          # a prefix directory following the ZIP file path.
      99          self.prefix = _bootstrap_external._path_join(*prefix[::-1])
     100          if self.prefix:
     101              self.prefix += path_sep
     102  
     103  
     104      def find_spec(self, fullname, target=None):
     105          """Create a ModuleSpec for the specified module.
     106  
     107          Returns None if the module cannot be found.
     108          """
     109          module_info = _get_module_info(self, fullname)
     110          if module_info is not None:
     111              return _bootstrap.spec_from_loader(fullname, self, is_package=module_info)
     112          else:
     113              # Not a module or regular package. See if this is a directory, and
     114              # therefore possibly a portion of a namespace package.
     115  
     116              # We're only interested in the last path component of fullname
     117              # earlier components are recorded in self.prefix.
     118              modpath = _get_module_path(self, fullname)
     119              if _is_dir(self, modpath):
     120                  # This is possibly a portion of a namespace
     121                  # package. Return the string representing its path,
     122                  # without a trailing separator.
     123                  path = f'{self.archive}{path_sep}{modpath}'
     124                  spec = _bootstrap.ModuleSpec(name=fullname, loader=None,
     125                                               is_package=True)
     126                  spec.submodule_search_locations.append(path)
     127                  return spec
     128              else:
     129                  return None
     130  
     131      def get_code(self, fullname):
     132          """get_code(fullname) -> code object.
     133  
     134          Return the code object for the specified module. Raise ZipImportError
     135          if the module couldn't be imported.
     136          """
     137          code, ispackage, modpath = _get_module_code(self, fullname)
     138          return code
     139  
     140  
     141      def get_data(self, pathname):
     142          """get_data(pathname) -> string with file data.
     143  
     144          Return the data associated with 'pathname'. Raise OSError if
     145          the file wasn't found.
     146          """
     147          if alt_path_sep:
     148              pathname = pathname.replace(alt_path_sep, path_sep)
     149  
     150          key = pathname
     151          if pathname.startswith(self.archive + path_sep):
     152              key = pathname[len(self.archive + path_sep):]
     153  
     154          try:
     155              toc_entry = self._files[key]
     156          except KeyError:
     157              raise OSError(0, '', key)
     158          return _get_data(self.archive, toc_entry)
     159  
     160  
     161      # Return a string matching __file__ for the named module
     162      def get_filename(self, fullname):
     163          """get_filename(fullname) -> filename string.
     164  
     165          Return the filename for the specified module or raise ZipImportError
     166          if it couldn't be imported.
     167          """
     168          # Deciding the filename requires working out where the code
     169          # would come from if the module was actually loaded
     170          code, ispackage, modpath = _get_module_code(self, fullname)
     171          return modpath
     172  
     173  
     174      def get_source(self, fullname):
     175          """get_source(fullname) -> source string.
     176  
     177          Return the source code for the specified module. Raise ZipImportError
     178          if the module couldn't be found, return None if the archive does
     179          contain the module, but has no source for it.
     180          """
     181          mi = _get_module_info(self, fullname)
     182          if mi is None:
     183              raise ZipImportError(f"can't find module {fullname!r}", name=fullname)
     184  
     185          path = _get_module_path(self, fullname)
     186          if mi:
     187              fullpath = _bootstrap_external._path_join(path, '__init__.py')
     188          else:
     189              fullpath = f'{path}.py'
     190  
     191          try:
     192              toc_entry = self._files[fullpath]
     193          except KeyError:
     194              # we have the module, but no source
     195              return None
     196          return _get_data(self.archive, toc_entry).decode()
     197  
     198  
     199      # Return a bool signifying whether the module is a package or not.
     200      def is_package(self, fullname):
     201          """is_package(fullname) -> bool.
     202  
     203          Return True if the module specified by fullname is a package.
     204          Raise ZipImportError if the module couldn't be found.
     205          """
     206          mi = _get_module_info(self, fullname)
     207          if mi is None:
     208              raise ZipImportError(f"can't find module {fullname!r}", name=fullname)
     209          return mi
     210  
     211  
     212      # Load and return the module named by 'fullname'.
     213      def load_module(self, fullname):
     214          """load_module(fullname) -> module.
     215  
     216          Load the module specified by 'fullname'. 'fullname' must be the
     217          fully qualified (dotted) module name. It returns the imported
     218          module, or raises ZipImportError if it could not be imported.
     219  
     220          Deprecated since Python 3.10. Use exec_module() instead.
     221          """
     222          msg = ("zipimport.zipimporter.load_module() is deprecated and slated for "
     223                 "removal in Python 3.12; use exec_module() instead")
     224          _warnings.warn(msg, DeprecationWarning)
     225          code, ispackage, modpath = _get_module_code(self, fullname)
     226          mod = sys.modules.get(fullname)
     227          if mod is None or not isinstance(mod, _module_type):
     228              mod = _module_type(fullname)
     229              sys.modules[fullname] = mod
     230          mod.__loader__ = self
     231  
     232          try:
     233              if ispackage:
     234                  # add __path__ to the module *before* the code gets
     235                  # executed
     236                  path = _get_module_path(self, fullname)
     237                  fullpath = _bootstrap_external._path_join(self.archive, path)
     238                  mod.__path__ = [fullpath]
     239  
     240              if not hasattr(mod, '__builtins__'):
     241                  mod.__builtins__ = __builtins__
     242              _bootstrap_external._fix_up_module(mod.__dict__, fullname, modpath)
     243              exec(code, mod.__dict__)
     244          except:
     245              del sys.modules[fullname]
     246              raise
     247  
     248          try:
     249              mod = sys.modules[fullname]
     250          except KeyError:
     251              raise ImportError(f'Loaded module {fullname!r} not found in sys.modules')
     252          _bootstrap._verbose_message('import {} # loaded from Zip {}', fullname, modpath)
     253          return mod
     254  
     255  
     256      def get_resource_reader(self, fullname):
     257          """Return the ResourceReader for a package in a zip file.
     258  
     259          If 'fullname' is a package within the zip file, return the
     260          'ResourceReader' object for the package.  Otherwise return None.
     261          """
     262          try:
     263              if not self.is_package(fullname):
     264                  return None
     265          except ZipImportError:
     266              return None
     267          from importlib.readers import ZipReader
     268          return ZipReader(self, fullname)
     269  
     270  
     271      def invalidate_caches(self):
     272          """Reload the file data of the archive path."""
     273          try:
     274              self._files = _read_directory(self.archive)
     275              _zip_directory_cache[self.archive] = self._files
     276          except ZipImportError:
     277              _zip_directory_cache.pop(self.archive, None)
     278              self._files = {}
     279  
     280  
     281      def __repr__(self):
     282          return f'<zipimporter object "{self.archive}{path_sep}{self.prefix}">'
     283  
     284  
     285  # _zip_searchorder defines how we search for a module in the Zip
     286  # archive: we first search for a package __init__, then for
     287  # non-package .pyc, and .py entries. The .pyc entries
     288  # are swapped by initzipimport() if we run in optimized mode. Also,
     289  # '/' is replaced by path_sep there.
     290  _zip_searchorder = (
     291      (path_sep + '__init__.pyc', True, True),
     292      (path_sep + '__init__.py', False, True),
     293      ('.pyc', True, False),
     294      ('.py', False, False),
     295  )
     296  
     297  # Given a module name, return the potential file path in the
     298  # archive (without extension).
     299  def _get_module_path(self, fullname):
     300      return self.prefix + fullname.rpartition('.')[2]
     301  
     302  # Does this path represent a directory?
     303  def _is_dir(self, path):
     304      # See if this is a "directory". If so, it's eligible to be part
     305      # of a namespace package. We test by seeing if the name, with an
     306      # appended path separator, exists.
     307      dirpath = path + path_sep
     308      # If dirpath is present in self._files, we have a directory.
     309      return dirpath in self._files
     310  
     311  # Return some information about a module.
     312  def _get_module_info(self, fullname):
     313      path = _get_module_path(self, fullname)
     314      for suffix, isbytecode, ispackage in _zip_searchorder:
     315          fullpath = path + suffix
     316          if fullpath in self._files:
     317              return ispackage
     318      return None
     319  
     320  
     321  # implementation
     322  
     323  # _read_directory(archive) -> files dict (new reference)
     324  #
     325  # Given a path to a Zip archive, build a dict, mapping file names
     326  # (local to the archive, using SEP as a separator) to toc entries.
     327  #
     328  # A toc_entry is a tuple:
     329  #
     330  # (__file__,        # value to use for __file__, available for all files,
     331  #                   # encoded to the filesystem encoding
     332  #  compress,        # compression kind; 0 for uncompressed
     333  #  data_size,       # size of compressed data on disk
     334  #  file_size,       # size of decompressed data
     335  #  file_offset,     # offset of file header from start of archive
     336  #  time,            # mod time of file (in dos format)
     337  #  date,            # mod data of file (in dos format)
     338  #  crc,             # crc checksum of the data
     339  # )
     340  #
     341  # Directories can be recognized by the trailing path_sep in the name,
     342  # data_size and file_offset are 0.
     343  def _read_directory(archive):
     344      try:
     345          fp = _io.open_code(archive)
     346      except OSError:
     347          raise ZipImportError(f"can't open Zip file: {archive!r}", path=archive)
     348  
     349      with fp:
     350          # GH-87235: On macOS all file descriptors for /dev/fd/N share the same
     351          # file offset, reset the file offset after scanning the zipfile diretory
     352          # to not cause problems when some runs 'python3 /dev/fd/9 9<some_script'
     353          start_offset = fp.tell()
     354          try:
     355              try:
     356                  fp.seek(-END_CENTRAL_DIR_SIZE, 2)
     357                  header_position = fp.tell()
     358                  buffer = fp.read(END_CENTRAL_DIR_SIZE)
     359              except OSError:
     360                  raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive)
     361              if len(buffer) != END_CENTRAL_DIR_SIZE:
     362                  raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive)
     363              if buffer[:4] != STRING_END_ARCHIVE:
     364                  # Bad: End of Central Dir signature
     365                  # Check if there's a comment.
     366                  try:
     367                      fp.seek(0, 2)
     368                      file_size = fp.tell()
     369                  except OSError:
     370                      raise ZipImportError(f"can't read Zip file: {archive!r}",
     371                                           path=archive)
     372                  max_comment_start = max(file_size - MAX_COMMENT_LEN -
     373                                          END_CENTRAL_DIR_SIZE, 0)
     374                  try:
     375                      fp.seek(max_comment_start)
     376                      data = fp.read()
     377                  except OSError:
     378                      raise ZipImportError(f"can't read Zip file: {archive!r}",
     379                                           path=archive)
     380                  pos = data.rfind(STRING_END_ARCHIVE)
     381                  if pos < 0:
     382                      raise ZipImportError(f'not a Zip file: {archive!r}',
     383                                           path=archive)
     384                  buffer = data[pos:pos+END_CENTRAL_DIR_SIZE]
     385                  if len(buffer) != END_CENTRAL_DIR_SIZE:
     386                      raise ZipImportError(f"corrupt Zip file: {archive!r}",
     387                                           path=archive)
     388                  header_position = file_size - len(data) + pos
     389  
     390              header_size = _unpack_uint32(buffer[12:16])
     391              header_offset = _unpack_uint32(buffer[16:20])
     392              if header_position < header_size:
     393                  raise ZipImportError(f'bad central directory size: {archive!r}', path=archive)
     394              if header_position < header_offset:
     395                  raise ZipImportError(f'bad central directory offset: {archive!r}', path=archive)
     396              header_position -= header_size
     397              arc_offset = header_position - header_offset
     398              if arc_offset < 0:
     399                  raise ZipImportError(f'bad central directory size or offset: {archive!r}', path=archive)
     400  
     401              files = {}
     402              # Start of Central Directory
     403              count = 0
     404              try:
     405                  fp.seek(header_position)
     406              except OSError:
     407                  raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive)
     408              while True:
     409                  buffer = fp.read(46)
     410                  if len(buffer) < 4:
     411                      raise EOFError('EOF read where not expected')
     412                  # Start of file header
     413                  if buffer[:4] != b'PK\x01\x02':
     414                      break                                # Bad: Central Dir File Header
     415                  if len(buffer) != 46:
     416                      raise EOFError('EOF read where not expected')
     417                  flags = _unpack_uint16(buffer[8:10])
     418                  compress = _unpack_uint16(buffer[10:12])
     419                  time = _unpack_uint16(buffer[12:14])
     420                  date = _unpack_uint16(buffer[14:16])
     421                  crc = _unpack_uint32(buffer[16:20])
     422                  data_size = _unpack_uint32(buffer[20:24])
     423                  file_size = _unpack_uint32(buffer[24:28])
     424                  name_size = _unpack_uint16(buffer[28:30])
     425                  extra_size = _unpack_uint16(buffer[30:32])
     426                  comment_size = _unpack_uint16(buffer[32:34])
     427                  file_offset = _unpack_uint32(buffer[42:46])
     428                  header_size = name_size + extra_size + comment_size
     429                  if file_offset > header_offset:
     430                      raise ZipImportError(f'bad local header offset: {archive!r}', path=archive)
     431                  file_offset += arc_offset
     432  
     433                  try:
     434                      name = fp.read(name_size)
     435                  except OSError:
     436                      raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive)
     437                  if len(name) != name_size:
     438                      raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive)
     439                  # On Windows, calling fseek to skip over the fields we don't use is
     440                  # slower than reading the data because fseek flushes stdio's
     441                  # internal buffers.    See issue #8745.
     442                  try:
     443                      if len(fp.read(header_size - name_size)) != header_size - name_size:
     444                          raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive)
     445                  except OSError:
     446                      raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive)
     447  
     448                  if flags & 0x800:
     449                      # UTF-8 file names extension
     450                      name = name.decode()
     451                  else:
     452                      # Historical ZIP filename encoding
     453                      try:
     454                          name = name.decode('ascii')
     455                      except UnicodeDecodeError:
     456                          name = name.decode('latin1').translate(cp437_table)
     457  
     458                  name = name.replace('/', path_sep)
     459                  path = _bootstrap_external._path_join(archive, name)
     460                  t = (path, compress, data_size, file_size, file_offset, time, date, crc)
     461                  files[name] = t
     462                  count += 1
     463          finally:
     464              fp.seek(start_offset)
     465      _bootstrap._verbose_message('zipimport: found {} names in {!r}', count, archive)
     466      return files
     467  
     468  # During bootstrap, we may need to load the encodings
     469  # package from a ZIP file. But the cp437 encoding is implemented
     470  # in Python in the encodings package.
     471  #
     472  # Break out of this dependency by using the translation table for
     473  # the cp437 encoding.
     474  cp437_table = (
     475      # ASCII part, 8 rows x 16 chars
     476      '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f'
     477      '\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f'
     478      ' !"#$%&\'()*+,-./'
     479      '0123456789:;<=>?'
     480      '@ABCDEFGHIJKLMNO'
     481      'PQRSTUVWXYZ[\\]^_'
     482      '`abcdefghijklmno'
     483      'pqrstuvwxyz{|}~\x7f'
     484      # non-ASCII part, 16 rows x 8 chars
     485      '\xc7\xfc\xe9\xe2\xe4\xe0\xe5\xe7'
     486      '\xea\xeb\xe8\xef\xee\xec\xc4\xc5'
     487      '\xc9\xe6\xc6\xf4\xf6\xf2\xfb\xf9'
     488      '\xff\xd6\xdc\xa2\xa3\xa5\u20a7\u0192'
     489      '\xe1\xed\xf3\xfa\xf1\xd1\xaa\xba'
     490      '\xbf\u2310\xac\xbd\xbc\xa1\xab\xbb'
     491      '\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556'
     492      '\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510'
     493      '\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f'
     494      '\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567'
     495      '\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b'
     496      '\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580'
     497      '\u03b1\xdf\u0393\u03c0\u03a3\u03c3\xb5\u03c4'
     498      '\u03a6\u0398\u03a9\u03b4\u221e\u03c6\u03b5\u2229'
     499      '\u2261\xb1\u2265\u2264\u2320\u2321\xf7\u2248'
     500      '\xb0\u2219\xb7\u221a\u207f\xb2\u25a0\xa0'
     501  )
     502  
     503  _importing_zlib = False
     504  
     505  # Return the zlib.decompress function object, or NULL if zlib couldn't
     506  # be imported. The function is cached when found, so subsequent calls
     507  # don't import zlib again.
     508  def _get_decompress_func():
     509      global _importing_zlib
     510      if _importing_zlib:
     511          # Someone has a zlib.py[co] in their Zip file
     512          # let's avoid a stack overflow.
     513          _bootstrap._verbose_message('zipimport: zlib UNAVAILABLE')
     514          raise ZipImportError("can't decompress data; zlib not available")
     515  
     516      _importing_zlib = True
     517      try:
     518          from zlib import decompress
     519      except Exception:
     520          _bootstrap._verbose_message('zipimport: zlib UNAVAILABLE')
     521          raise ZipImportError("can't decompress data; zlib not available")
     522      finally:
     523          _importing_zlib = False
     524  
     525      _bootstrap._verbose_message('zipimport: zlib available')
     526      return decompress
     527  
     528  # Given a path to a Zip file and a toc_entry, return the (uncompressed) data.
     529  def _get_data(archive, toc_entry):
     530      datapath, compress, data_size, file_size, file_offset, time, date, crc = toc_entry
     531      if data_size < 0:
     532          raise ZipImportError('negative data size')
     533  
     534      with _io.open_code(archive) as fp:
     535          # Check to make sure the local file header is correct
     536          try:
     537              fp.seek(file_offset)
     538          except OSError:
     539              raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive)
     540          buffer = fp.read(30)
     541          if len(buffer) != 30:
     542              raise EOFError('EOF read where not expected')
     543  
     544          if buffer[:4] != b'PK\x03\x04':
     545              # Bad: Local File Header
     546              raise ZipImportError(f'bad local file header: {archive!r}', path=archive)
     547  
     548          name_size = _unpack_uint16(buffer[26:28])
     549          extra_size = _unpack_uint16(buffer[28:30])
     550          header_size = 30 + name_size + extra_size
     551          file_offset += header_size  # Start of file data
     552          try:
     553              fp.seek(file_offset)
     554          except OSError:
     555              raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive)
     556          raw_data = fp.read(data_size)
     557          if len(raw_data) != data_size:
     558              raise OSError("zipimport: can't read data")
     559  
     560      if compress == 0:
     561          # data is not compressed
     562          return raw_data
     563  
     564      # Decompress with zlib
     565      try:
     566          decompress = _get_decompress_func()
     567      except Exception:
     568          raise ZipImportError("can't decompress data; zlib not available")
     569      return decompress(raw_data, -15)
     570  
     571  
     572  # Lenient date/time comparison function. The precision of the mtime
     573  # in the archive is lower than the mtime stored in a .pyc: we
     574  # must allow a difference of at most one second.
     575  def _eq_mtime(t1, t2):
     576      # dostime only stores even seconds, so be lenient
     577      return abs(t1 - t2) <= 1
     578  
     579  
     580  # Given the contents of a .py[co] file, unmarshal the data
     581  # and return the code object. Raises ImportError it the magic word doesn't
     582  # match, or if the recorded .py[co] metadata does not match the source.
     583  def _unmarshal_code(self, pathname, fullpath, fullname, data):
     584      exc_details = {
     585          'name': fullname,
     586          'path': fullpath,
     587      }
     588  
     589      flags = _bootstrap_external._classify_pyc(data, fullname, exc_details)
     590  
     591      hash_based = flags & 0b1 != 0
     592      if hash_based:
     593          check_source = flags & 0b10 != 0
     594          if (_imp.check_hash_based_pycs != 'never' and
     595                  (check_source or _imp.check_hash_based_pycs == 'always')):
     596              source_bytes = _get_pyc_source(self, fullpath)
     597              if source_bytes is not None:
     598                  source_hash = _imp.source_hash(
     599                      _bootstrap_external._RAW_MAGIC_NUMBER,
     600                      source_bytes,
     601                  )
     602  
     603                  _bootstrap_external._validate_hash_pyc(
     604                      data, source_hash, fullname, exc_details)
     605      else:
     606          source_mtime, source_size = \
     607              _get_mtime_and_size_of_source(self, fullpath)
     608  
     609          if source_mtime:
     610              # We don't use _bootstrap_external._validate_timestamp_pyc
     611              # to allow for a more lenient timestamp check.
     612              if (not _eq_mtime(_unpack_uint32(data[8:12]), source_mtime) or
     613                      _unpack_uint32(data[12:16]) != source_size):
     614                  _bootstrap._verbose_message(
     615                      f'bytecode is stale for {fullname!r}')
     616                  return None
     617  
     618      code = marshal.loads(data[16:])
     619      if not isinstance(code, _code_type):
     620          raise TypeError(f'compiled module {pathname!r} is not a code object')
     621      return code
     622  
     623  _code_type = type(_unmarshal_code.__code__)
     624  
     625  
     626  # Replace any occurrences of '\r\n?' in the input string with '\n'.
     627  # This converts DOS and Mac line endings to Unix line endings.
     628  def _normalize_line_endings(source):
     629      source = source.replace(b'\r\n', b'\n')
     630      source = source.replace(b'\r', b'\n')
     631      return source
     632  
     633  # Given a string buffer containing Python source code, compile it
     634  # and return a code object.
     635  def _compile_source(pathname, source):
     636      source = _normalize_line_endings(source)
     637      return compile(source, pathname, 'exec', dont_inherit=True)
     638  
     639  # Convert the date/time values found in the Zip archive to a value
     640  # that's compatible with the time stamp stored in .pyc files.
     641  def _parse_dostime(d, t):
     642      return time.mktime((
     643          (d >> 9) + 1980,    # bits 9..15: year
     644          (d >> 5) & 0xF,     # bits 5..8: month
     645          d & 0x1F,           # bits 0..4: day
     646          t >> 11,            # bits 11..15: hours
     647          (t >> 5) & 0x3F,    # bits 8..10: minutes
     648          (t & 0x1F) * 2,     # bits 0..7: seconds / 2
     649          -1, -1, -1))
     650  
     651  # Given a path to a .pyc file in the archive, return the
     652  # modification time of the matching .py file and its size,
     653  # or (0, 0) if no source is available.
     654  def _get_mtime_and_size_of_source(self, path):
     655      try:
     656          # strip 'c' or 'o' from *.py[co]
     657          assert path[-1:] in ('c', 'o')
     658          path = path[:-1]
     659          toc_entry = self._files[path]
     660          # fetch the time stamp of the .py file for comparison
     661          # with an embedded pyc time stamp
     662          time = toc_entry[5]
     663          date = toc_entry[6]
     664          uncompressed_size = toc_entry[3]
     665          return _parse_dostime(date, time), uncompressed_size
     666      except (KeyError, IndexError, TypeError):
     667          return 0, 0
     668  
     669  
     670  # Given a path to a .pyc file in the archive, return the
     671  # contents of the matching .py file, or None if no source
     672  # is available.
     673  def _get_pyc_source(self, path):
     674      # strip 'c' or 'o' from *.py[co]
     675      assert path[-1:] in ('c', 'o')
     676      path = path[:-1]
     677  
     678      try:
     679          toc_entry = self._files[path]
     680      except KeyError:
     681          return None
     682      else:
     683          return _get_data(self.archive, toc_entry)
     684  
     685  
     686  # Get the code object associated with the module specified by
     687  # 'fullname'.
     688  def _get_module_code(self, fullname):
     689      path = _get_module_path(self, fullname)
     690      import_error = None
     691      for suffix, isbytecode, ispackage in _zip_searchorder:
     692          fullpath = path + suffix
     693          _bootstrap._verbose_message('trying {}{}{}', self.archive, path_sep, fullpath, verbosity=2)
     694          try:
     695              toc_entry = self._files[fullpath]
     696          except KeyError:
     697              pass
     698          else:
     699              modpath = toc_entry[0]
     700              data = _get_data(self.archive, toc_entry)
     701              code = None
     702              if isbytecode:
     703                  try:
     704                      code = _unmarshal_code(self, modpath, fullpath, fullname, data)
     705                  except ImportError as exc:
     706                      import_error = exc
     707              else:
     708                  code = _compile_source(modpath, data)
     709              if code is None:
     710                  # bad magic number or non-matching mtime
     711                  # in byte code, try next
     712                  continue
     713              modpath = toc_entry[0]
     714              return code, ispackage, modpath
     715      else:
     716          if import_error:
     717              msg = f"module load failed: {import_error}"
     718              raise ZipImportError(msg, name=fullname) from import_error
     719          else:
     720              raise ZipImportError(f"can't find module {fullname!r}", name=fullname)