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