diff --git a/tractor/_state.py b/tractor/_state.py index 1644cd8a..86e3ea12 100644 --- a/tractor/_state.py +++ b/tractor/_state.py @@ -175,28 +175,50 @@ def current_ipc_ctx( def get_rt_dir( subdir: str|Path|None = None, + appname: str = 'tractor', ) -> Path: ''' Return the user "runtime dir", the file-sys location where most userspace apps stick their IPC and cache related system util-files. - On linux we take use a `'${XDG_RUNTIME_DIR}/tractor/` subdir by - default but equivalents are mapped for each platform using - the lovely `platformdirs`. + On linux we use a `${XDG_RUNTIME_DIR}/tractor/` subdir by + default, but equivalents are mapped for each platform using + the lovely `platformdirs` lib. ''' rt_dir: Path = Path( platformdirs.user_runtime_dir( - appname='tractor', + appname=appname, ), ) + + # Normalize and validate that `subdir` is a relative path + # without any parent-directory ("..") components, to prevent + # escaping the runtime directory. if subdir: - rt_dir: Path = rt_dir / subdir + subdir_path = ( + subdir + if isinstance(subdir, Path) + else Path(subdir) + ) + if subdir_path.is_absolute(): + raise ValueError( + f'`subdir` must be a relative path!\n' + f'{subdir!r}\n' + ) + if any(part == '..' for part in subdir_path.parts): + raise ValueError( + "`subdir` must not contain '..' components!\n" + f'{subdir!r}\n' + ) + + rt_dir: Path = rt_dir / subdir_path if not rt_dir.is_dir(): rt_dir.mkdir( parents=True, + exist_ok=True, # avoid `FileExistsError` from conc calls ) return rt_dir