| Index: third_party/google-endpoints/setuptools/sandbox.py
|
| diff --git a/third_party/google-endpoints/setuptools/sandbox.py b/third_party/google-endpoints/setuptools/sandbox.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0ddd23324208e94978713f086482f5a4d63066b4
|
| --- /dev/null
|
| +++ b/third_party/google-endpoints/setuptools/sandbox.py
|
| @@ -0,0 +1,491 @@
|
| +import os
|
| +import sys
|
| +import tempfile
|
| +import operator
|
| +import functools
|
| +import itertools
|
| +import re
|
| +import contextlib
|
| +import pickle
|
| +
|
| +import six
|
| +from six.moves import builtins, map
|
| +
|
| +import pkg_resources
|
| +
|
| +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
|
| +from distutils.errors import DistutilsError
|
| +from pkg_resources import working_set
|
| +
|
| +__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()
|
| + # compile() function in Python 2.6 and 3.1 requires LF line endings.
|
| + if sys.version_info[:2] < (2, 7) or sys.version_info[:2] >= (3, 0) and sys.version_info[:2] < (3, 2):
|
| + script = script.replace(b'\r\n', b'\n')
|
| + script = script.replace(b'\r', b'\n')
|
| + if locals is None:
|
| + locals = globals
|
| + code = compile(script, filename, 'exec')
|
| + exec(code, globals, locals)
|
| +
|
| +
|
| +@contextlib.contextmanager
|
| +def save_argv(repl=None):
|
| + saved = sys.argv[:]
|
| + if repl is not None:
|
| + sys.argv[:] = repl
|
| + try:
|
| + yield saved
|
| + finally:
|
| + sys.argv[:] = saved
|
| +
|
| +
|
| +@contextlib.contextmanager
|
| +def save_path():
|
| + saved = sys.path[:]
|
| + try:
|
| + yield saved
|
| + finally:
|
| + sys.path[:] = saved
|
| +
|
| +
|
| +@contextlib.contextmanager
|
| +def override_temp(replacement):
|
| + """
|
| + Monkey-patch tempfile.tempdir with replacement, ensuring it exists
|
| + """
|
| + if not os.path.isdir(replacement):
|
| + os.makedirs(replacement)
|
| +
|
| + saved = tempfile.tempdir
|
| +
|
| + tempfile.tempdir = replacement
|
| +
|
| + try:
|
| + yield
|
| + finally:
|
| + tempfile.tempdir = saved
|
| +
|
| +
|
| +@contextlib.contextmanager
|
| +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.
|
| + """
|
| +
|
| + @staticmethod
|
| + 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)
|
| + six.reraise(type, exc, self._tb)
|
| +
|
| +
|
| +@contextlib.contextmanager
|
| +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]
|
| +
|
| +
|
| +@contextlib.contextmanager
|
| +def save_pkg_resources_state():
|
| + saved = pkg_resources.__getstate__()
|
| + try:
|
| + yield saved
|
| + finally:
|
| + pkg_resources.__setstate__(saved)
|
| +
|
| +
|
| +@contextlib.contextmanager
|
| +def setup_context(setup_dir):
|
| + temp_dir = os.path.join(setup_dir, 'temp')
|
| + with save_pkg_resources_state():
|
| + with save_modules():
|
| + hide_setuptools()
|
| + with save_path():
|
| + with save_argv():
|
| + with override_temp(temp_dir):
|
| + with pushd(setup_dir):
|
| + # ensure setuptools commands are available
|
| + __import__('setuptools')
|
| + yield
|
| +
|
| +
|
| +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
|
| + """
|
| + pattern = re.compile('(setuptools|pkg_resources|distutils|Cython)(\.|$)')
|
| + return bool(pattern.match(mod_name))
|
| +
|
| +
|
| +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.
|
| + """
|
| + 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())
|
| +
|
| + # __file__ should be a byte string on Python 2 (#712)
|
| + dunder_file = (
|
| + setup_script
|
| + if isinstance(setup_script, str) else
|
| + setup_script.encode(sys.getfilesystemencoding())
|
| + )
|
| +
|
| + def runner():
|
| + ns = dict(__file__=dunder_file, __name__='__main__')
|
| + _execfile(setup_script, ns)
|
| +
|
| + DirectorySandbox(setup_dir).run(runner)
|
| + 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 run(self, func):
|
| + """Run 'func' under os sandboxing"""
|
| + try:
|
| + self._copy(self)
|
| + if _file:
|
| + builtins.file = self._file
|
| + builtins.open = self._open
|
| + self._active = True
|
| + return func()
|
| + finally:
|
| + self._active = False
|
| + if _file:
|
| + builtins.file = _file
|
| + builtins.open = _open
|
| + self._copy(_os)
|
| +
|
| + 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 = [
|
| + # Allow lib2to3 to attempt to save a pickled grammar object (#121)
|
| + '.*lib2to3.*\.pickle$',
|
| + ]
|
| + "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"""
|
| +
|
| + def __str__(self):
|
| + return """SandboxViolation: %s%r %s
|
| +
|
| +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.""" % self.args
|
| +
|
| +
|
| +#
|
|
|