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)