]> jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/setuptools/sandbox.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / setuptools / sandbox.py
1 import os
2 import sys
3 import tempfile
4 import operator
5 import functools
6 import itertools
7 import re
8 import contextlib
9 import pickle
10 import textwrap
11 import builtins
12
13 import pkg_resources
14 from distutils.errors import DistutilsError
15 from pkg_resources import working_set
16
17 if sys.platform.startswith('java'):
18 import org.python.modules.posix.PosixModule as _os
19 else:
20 _os = sys.modules[os.name]
21 try:
22 _file = file
23 except NameError:
24 _file = None
25 _open = open
26
27
28 __all__ = [
29 "AbstractSandbox",
30 "DirectorySandbox",
31 "SandboxViolation",
32 "run_setup",
33 ]
34
35
36 def _execfile(filename, globals, locals=None):
37 """
38 Python 3 implementation of execfile.
39 """
40 mode = 'rb'
41 with open(filename, mode) as stream:
42 script = stream.read()
43 if locals is None:
44 locals = globals
45 code = compile(script, filename, 'exec')
46 exec(code, globals, locals)
47
48
49 @contextlib.contextmanager
50 def save_argv(repl=None):
51 saved = sys.argv[:]
52 if repl is not None:
53 sys.argv[:] = repl
54 try:
55 yield saved
56 finally:
57 sys.argv[:] = saved
58
59
60 @contextlib.contextmanager
61 def save_path():
62 saved = sys.path[:]
63 try:
64 yield saved
65 finally:
66 sys.path[:] = saved
67
68
69 @contextlib.contextmanager
70 def override_temp(replacement):
71 """
72 Monkey-patch tempfile.tempdir with replacement, ensuring it exists
73 """
74 os.makedirs(replacement, exist_ok=True)
75
76 saved = tempfile.tempdir
77
78 tempfile.tempdir = replacement
79
80 try:
81 yield
82 finally:
83 tempfile.tempdir = saved
84
85
86 @contextlib.contextmanager
87 def pushd(target):
88 saved = os.getcwd()
89 os.chdir(target)
90 try:
91 yield saved
92 finally:
93 os.chdir(saved)
94
95
96 class UnpickleableException(Exception):
97 """
98 An exception representing another Exception that could not be pickled.
99 """
100
101 @staticmethod
102 def dump(type, exc):
103 """
104 Always return a dumped (pickled) type and exc. If exc can't be pickled,
105 wrap it in UnpickleableException first.
106 """
107 try:
108 return pickle.dumps(type), pickle.dumps(exc)
109 except Exception:
110 # get UnpickleableException inside the sandbox
111 from setuptools.sandbox import UnpickleableException as cls
112
113 return cls.dump(cls, cls(repr(exc)))
114
115
116 class ExceptionSaver:
117 """
118 A Context Manager that will save an exception, serialized, and restore it
119 later.
120 """
121
122 def __enter__(self):
123 return self
124
125 def __exit__(self, type, exc, tb):
126 if not exc:
127 return
128
129 # dump the exception
130 self._saved = UnpickleableException.dump(type, exc)
131 self._tb = tb
132
133 # suppress the exception
134 return True
135
136 def resume(self):
137 "restore and re-raise any exception"
138
139 if '_saved' not in vars(self):
140 return
141
142 type, exc = map(pickle.loads, self._saved)
143 raise exc.with_traceback(self._tb)
144
145
146 @contextlib.contextmanager
147 def save_modules():
148 """
149 Context in which imported modules are saved.
150
151 Translates exceptions internal to the context into the equivalent exception
152 outside the context.
153 """
154 saved = sys.modules.copy()
155 with ExceptionSaver() as saved_exc:
156 yield saved
157
158 sys.modules.update(saved)
159 # remove any modules imported since
160 del_modules = (
161 mod_name
162 for mod_name in sys.modules
163 if mod_name not in saved
164 # exclude any encodings modules. See #285
165 and not mod_name.startswith('encodings.')
166 )
167 _clear_modules(del_modules)
168
169 saved_exc.resume()
170
171
172 def _clear_modules(module_names):
173 for mod_name in list(module_names):
174 del sys.modules[mod_name]
175
176
177 @contextlib.contextmanager
178 def save_pkg_resources_state():
179 saved = pkg_resources.__getstate__()
180 try:
181 yield saved
182 finally:
183 pkg_resources.__setstate__(saved)
184
185
186 @contextlib.contextmanager
187 def setup_context(setup_dir):
188 temp_dir = os.path.join(setup_dir, 'temp')
189 with save_pkg_resources_state():
190 with save_modules():
191 with save_path():
192 hide_setuptools()
193 with save_argv():
194 with override_temp(temp_dir):
195 with pushd(setup_dir):
196 # ensure setuptools commands are available
197 __import__('setuptools')
198 yield
199
200
201 _MODULES_TO_HIDE = {
202 'setuptools',
203 'distutils',
204 'pkg_resources',
205 'Cython',
206 '_distutils_hack',
207 }
208
209
210 def _needs_hiding(mod_name):
211 """
212 >>> _needs_hiding('setuptools')
213 True
214 >>> _needs_hiding('pkg_resources')
215 True
216 >>> _needs_hiding('setuptools_plugin')
217 False
218 >>> _needs_hiding('setuptools.__init__')
219 True
220 >>> _needs_hiding('distutils')
221 True
222 >>> _needs_hiding('os')
223 False
224 >>> _needs_hiding('Cython')
225 True
226 """
227 base_module = mod_name.split('.', 1)[0]
228 return base_module in _MODULES_TO_HIDE
229
230
231 def hide_setuptools():
232 """
233 Remove references to setuptools' modules from sys.modules to allow the
234 invocation to import the most appropriate setuptools. This technique is
235 necessary to avoid issues such as #315 where setuptools upgrading itself
236 would fail to find a function declared in the metadata.
237 """
238 _distutils_hack = sys.modules.get('_distutils_hack', None)
239 if _distutils_hack is not None:
240 _distutils_hack._remove_shim()
241
242 modules = filter(_needs_hiding, sys.modules)
243 _clear_modules(modules)
244
245
246 def run_setup(setup_script, args):
247 """Run a distutils setup script, sandboxed in its directory"""
248 setup_dir = os.path.abspath(os.path.dirname(setup_script))
249 with setup_context(setup_dir):
250 try:
251 sys.argv[:] = [setup_script] + list(args)
252 sys.path.insert(0, setup_dir)
253 # reset to include setup dir, w/clean callback list
254 working_set.__init__()
255 working_set.callbacks.append(lambda dist: dist.activate())
256
257 with DirectorySandbox(setup_dir):
258 ns = dict(__file__=setup_script, __name__='__main__')
259 _execfile(setup_script, ns)
260 except SystemExit as v:
261 if v.args and v.args[0]:
262 raise
263 # Normal exit, just return
264
265
266 class AbstractSandbox:
267 """Wrap 'os' module and 'open()' builtin for virtualizing setup scripts"""
268
269 _active = False
270
271 def __init__(self):
272 self._attrs = [
273 name
274 for name in dir(_os)
275 if not name.startswith('_') and hasattr(self, name)
276 ]
277
278 def _copy(self, source):
279 for name in self._attrs:
280 setattr(os, name, getattr(source, name))
281
282 def __enter__(self):
283 self._copy(self)
284 if _file:
285 builtins.file = self._file
286 builtins.open = self._open
287 self._active = True
288
289 def __exit__(self, exc_type, exc_value, traceback):
290 self._active = False
291 if _file:
292 builtins.file = _file
293 builtins.open = _open
294 self._copy(_os)
295
296 def run(self, func):
297 """Run 'func' under os sandboxing"""
298 with self:
299 return func()
300
301 def _mk_dual_path_wrapper(name):
302 original = getattr(_os, name)
303
304 def wrap(self, src, dst, *args, **kw):
305 if self._active:
306 src, dst = self._remap_pair(name, src, dst, *args, **kw)
307 return original(src, dst, *args, **kw)
308
309 return wrap
310
311 for name in ["rename", "link", "symlink"]:
312 if hasattr(_os, name):
313 locals()[name] = _mk_dual_path_wrapper(name)
314
315 def _mk_single_path_wrapper(name, original=None):
316 original = original or getattr(_os, name)
317
318 def wrap(self, path, *args, **kw):
319 if self._active:
320 path = self._remap_input(name, path, *args, **kw)
321 return original(path, *args, **kw)
322
323 return wrap
324
325 if _file:
326 _file = _mk_single_path_wrapper('file', _file)
327 _open = _mk_single_path_wrapper('open', _open)
328 for name in [
329 "stat",
330 "listdir",
331 "chdir",
332 "open",
333 "chmod",
334 "chown",
335 "mkdir",
336 "remove",
337 "unlink",
338 "rmdir",
339 "utime",
340 "lchown",
341 "chroot",
342 "lstat",
343 "startfile",
344 "mkfifo",
345 "mknod",
346 "pathconf",
347 "access",
348 ]:
349 if hasattr(_os, name):
350 locals()[name] = _mk_single_path_wrapper(name)
351
352 def _mk_single_with_return(name):
353 original = getattr(_os, name)
354
355 def wrap(self, path, *args, **kw):
356 if self._active:
357 path = self._remap_input(name, path, *args, **kw)
358 return self._remap_output(name, original(path, *args, **kw))
359 return original(path, *args, **kw)
360
361 return wrap
362
363 for name in ['readlink', 'tempnam']:
364 if hasattr(_os, name):
365 locals()[name] = _mk_single_with_return(name)
366
367 def _mk_query(name):
368 original = getattr(_os, name)
369
370 def wrap(self, *args, **kw):
371 retval = original(*args, **kw)
372 if self._active:
373 return self._remap_output(name, retval)
374 return retval
375
376 return wrap
377
378 for name in ['getcwd', 'tmpnam']:
379 if hasattr(_os, name):
380 locals()[name] = _mk_query(name)
381
382 def _validate_path(self, path):
383 """Called to remap or validate any path, whether input or output"""
384 return path
385
386 def _remap_input(self, operation, path, *args, **kw):
387 """Called for path inputs"""
388 return self._validate_path(path)
389
390 def _remap_output(self, operation, path):
391 """Called for path outputs"""
392 return self._validate_path(path)
393
394 def _remap_pair(self, operation, src, dst, *args, **kw):
395 """Called for path pairs like rename, link, and symlink operations"""
396 return (
397 self._remap_input(operation + '-from', src, *args, **kw),
398 self._remap_input(operation + '-to', dst, *args, **kw),
399 )
400
401
402 if hasattr(os, 'devnull'):
403 _EXCEPTIONS = [os.devnull]
404 else:
405 _EXCEPTIONS = []
406
407
408 class DirectorySandbox(AbstractSandbox):
409 """Restrict operations to a single subdirectory - pseudo-chroot"""
410
411 write_ops = dict.fromkeys(
412 [
413 "open",
414 "chmod",
415 "chown",
416 "mkdir",
417 "remove",
418 "unlink",
419 "rmdir",
420 "utime",
421 "lchown",
422 "chroot",
423 "mkfifo",
424 "mknod",
425 "tempnam",
426 ]
427 )
428
429 _exception_patterns = []
430 "exempt writing to paths that match the pattern"
431
432 def __init__(self, sandbox, exceptions=_EXCEPTIONS):
433 self._sandbox = os.path.normcase(os.path.realpath(sandbox))
434 self._prefix = os.path.join(self._sandbox, '')
435 self._exceptions = [
436 os.path.normcase(os.path.realpath(path)) for path in exceptions
437 ]
438 AbstractSandbox.__init__(self)
439
440 def _violation(self, operation, *args, **kw):
441 from setuptools.sandbox import SandboxViolation
442
443 raise SandboxViolation(operation, args, kw)
444
445 if _file:
446
447 def _file(self, path, mode='r', *args, **kw):
448 if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
449 self._violation("file", path, mode, *args, **kw)
450 return _file(path, mode, *args, **kw)
451
452 def _open(self, path, mode='r', *args, **kw):
453 if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
454 self._violation("open", path, mode, *args, **kw)
455 return _open(path, mode, *args, **kw)
456
457 def tmpnam(self):
458 self._violation("tmpnam")
459
460 def _ok(self, path):
461 active = self._active
462 try:
463 self._active = False
464 realpath = os.path.normcase(os.path.realpath(path))
465 return (
466 self._exempted(realpath)
467 or realpath == self._sandbox
468 or realpath.startswith(self._prefix)
469 )
470 finally:
471 self._active = active
472
473 def _exempted(self, filepath):
474 start_matches = (
475 filepath.startswith(exception) for exception in self._exceptions
476 )
477 pattern_matches = (
478 re.match(pattern, filepath) for pattern in self._exception_patterns
479 )
480 candidates = itertools.chain(start_matches, pattern_matches)
481 return any(candidates)
482
483 def _remap_input(self, operation, path, *args, **kw):
484 """Called for path inputs"""
485 if operation in self.write_ops and not self._ok(path):
486 self._violation(operation, os.path.realpath(path), *args, **kw)
487 return path
488
489 def _remap_pair(self, operation, src, dst, *args, **kw):
490 """Called for path pairs like rename, link, and symlink operations"""
491 if not self._ok(src) or not self._ok(dst):
492 self._violation(operation, src, dst, *args, **kw)
493 return (src, dst)
494
495 def open(self, file, flags, mode=0o777, *args, **kw):
496 """Called for low-level os.open()"""
497 if flags & WRITE_FLAGS and not self._ok(file):
498 self._violation("os.open", file, flags, mode, *args, **kw)
499 return _os.open(file, flags, mode, *args, **kw)
500
501
502 WRITE_FLAGS = functools.reduce(
503 operator.or_,
504 [
505 getattr(_os, a, 0)
506 for a in "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()
507 ],
508 )
509
510
511 class SandboxViolation(DistutilsError):
512 """A setup script attempted to modify the filesystem outside the sandbox"""
513
514 tmpl = textwrap.dedent(
515 """
516 SandboxViolation: {cmd}{args!r} {kwargs}
517
518 The package setup script has attempted to modify files on your system
519 that are not within the EasyInstall build area, and has been aborted.
520
521 This package cannot be safely installed by EasyInstall, and may not
522 support alternate installation locations even if you run its setup
523 script by hand. Please inform the package's author and the EasyInstall
524 maintainers to find out if a fix or workaround is available.
525 """
526 ).lstrip()
527
528 def __str__(self):
529 cmd, args, kwargs = self.args
530 return self.tmpl.format(**locals())