App Engine Python SDK version 1.7.7
[gae.git] / python / google / appengine / tools / devappserver2 / python / sandbox.py
blobad8d93f370f1f5498a18e0dfc9cd8f6d4f607ced
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """A sandbox implementation that emulates production App Engine."""
20 import __builtin__
21 import imp
22 import os
23 import re
24 import sys
25 import traceback
26 import types
28 import google
30 import protorpc
32 from google.appengine import dist
33 from google.appengine.api import app_logging
34 from google.appengine.api.logservice import logservice
35 from google.appengine import dist27 as dist27
36 from google.appengine.ext.remote_api import remote_api_stub
37 from google.appengine.runtime import request_environment
38 from google.appengine.tools.devappserver2.python import request_state
39 from google.appengine.tools.devappserver2.python import stubs
41 # Needed to handle source file encoding
42 CODING_MAGIC_COMMENT_RE = re.compile('coding[:=]\s*([-\w.]+)')
43 DEFAULT_ENCODING = 'ascii'
45 _C_MODULES = frozenset(['numpy', 'Crypto', 'lxml', 'PIL'])
47 NAME_TO_CMODULE_WHITELIST_REGEX = {
48 'numpy': re.compile(r'numpy(\..*)?$'),
49 'pycrypto': re.compile(r'Crypto(\..*)?$'),
50 'lxml': re.compile(r'lxml(\..*)?$'),
51 'PIL': re.compile(r'(PIL(\..*)?|_imaging|_imagingft|_imagingmath)$'),
54 # Maps App Engine third-party library names to the Python package name for
55 # libraries whose names differ from the package names.
56 _THIRD_PARTY_LIBRARY_NAME_OVERRIDES = {
57 'pycrypto': 'Crypto'
60 # The location of third-party libraries will be different for the packaged SDK.
61 _THIRD_PARTY_LIBRARY_FORMAT_STRING = (
62 'lib/%(name)s-%(version)s')
64 # Store all the modules removed from sys.modules so they don't get cleaned up.
65 _removed_modules = []
68 def _make_request_id_aware_start_new_thread(base_start_new_thread):
69 """Returns a replacement for start_new_thread that inherits request id.
71 Returns a function with an interface that matches thread.start_new_thread
72 where the new thread inherits the request id of the current thread. The
73 request id is used by the Remote API to associate API calls with the HTTP
74 request that provoked them.
76 Args:
77 base_start_new_thread: The thread.start_new_thread function to call to
78 create a new thread.
80 Returns:
81 A replacement for start_new_thread.
82 """
84 def _start_new_thread(target, args, kw=None):
85 if kw is None:
86 kw = {}
88 request_id = remote_api_stub.RemoteStub._GetRequestId()
89 request = request_state.get_request_state(request_id)
91 def _run():
92 try:
93 remote_api_stub.RemoteStub._SetRequestId(request_id)
94 request.start_thread()
95 target(*args, **kw)
96 finally:
97 request_environment.current_request.Clear()
98 request.end_thread()
99 return base_start_new_thread(_run, ())
100 return _start_new_thread
103 def enable_sandbox(config):
104 """Enable the sandbox based on the configuration.
106 This includes installing import hooks to restrict access to C modules and
107 stub out functions that are not implemented in production, replacing the file
108 builtins with read-only versions and add enabled libraries to the path.
110 Args:
111 config: The runtime_config_pb2.Config to use to configure the sandbox.
114 modules = [os, traceback, google, protorpc]
115 c_module = _find_shared_object_c_module()
116 if c_module:
117 modules.append(c_module)
118 module_paths = [module.__file__ for module in modules]
119 module_paths.extend([os.path.realpath(module.__file__) for module in modules])
120 python_lib_paths = [config.application_root]
121 for path in sys.path:
122 if any(module_path.startswith(path) for module_path in module_paths):
123 python_lib_paths.append(path)
124 python_lib_paths.extend(_enable_libraries(config.libraries))
125 for name in list(sys.modules):
126 if not _should_keep_module(name):
127 _removed_modules.append(sys.modules[name])
128 del sys.modules[name]
129 path_override_hook = PathOverrideImportHook(
130 set(_THIRD_PARTY_LIBRARY_NAME_OVERRIDES.get(lib.name, lib.name)
131 for lib in config.libraries).intersection(_C_MODULES))
132 python_lib_paths.extend(path_override_hook.extra_sys_paths)
133 stubs.FakeFile.set_allowed_paths(config.application_root,
134 python_lib_paths[1:] +
135 path_override_hook.extra_accessible_paths)
136 stubs.FakeFile.set_skip_files(config.skip_files)
137 stubs.FakeFile.set_static_files(config.static_files)
138 __builtin__.file = stubs.FakeFile
139 __builtin__.open = stubs.FakeFile
140 types.FileType = stubs.FakeFile
141 sys.platform = 'linux3'
142 enabled_library_regexes = [
143 NAME_TO_CMODULE_WHITELIST_REGEX[lib.name] for lib in config.libraries
144 if lib.name in NAME_TO_CMODULE_WHITELIST_REGEX]
145 sys.meta_path = [
146 StubModuleImportHook(),
147 ModuleOverrideImportHook(_MODULE_OVERRIDE_POLICIES),
148 BuiltinImportHook(),
149 CModuleImportHook(enabled_library_regexes),
150 path_override_hook,
151 PyCryptoRandomImportHook,
152 PathRestrictingImportHook(enabled_library_regexes)
154 sys.path_importer_cache = {}
155 sys.path = python_lib_paths[:]
157 thread = __import__('thread')
158 __import__('%s.threading' % dist27.__name__)
159 threading = sys.modules['%s.threading' % dist27.__name__]
160 thread.start_new_thread = _make_request_id_aware_start_new_thread(
161 thread.start_new_thread)
162 # This import needs to be after enabling the sandbox so it imports the
163 # sandboxed version of the logging module.
164 from google.appengine.runtime import runtime
165 runtime.PatchStartNewThread(thread)
166 threading._start_new_thread = thread.start_new_thread
168 os.chdir(config.application_root)
169 sandboxed_os = __import__('os')
170 request_environment.PatchOsEnviron(sandboxed_os)
171 os.__dict__.update(sandboxed_os.__dict__)
172 _init_logging(config.stderr_log_level)
175 def _find_shared_object_c_module():
176 for module_name in ['_sqlite3', '_multiprocessing', '_ctypes', 'bz2']:
177 try:
178 module = __import__(module_name)
179 except ImportError:
180 continue
181 else:
182 if hasattr(module, '__file__'):
183 return module
184 return None
187 def _should_keep_module(name):
188 """Returns True if the module should be retained after sandboxing."""
189 return (name in ('__builtin__', 'sys', 'codecs', 'encodings', 'site',
190 'google') or
191 name.startswith('google.') or name.startswith('encodings.') or
193 # Making mysql available is a hack to make the CloudSQL functionality
194 # work.
195 'mysql' in name.lower())
198 def _init_logging(stderr_log_level):
199 logging = __import__('logging')
200 logger = logging.getLogger()
202 console_handler = logging.StreamHandler(sys.stderr)
203 if stderr_log_level == 0:
204 console_handler.setLevel(logging.DEBUG)
205 elif stderr_log_level == 1:
206 console_handler.setLevel(logging.INFO)
207 elif stderr_log_level == 2:
208 console_handler.setLevel(logging.WARNING)
209 elif stderr_log_level == 3:
210 console_handler.setLevel(logging.ERROR)
211 elif stderr_log_level == 4:
212 console_handler.setLevel(logging.CRITICAL)
214 console_handler.setFormatter(logging.Formatter(
215 '%(levelname)-8s %(asctime)s %(filename)s:%(lineno)s] %(message)s'))
216 logger.addHandler(console_handler)
218 logging_stream = request_environment.RequestLocalStream(
219 request_environment.current_request)
220 logger.addHandler(app_logging.AppLogsHandler())
221 logger.setLevel(logging.DEBUG)
222 logservice.logs_buffer = lambda: request_environment.current_request.errors
223 sys.stderr = Tee(sys.stderr, logging_stream)
226 class Tee(object):
227 """A writeable stream that forwards to zero or more streams."""
229 def __init__(self, *streams):
230 self._streams = streams
232 def close(self):
233 for stream in self._streams:
234 stream.close()
236 def flush(self):
237 for stream in self._streams:
238 stream.flush()
240 def write(self, data):
241 for stream in self._streams:
242 stream.write(data)
244 def writelines(self, data):
245 for stream in self._streams:
246 stream.writelines(data)
249 def _enable_libraries(libraries):
250 """Add enabled libraries to the path.
252 Args:
253 libraries: A repeated Config.Library containing the libraries to enable.
255 Returns:
256 A list of paths containing the enabled libraries.
258 library_dirs = []
259 library_pattern = os.path.join(os.path.dirname(
260 os.path.dirname(google.__file__)), _THIRD_PARTY_LIBRARY_FORMAT_STRING)
261 for library in libraries:
262 library_dir = os.path.abspath(
263 library_pattern % {'name': library.name, 'version': library.version})
264 library_dirs.append(library_dir)
265 return library_dirs
268 class BaseImportHook(object):
269 """A base class implementing common import hook functionality.
271 This provides utilities for implementing both the finder and loader parts of
272 the PEP 302 importer protocol and implements the optional extensions to the
273 importer protocol.
276 def _find_module_or_loader(self, submodule_name, fullname, path):
277 """Acts like imp.find_module with support for path hooks.
279 Args:
280 submodule_name: The name of the submodule within its parent package.
281 fullname: The full name of the module to load.
282 path: A list containing the paths to search for the module.
284 Returns:
285 A tuple (source_file, path_name, description, loader) where:
286 source_file: An open file or None.
287 path_name: A str containing the the path to the module.
288 description: A description tuple like the one imp.find_module returns.
289 loader: A PEP 302 compatible path hook. If this is not None, then the
290 other elements will be None.
292 Raises:
293 ImportError: The module could not be imported.
295 for path_entry in path + [None]:
296 result = self._find_path_hook(submodule_name, fullname, path_entry)
297 if result is not None:
298 break
299 else:
300 raise ImportError('No module named %s' % fullname)
301 if isinstance(result, tuple):
302 return result + (None,)
303 else:
304 return None, None, None, result.find_module(fullname)
306 def _find_and_load_module(self, submodule_name, fullname, path):
307 """Finds and loads a module, using a provided search path.
309 Args:
310 submodule_name: The name of the submodule within its parent package.
311 fullname: The full name of the module to load.
312 path: A list containing the paths to search for the module.
314 Returns:
315 The requested module.
317 Raises:
318 ImportError: The module could not be imported.
320 source_file, path_name, description, loader = self._find_module_or_loader(
321 submodule_name, fullname, path)
322 if loader:
323 return loader.load_module(fullname)
324 try:
325 return imp.load_module(fullname, source_file, path_name, description)
326 finally:
327 if source_file:
328 source_file.close()
330 def _find_path_hook(self, submodule, submodule_fullname, path_entry):
331 """Helper for _find_and_load_module to find a module in a path entry.
333 Args:
334 submodule: The last portion of the module name from submodule_fullname.
335 submodule_fullname: The full name of the module to be imported.
336 path_entry: A single sys.path entry, or None representing the builtins.
338 Returns:
339 None if nothing was found, a PEP 302 loader if one was found or a
340 tuple (source_file, path_name, description) where:
341 source_file: An open file of the source file.
342 path_name: A str containing the path to the source file.
343 description: A description tuple to be passed to imp.load_module.
345 if path_entry is None:
346 # This is the magic entry that tells us to look for a built-in module.
347 if submodule_fullname in sys.builtin_module_names:
348 try:
349 result = imp.find_module(submodule)
350 except ImportError:
351 pass
352 else:
353 # Did find_module() find a built-in module? Unpack the result.
354 _, _, description = result
355 _, _, file_type = description
356 if file_type == imp.C_BUILTIN:
357 return result
358 # Skip over this entry if we get this far.
359 return None
361 # It's a regular sys.path entry.
362 try:
363 importer = sys.path_importer_cache[path_entry]
364 except KeyError:
365 # Cache miss; try each path hook in turn.
366 importer = None
367 for hook in sys.path_hooks:
368 try:
369 importer = hook(path_entry)
370 # Success.
371 break
372 except ImportError:
373 # This importer doesn't handle this path entry.
374 pass
375 # Cache the result, whether an importer matched or not.
376 sys.path_importer_cache[path_entry] = importer
378 if importer is None:
379 # No importer. Use the default approach.
380 try:
381 return imp.find_module(submodule, [path_entry])
382 except ImportError:
383 pass
384 else:
385 # Have an importer. Try it.
386 loader = importer.find_module(submodule_fullname)
387 if loader is not None:
388 # This importer knows about this module.
389 return loader
391 # None of the above.
392 return None
394 def _get_parent_package(self, fullname):
395 """Retrieves the parent package of a fully qualified module name.
397 Args:
398 fullname: Full name of the module whose parent should be retrieved (e.g.,
399 foo.bar).
401 Returns:
402 Module instance for the parent or None if there is no parent module.
404 Raises:
405 ImportError: The module's parent could not be found.
407 all_modules = fullname.split('.')
408 parent_module_fullname = '.'.join(all_modules[:-1])
409 if parent_module_fullname:
410 __import__(parent_module_fullname)
411 return sys.modules[parent_module_fullname]
412 return None
414 def _get_parent_search_path(self, fullname):
415 """Determines the search path of a module's parent package.
417 Args:
418 fullname: Full name of the module to look up (e.g., foo.bar).
420 Returns:
421 Tuple (submodule, search_path) where:
422 submodule: The last portion of the module name from fullname (e.g.,
423 if fullname is foo.bar, then this is bar).
424 search_path: List of paths that belong to the parent package's search
425 path or None if there is no parent package.
427 Raises:
428 ImportError exception if the module or its parent could not be found.
430 _, _, submodule = fullname.rpartition('.')
431 parent_package = self._get_parent_package(fullname)
432 search_path = sys.path
433 if parent_package is not None and hasattr(parent_package, '__path__'):
434 search_path = parent_package.__path__
435 return submodule, search_path
437 def _get_module_info(self, fullname):
438 """Determines the path on disk and the search path of a module or package.
440 Args:
441 fullname: Full name of the module to look up (e.g., foo.bar).
443 Returns:
444 Tuple (pathname, search_path, submodule, loader) where:
445 pathname: String containing the full path of the module on disk,
446 or None if the module wasn't loaded from disk (e.g. from a zipfile).
447 search_path: List of paths that belong to the found package's search
448 path or None if found module is not a package.
449 submodule: The relative name of the submodule that's being imported.
450 loader: A PEP 302 compatible path hook. If this is not None, then the
451 other elements will be None.
453 submodule, search_path = self._get_parent_search_path(fullname)
454 _, pathname, description, loader = self._find_module_or_loader(
455 submodule, fullname, search_path)
456 if loader:
457 return None, None, None, loader
458 else:
459 _, _, file_type = description
460 module_search_path = None
461 if file_type == imp.PKG_DIRECTORY:
462 module_search_path = [pathname]
463 pathname = os.path.join(pathname, '__init__%spy' % os.extsep)
464 return pathname, module_search_path, submodule, None
466 def is_package(self, fullname):
467 """Returns whether the module specified by fullname refers to a package.
469 This implements part of the extensions to the PEP 302 importer protocol.
471 Args:
472 fullname: The fullname of the module.
474 Returns:
475 True if fullname refers to a package.
477 submodule, search_path = self._get_parent_search_path(fullname)
478 _, _, description, loader = self._find_module_or_loader(
479 submodule, fullname, search_path)
480 if loader:
481 return loader.is_package(fullname)
482 _, _, file_type = description
483 if file_type == imp.PKG_DIRECTORY:
484 return True
485 return False
487 def get_source(self, fullname):
488 """Returns the source for the module specified by fullname.
490 This implements part of the extensions to the PEP 302 importer protocol.
492 Args:
493 fullname: The fullname of the module.
495 Returns:
496 The source for the module.
498 full_path, _, _, loader = self._get_module_info(fullname)
499 if loader:
500 return loader.get_source(fullname)
501 if full_path is None:
502 return None
503 source_file = open(full_path)
504 try:
505 return source_file.read()
506 finally:
507 source_file.close()
509 def get_code(self, fullname):
510 """Returns the code object for the module specified by fullname.
512 This implements part of the extensions to the PEP 302 importer protocol.
514 Args:
515 fullname: The fullname of the module.
517 Returns:
518 The code object associated the module.
520 full_path, _, _, loader = self._get_module_info(fullname)
521 if loader:
522 return loader.get_code(fullname)
523 if full_path is None:
524 return None
525 source_file = open(full_path)
526 try:
527 source_code = source_file.read()
528 finally:
529 source_file.close()
531 # Check that coding cookie is correct if present, error if not present and
532 # we can't decode with the default of 'ascii'. According to PEP 263 this
533 # coding cookie line must be in the first or second line of the file.
534 encoding = DEFAULT_ENCODING
535 for line in source_code.split('\n', 2)[:2]:
536 matches = CODING_MAGIC_COMMENT_RE.findall(line)
537 if matches:
538 encoding = matches[0].lower()
539 # This may raise up to the user, which is what we want, however we ignore
540 # the output because we don't want to return a unicode version of the code.
541 source_code.decode(encoding)
543 return compile(source_code, full_path, 'exec')
546 class PathOverrideImportHook(BaseImportHook):
547 """An import hook that imports enabled modules from predetermined paths.
549 Imports handled by this hook ignore the paths in sys.path, instead using paths
550 discovered at initialization time.
552 Attributes:
553 extra_sys_paths: A list of paths that should be added to sys.path.
554 extra_accessible_paths: A list of paths that should be accessible by
555 sandboxed code.
558 def __init__(self, modules):
559 self._modules = {}
560 self.extra_accessible_paths = []
561 self.extra_sys_paths = []
562 for module in modules:
563 module_path = self._get_module_path(module)
564 if module_path:
565 self._modules[module] = module_path
566 if isinstance(module_path, str):
567 package_dir = os.path.join(module_path, module)
568 if os.path.isdir(package_dir):
569 if module == 'PIL':
570 self.extra_sys_paths.append(package_dir)
571 else:
572 self.extra_accessible_paths.append(package_dir)
574 def find_module(self, fullname, unused_path=None):
575 return fullname in self._modules and self or None
577 def load_module(self, fullname):
578 if fullname in sys.modules:
579 return sys.modules[fullname]
580 module_path = self._modules[fullname]
581 if hasattr(module_path, 'load_module'):
582 module = module_path.load_module(fullname)
583 else:
584 module = self._find_and_load_module(fullname, fullname, [module_path])
585 module.__loader__ = self
586 return module
588 def _get_module_path(self, fullname):
589 """Returns the directory containing the module or None if not found."""
590 try:
591 _, _, submodule = fullname.rpartition('.')
592 f, filepath, _, loader = self._find_module_or_loader(
593 submodule, fullname, sys.path)
594 except ImportError:
595 return None
596 if f:
597 f.close()
598 if loader:
599 return loader.find_module(fullname)
600 return os.path.dirname(filepath)
603 class ModuleOverridePolicy(object):
604 """A policy for implementing a partial whitelist for a module."""
606 def __init__(self, default_stub=None,
607 whitelist=None,
608 overrides=None,
609 deletes=None,
610 constant_types=(str, int, long, BaseException),
611 default_pass_through=False):
612 self.default_stub = default_stub
613 self.whitelist = whitelist or []
614 self.overrides = overrides or {}
615 self.deletes = deletes or []
616 self.constant_types = constant_types
617 self.default_pass_through = default_pass_through
619 def apply_policy(self, module_dict):
620 """Apply this policy to the provided module dict.
622 In order, one of the following will apply:
623 - Symbols in overrides are set to the override value.
624 - Symbols in deletes are removed.
625 - Whitelisted symbols and symbols with a constant type are unchanged.
626 - If a default stub is set, all other symbols are replaced by it.
627 - If default_pass_through is True, all other symbols are unchanged.
628 - If default_pass_through is False, all other symbols are removed.
630 Args:
631 module_dict: The module dict to be filtered.
633 for symbol in module_dict.keys():
634 if symbol in self.overrides:
635 module_dict[symbol] = self.overrides[symbol]
636 elif symbol in self.deletes:
637 del module_dict[symbol]
638 elif not (symbol in self.whitelist or
639 isinstance(module_dict[symbol], self.constant_types) or
640 (symbol.startswith('__') and symbol.endswith('__'))):
641 if self.default_stub:
642 module_dict[symbol] = self.default_stub
643 elif not self.default_pass_through:
644 del module_dict[symbol]
646 _MODULE_OVERRIDE_POLICIES = {
647 'os': ModuleOverridePolicy(
648 default_stub=stubs.os_error_not_implemented,
649 whitelist=['altsep', 'curdir', 'defpath', 'devnull', 'environ', 'error',
650 'fstat', 'getcwd', 'getcwdu', 'getenv', '_get_exports_list',
651 'name', 'open', 'pardir', 'path', 'pathsep', 'sep',
652 'stat_float_times', 'stat_result', 'strerror', 'sys',
653 'walk'],
654 overrides={
655 'access': stubs.fake_access,
656 'listdir': stubs.RestrictedPathFunction(os.listdir),
657 # Alias lstat() to stat() to match the behavior in production.
658 'lstat': stubs.RestrictedPathFunction(os.stat),
659 'open': stubs.fake_open,
660 'stat': stubs.RestrictedPathFunction(os.stat),
661 'uname': stubs.fake_uname,
662 'getpid': stubs.return_minus_one,
663 'getppid': stubs.return_minus_one,
664 'getpgrp': stubs.return_minus_one,
665 'getgid': stubs.return_minus_one,
666 'getegid': stubs.return_minus_one,
667 'geteuid': stubs.return_minus_one,
668 'getuid': stubs.return_minus_one,
669 'urandom': stubs.fake_urandom,
670 'system': stubs.return_minus_one,
672 deletes=['execv', 'execve']),
673 'signal': ModuleOverridePolicy(overrides={'__doc__': None}),
674 'locale': ModuleOverridePolicy(
675 overrides={'setlocale': stubs.fake_set_locale},
676 default_pass_through=True),
677 'distutils.util': ModuleOverridePolicy(
678 overrides={'get_platform': stubs.fake_get_platform},
679 default_pass_through=True),
680 # TODO: Stub out imp.find_module and friends.
684 class ModuleOverrideImportHook(BaseImportHook):
685 """An import hook that applies a ModuleOverridePolicy to modules."""
687 def __init__(self, policies):
688 super(ModuleOverrideImportHook, self).__init__()
689 self.policies = policies
691 def find_module(self, fullname, unused_path=None):
692 return fullname in self.policies and self or None
694 def load_module(self, fullname):
695 if fullname in sys.modules:
696 return sys.modules[fullname]
697 parent_name, _, submodule_name = fullname.rpartition('.')
698 if parent_name:
699 parent = sys.modules[parent_name]
700 path = getattr(parent, '__path__', sys.path)
701 else:
702 path = sys.path
703 parent = None
704 module = self._find_and_load_module(submodule_name, fullname, path)
705 self.policies[fullname].apply_policy(module.__dict__)
706 module.__loader__ = self
707 sys.modules[fullname] = module
708 return module
711 class StubModuleImportHook(BaseImportHook):
712 """An import hook that replaces entire modules with stubs."""
714 def find_module(self, fullname, unused_path=None):
715 return self if fullname in dist27.MODULE_OVERRIDES else None
717 def load_module(self, fullname):
718 if fullname in sys.modules:
719 return sys.modules[fullname]
720 return self.import_stub_module(fullname)
722 def import_stub_module(self, name):
723 """Import the stub module replacement for the specified module."""
724 # Do the equivalent of
725 # ``from google.appengine.dist import <name>``.
726 providing_dist = dist
727 # When using the Py27 runtime, modules in dist27 have priority.
728 # (They have already been vetted.)
729 if name in dist27.__all__:
730 providing_dist = dist27
731 fullname = '%s.%s' % (providing_dist.__name__, name)
732 __import__(fullname, {}, {})
733 module = imp.new_module(fullname)
734 module.__dict__.update(sys.modules[fullname].__dict__)
735 module.__loader__ = self
736 module.__name__ = name
737 module.__package__ = None
738 module.__name__ = name
739 sys.modules[name] = module
740 return module
742 _WHITE_LIST_C_MODULES = [
743 'array',
744 '_ast',
745 'binascii',
746 '_bisect',
747 '_bytesio',
748 'bz2',
749 'cmath',
750 '_codecs',
751 '_codecs_cn',
752 '_codecs_hk',
753 '_codecs_iso2022',
754 '_codecs_jp',
755 '_codecs_kr',
756 '_codecs_tw',
757 '_collections', # Python 2.6 compatibility
758 'crypt',
759 'cPickle',
760 'cStringIO',
761 '_csv',
762 'datetime',
763 '_elementtree',
764 'errno',
765 'exceptions',
766 '_fileio',
767 '_functools',
768 'gc',
769 '_hashlib',
770 '_heapq',
771 'imp',
772 '_io',
773 'itertools',
774 '_json',
775 '_locale',
776 '_lsprof',
777 '__main__',
778 'marshal',
779 'math',
780 '_md5', # Python2.5 compatibility
781 '_multibytecodec',
782 'nt', # Only indirectly through the os module.
783 'operator',
784 'parser',
785 'posix', # Only indirectly through the os module.
786 'pyexpat',
787 '_random',
788 '_sha256', # Python2.5 compatibility
789 '_sha512', # Python2.5 compatibility
790 '_sha', # Python2.5 compatibility
791 '_sre',
792 'strop',
793 '_struct',
794 '_symtable',
795 'sys',
796 'thread',
797 'time',
798 'timing',
799 'unicodedata',
800 '_warnings',
801 '_weakref',
802 'zipimport',
803 'zlib',
807 class BuiltinImportHook(object):
808 """An import hook implementing a builtin whitelist.
810 BuiltinImportHook implements the PEP 302 finder protocol where it returns
811 itself as a loader for any builtin module that isn't whitelisted. The loader
812 implementation always raises ImportError.
815 def find_module(self, fullname, unused_path=None):
816 if (fullname in sys.builtin_module_names and
817 fullname not in _WHITE_LIST_C_MODULES):
818 return self
819 return None
821 def load_module(self, fullname):
822 raise ImportError('No module named %s' % fullname)
825 class CModuleImportHook(object):
826 """An import hook implementing a C module whitelist.
828 CModuleImportHook implements the PEP 302 finder protocol where it returns
829 itself as a loader for any builtin module that isn't whitelisted or part of an
830 enabled third-party library. The loader implementation always raises
831 ImportError.
834 def __init__(self, enabled_regexes):
835 self._enabled_regexes = enabled_regexes
837 def find_module(self, fullname, path=None):
838 if fullname in _WHITE_LIST_C_MODULES:
839 return None
840 if any(regex.match(fullname) for regex in self._enabled_regexes):
841 return None
842 _, _, submodule_name = fullname.rpartition('.')
843 try:
844 result = imp.find_module(submodule_name, path)
845 except ImportError:
846 return None
847 f, _, description = result
848 _, _, file_type = description
849 if isinstance(f, file):
850 f.close()
851 if file_type == imp.C_EXTENSION:
852 return self
853 return None
855 def load_module(self, fullname):
856 raise ImportError('No module named %s' % fullname)
859 class PathRestrictingImportHook(object):
860 """An import hook that restricts imports to accessible paths.
862 This import hook uses FakeFile.is_file_accessible to determine which paths are
863 accessible.
865 _EXCLUDED_TYPES = frozenset([
866 imp.C_BUILTIN,
867 imp.PY_FROZEN,
870 def __init__(self, enabled_regexes):
871 self._enabled_regexes = enabled_regexes
873 def find_module(self, fullname, path=None):
874 if any(regex.match(fullname) for regex in self._enabled_regexes):
875 return None
876 _, _, submodule_name = fullname.rpartition('.')
877 try:
878 f, filename, description = imp.find_module(submodule_name, path)
879 except ImportError:
880 return None
881 if f:
882 f.close()
883 _, _, file_type = description
884 if (file_type in self._EXCLUDED_TYPES or
885 stubs.FakeFile.is_file_accessible(filename) or
886 (filename.endswith('.pyc') and
887 os.path.exists(filename.replace('.pyc', '.py')))):
888 return None
889 return self
891 def load_module(self, fullname):
892 raise ImportError('No module named %s' % fullname)
895 class PyCryptoRandomImportHook(BaseImportHook):
896 """An import hook that allows Crypto.Random.OSRNG.new() to work on posix.
898 This changes PyCrypto to always use os.urandom() instead of reading from
899 /dev/urandom.
902 def __init__(self, path):
903 self._path = path
905 @classmethod
906 def find_module(cls, fullname, path=None):
907 if fullname == 'Crypto.Random.OSRNG.posix':
908 return cls(path)
909 return None
911 def load_module(self, fullname):
912 if fullname in sys.modules:
913 return sys.modules[fullname]
914 __import__('Crypto.Random.OSRNG.fallback')
915 module = self._find_and_load_module('posix', fullname, self._path)
916 fallback = sys.modules['Crypto.Random.OSRNG.fallback']
917 module.new = fallback.new
918 module.__loader__ = self
919 sys.modules[fullname] = module
920 return module