42 lines
1.3 KiB
Python
42 lines
1.3 KiB
Python
"""Composition read boundary, independent of optional audition/reference tools."""
|
|
from __future__ import annotations
|
|
import contextlib
|
|
import contextvars
|
|
import os
|
|
import pathlib
|
|
import sys
|
|
|
|
ACTIVE = contextvars.ContextVar('composition_reads', default=None)
|
|
|
|
def _read(event, args):
|
|
scope = ACTIVE.get()
|
|
if scope is None or event != 'open':
|
|
return
|
|
name, mode, flags = args
|
|
if not isinstance(name, (str, bytes, os.PathLike)):
|
|
return
|
|
# r+ and w+ can read; they must obey the same boundary as a plain read.
|
|
if flags & os.O_WRONLY:
|
|
return
|
|
path = pathlib.Path(os.fsdecode(name)).resolve()
|
|
data, code, cache, reads = scope
|
|
python_runtime = any(path.is_relative_to(pathlib.Path(root).resolve()) for root in (sys.base_prefix, sys.prefix))
|
|
allowed = path.is_relative_to(data) or path.is_relative_to(code) or python_runtime
|
|
if cache and path.is_relative_to(cache):
|
|
allowed = True
|
|
if not allowed:
|
|
raise ValueError('Forbidden composition input: ' + str(path))
|
|
reads.add(str(path))
|
|
|
|
sys.addaudithook(_read)
|
|
|
|
@contextlib.contextmanager
|
|
def composition(data, role, cache=None):
|
|
reads = set()
|
|
code = pathlib.Path(__file__).resolve().parent
|
|
token = ACTIVE.set((pathlib.Path(data).resolve(), code, pathlib.Path(cache).resolve() / role if cache else None, reads))
|
|
try:
|
|
yield reads
|
|
finally:
|
|
ACTIVE.reset(token)
|