Spaces:
Runtime error
Runtime error
| import os | |
| import sys | |
| import tempfile | |
| import operator | |
| import functools | |
| import itertools | |
| import re | |
| import contextlib | |
| import pickle | |
| import textwrap | |
| import builtins | |
| import pkg_resources | |
| from distutils.errors import DistutilsError | |
| from pkg_resources import working_set | |
| if sys.platform.startswith('java'): | |
| import org.python.modules.posix.PosixModule as _os | |
| else: | |
| _os = sys.modules[os.name] | |
| try: | |
| _file = file | |
| except NameError: | |
| _file = None | |
| _open = open | |
| __all__ = [ | |
| "AbstractSandbox", | |
| "DirectorySandbox", | |
| "SandboxViolation", | |
| "run_setup", | |
| ] | |
| def _execfile(filename, globals, locals=None): | |
| """ | |
| Python 3 implementation of execfile. | |
| """ | |
| mode = 'rb' | |
| with open(filename, mode) as stream: | |
| script = stream.read() | |
| if locals is None: | |
| locals = globals | |
| code = compile(script, filename, 'exec') | |
| exec(code, globals, locals) | |
| def save_argv(repl=None): | |
| saved = sys.argv[:] | |
| if repl is not None: | |
| sys.argv[:] = repl | |
| try: | |
| yield saved | |
| finally: | |
| sys.argv[:] = saved | |
| def save_path(): | |
| saved = sys.path[:] | |
| try: | |
| yield saved | |
| finally: | |
| sys.path[:] = saved | |
| def override_temp(replacement): | |
| """ | |
| Monkey-patch tempfile.tempdir with replacement, ensuring it exists | |
| """ | |
| os.makedirs(replacement, exist_ok=True) | |
| saved = tempfile.tempdir | |
| tempfile.tempdir = replacement | |
| try: | |
| yield | |
| finally: | |
| tempfile.tempdir = saved | |
| def pushd(target): | |
| saved = os.getcwd() | |
| os.chdir(target) | |
| try: | |
| yield saved | |
| finally: | |
| os.chdir(saved) | |
| class UnpickleableException(Exception): | |
| """ | |
| An exception representing another Exception that could not be pickled. | |
| """ | |
| def dump(type, exc): | |
| """ | |
| Always return a dumped (pickled) type and exc. If exc can't be pickled, | |
| wrap it in UnpickleableException first. | |
| """ | |
| try: | |
| return pickle.dumps(type), pickle.dumps(exc) | |
| except Exception: | |
| # get UnpickleableException inside the sandbox | |
| from setuptools.sandbox import UnpickleableException as cls | |
| return cls.dump(cls, cls(repr(exc))) | |
| class ExceptionSaver: | |
| """ | |
| A Context Manager that will save an exception, serialized, and restore it | |
| later. | |
| """ | |
| def __enter__(self): | |
| return self | |
| def __exit__(self, type, exc, tb): | |
| if not exc: | |
| return | |
| # dump the exception | |
| self._saved = UnpickleableException.dump(type, exc) | |
| self._tb = tb | |
| # suppress the exception | |
| return True | |
| def resume(self): | |
| "restore and re-raise any exception" | |
| if '_saved' not in vars(self): | |
| return | |
| type, exc = map(pickle.loads, self._saved) | |
| raise exc.with_traceback(self._tb) | |
| def save_modules(): | |
| """ | |
| Context in which imported modules are saved. | |
| Translates exceptions internal to the context into the equivalent exception | |
| outside the context. | |
| """ | |
| saved = sys.modules.copy() | |
| with ExceptionSaver() as saved_exc: | |
| yield saved | |
| sys.modules.update(saved) | |
| # remove any modules imported since | |
| del_modules = ( | |
| mod_name | |
| for mod_name in sys.modules | |
| if mod_name not in saved | |
| # exclude any encodings modules. See #285 | |
| and not mod_name.startswith('encodings.') | |
| ) | |
| _clear_modules(del_modules) | |
| saved_exc.resume() | |
| def _clear_modules(module_names): | |
| for mod_name in list(module_names): | |
| del sys.modules[mod_name] | |
| def save_pkg_resources_state(): | |
| saved = pkg_resources.__getstate__() | |
| try: | |
| yield saved | |
| finally: | |
| pkg_resources.__setstate__(saved) | |
| def setup_context(setup_dir): | |
| temp_dir = os.path.join(setup_dir, 'temp') | |
| with save_pkg_resources_state(): | |
| with save_modules(): | |
| with save_path(): | |
| hide_setuptools() | |
| with save_argv(): | |
| with override_temp(temp_dir): | |
| with pushd(setup_dir): | |
| # ensure setuptools commands are available | |
| __import__('setuptools') | |
| yield | |
| _MODULES_TO_HIDE = { | |
| 'setuptools', | |
| 'distutils', | |
| 'pkg_resources', | |
| 'Cython', | |
| '_distutils_hack', | |
| } | |
| def _needs_hiding(mod_name): | |
| """ | |
| >>> _needs_hiding('setuptools') | |
| True | |
| >>> _needs_hiding('pkg_resources') | |
| True | |
| >>> _needs_hiding('setuptools_plugin') | |
| False | |
| >>> _needs_hiding('setuptools.__init__') | |
| True | |
| >>> _needs_hiding('distutils') | |
| True | |
| >>> _needs_hiding('os') | |
| False | |
| >>> _needs_hiding('Cython') | |
| True | |
| """ | |
| base_module = mod_name.split('.', 1)[0] | |
| return base_module in _MODULES_TO_HIDE | |
| def hide_setuptools(): | |
| """ | |
| Remove references to setuptools' modules from sys.modules to allow the | |
| invocation to import the most appropriate setuptools. This technique is | |
| necessary to avoid issues such as #315 where setuptools upgrading itself | |
| would fail to find a function declared in the metadata. | |
| """ | |
| _distutils_hack = sys.modules.get('_distutils_hack', None) | |
| if _distutils_hack is not None: | |
| _distutils_hack.remove_shim() | |
| modules = filter(_needs_hiding, sys.modules) | |
| _clear_modules(modules) | |
| def run_setup(setup_script, args): | |
| """Run a distutils setup script, sandboxed in its directory""" | |
| setup_dir = os.path.abspath(os.path.dirname(setup_script)) | |
| with setup_context(setup_dir): | |
| try: | |
| sys.argv[:] = [setup_script] + list(args) | |
| sys.path.insert(0, setup_dir) | |
| # reset to include setup dir, w/clean callback list | |
| working_set.__init__() | |
| working_set.callbacks.append(lambda dist: dist.activate()) | |
| with DirectorySandbox(setup_dir): | |
| ns = dict(__file__=setup_script, __name__='__main__') | |
| _execfile(setup_script, ns) | |
| except SystemExit as v: | |
| if v.args and v.args[0]: | |
| raise | |
| # Normal exit, just return | |
| class AbstractSandbox: | |
| """Wrap 'os' module and 'open()' builtin for virtualizing setup scripts""" | |
| _active = False | |
| def __init__(self): | |
| self._attrs = [ | |
| name | |
| for name in dir(_os) | |
| if not name.startswith('_') and hasattr(self, name) | |
| ] | |
| def _copy(self, source): | |
| for name in self._attrs: | |
| setattr(os, name, getattr(source, name)) | |
| def __enter__(self): | |
| self._copy(self) | |
| if _file: | |
| builtins.file = self._file | |
| builtins.open = self._open | |
| self._active = True | |
| def __exit__(self, exc_type, exc_value, traceback): | |
| self._active = False | |
| if _file: | |
| builtins.file = _file | |
| builtins.open = _open | |
| self._copy(_os) | |
| def run(self, func): | |
| """Run 'func' under os sandboxing""" | |
| with self: | |
| return func() | |
| def _mk_dual_path_wrapper(name): | |
| original = getattr(_os, name) | |
| def wrap(self, src, dst, *args, **kw): | |
| if self._active: | |
| src, dst = self._remap_pair(name, src, dst, *args, **kw) | |
| return original(src, dst, *args, **kw) | |
| return wrap | |
| for name in ["rename", "link", "symlink"]: | |
| if hasattr(_os, name): | |
| locals()[name] = _mk_dual_path_wrapper(name) | |
| def _mk_single_path_wrapper(name, original=None): | |
| original = original or getattr(_os, name) | |
| def wrap(self, path, *args, **kw): | |
| if self._active: | |
| path = self._remap_input(name, path, *args, **kw) | |
| return original(path, *args, **kw) | |
| return wrap | |
| if _file: | |
| _file = _mk_single_path_wrapper('file', _file) | |
| _open = _mk_single_path_wrapper('open', _open) | |
| for name in [ | |
| "stat", | |
| "listdir", | |
| "chdir", | |
| "open", | |
| "chmod", | |
| "chown", | |
| "mkdir", | |
| "remove", | |
| "unlink", | |
| "rmdir", | |
| "utime", | |
| "lchown", | |
| "chroot", | |
| "lstat", | |
| "startfile", | |
| "mkfifo", | |
| "mknod", | |
| "pathconf", | |
| "access", | |
| ]: | |
| if hasattr(_os, name): | |
| locals()[name] = _mk_single_path_wrapper(name) | |
| def _mk_single_with_return(name): | |
| original = getattr(_os, name) | |
| def wrap(self, path, *args, **kw): | |
| if self._active: | |
| path = self._remap_input(name, path, *args, **kw) | |
| return self._remap_output(name, original(path, *args, **kw)) | |
| return original(path, *args, **kw) | |
| return wrap | |
| for name in ['readlink', 'tempnam']: | |
| if hasattr(_os, name): | |
| locals()[name] = _mk_single_with_return(name) | |
| def _mk_query(name): | |
| original = getattr(_os, name) | |
| def wrap(self, *args, **kw): | |
| retval = original(*args, **kw) | |
| if self._active: | |
| return self._remap_output(name, retval) | |
| return retval | |
| return wrap | |
| for name in ['getcwd', 'tmpnam']: | |
| if hasattr(_os, name): | |
| locals()[name] = _mk_query(name) | |
| def _validate_path(self, path): | |
| """Called to remap or validate any path, whether input or output""" | |
| return path | |
| def _remap_input(self, operation, path, *args, **kw): | |
| """Called for path inputs""" | |
| return self._validate_path(path) | |
| def _remap_output(self, operation, path): | |
| """Called for path outputs""" | |
| return self._validate_path(path) | |
| def _remap_pair(self, operation, src, dst, *args, **kw): | |
| """Called for path pairs like rename, link, and symlink operations""" | |
| return ( | |
| self._remap_input(operation + '-from', src, *args, **kw), | |
| self._remap_input(operation + '-to', dst, *args, **kw), | |
| ) | |
| if hasattr(os, 'devnull'): | |
| _EXCEPTIONS = [os.devnull] | |
| else: | |
| _EXCEPTIONS = [] | |
| class DirectorySandbox(AbstractSandbox): | |
| """Restrict operations to a single subdirectory - pseudo-chroot""" | |
| write_ops = dict.fromkeys( | |
| [ | |
| "open", | |
| "chmod", | |
| "chown", | |
| "mkdir", | |
| "remove", | |
| "unlink", | |
| "rmdir", | |
| "utime", | |
| "lchown", | |
| "chroot", | |
| "mkfifo", | |
| "mknod", | |
| "tempnam", | |
| ] | |
| ) | |
| _exception_patterns = [] | |
| "exempt writing to paths that match the pattern" | |
| def __init__(self, sandbox, exceptions=_EXCEPTIONS): | |
| self._sandbox = os.path.normcase(os.path.realpath(sandbox)) | |
| self._prefix = os.path.join(self._sandbox, '') | |
| self._exceptions = [ | |
| os.path.normcase(os.path.realpath(path)) for path in exceptions | |
| ] | |
| AbstractSandbox.__init__(self) | |
| def _violation(self, operation, *args, **kw): | |
| from setuptools.sandbox import SandboxViolation | |
| raise SandboxViolation(operation, args, kw) | |
| if _file: | |
| def _file(self, path, mode='r', *args, **kw): | |
| if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): | |
| self._violation("file", path, mode, *args, **kw) | |
| return _file(path, mode, *args, **kw) | |
| def _open(self, path, mode='r', *args, **kw): | |
| if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): | |
| self._violation("open", path, mode, *args, **kw) | |
| return _open(path, mode, *args, **kw) | |
| def tmpnam(self): | |
| self._violation("tmpnam") | |
| def _ok(self, path): | |
| active = self._active | |
| try: | |
| self._active = False | |
| realpath = os.path.normcase(os.path.realpath(path)) | |
| return ( | |
| self._exempted(realpath) | |
| or realpath == self._sandbox | |
| or realpath.startswith(self._prefix) | |
| ) | |
| finally: | |
| self._active = active | |
| def _exempted(self, filepath): | |
| start_matches = ( | |
| filepath.startswith(exception) for exception in self._exceptions | |
| ) | |
| pattern_matches = ( | |
| re.match(pattern, filepath) for pattern in self._exception_patterns | |
| ) | |
| candidates = itertools.chain(start_matches, pattern_matches) | |
| return any(candidates) | |
| def _remap_input(self, operation, path, *args, **kw): | |
| """Called for path inputs""" | |
| if operation in self.write_ops and not self._ok(path): | |
| self._violation(operation, os.path.realpath(path), *args, **kw) | |
| return path | |
| def _remap_pair(self, operation, src, dst, *args, **kw): | |
| """Called for path pairs like rename, link, and symlink operations""" | |
| if not self._ok(src) or not self._ok(dst): | |
| self._violation(operation, src, dst, *args, **kw) | |
| return (src, dst) | |
| def open(self, file, flags, mode=0o777, *args, **kw): | |
| """Called for low-level os.open()""" | |
| if flags & WRITE_FLAGS and not self._ok(file): | |
| self._violation("os.open", file, flags, mode, *args, **kw) | |
| return _os.open(file, flags, mode, *args, **kw) | |
| WRITE_FLAGS = functools.reduce( | |
| operator.or_, | |
| [ | |
| getattr(_os, a, 0) | |
| for a in "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split() | |
| ], | |
| ) | |
| class SandboxViolation(DistutilsError): | |
| """A setup script attempted to modify the filesystem outside the sandbox""" | |
| tmpl = textwrap.dedent( | |
| """ | |
| SandboxViolation: {cmd}{args!r} {kwargs} | |
| The package setup script has attempted to modify files on your system | |
| that are not within the EasyInstall build area, and has been aborted. | |
| This package cannot be safely installed by EasyInstall, and may not | |
| support alternate installation locations even if you run its setup | |
| script by hand. Please inform the package's author and the EasyInstall | |
| maintainers to find out if a fix or workaround is available. | |
| """ | |
| ).lstrip() | |
| def __str__(self): | |
| cmd, args, kwargs = self.args | |
| return self.tmpl.format(**locals()) | |