"""Workspace path validation utilities""" import os import shutil from pathlib import Path from typing import Optional from flask import current_app from backend import load_config def get_workspace_root() -> Path: """Get workspace root directory from config""" cfg = load_config() workspace_root = cfg.get("workspace_root", "./workspaces") # Convert to absolute path workspace_path = Path(workspace_root) if not workspace_path.is_absolute(): # Relative to project root workspace_path = Path(__file__).parent.parent.parent / workspace_root # Create if not exists workspace_path.mkdir(parents=True, exist_ok=True) return workspace_path.resolve() def get_project_path(project_id: str, project_path: str) -> Path: """ Get absolute path for a project Args: project_id: Project ID project_path: Relative path stored in database Returns: Absolute path to project directory """ workspace_root = get_workspace_root() project_dir = workspace_root / project_path # Create if not exists project_dir.mkdir(parents=True, exist_ok=True) return project_dir.resolve() def validate_path_in_project(path: str, project_dir: Path) -> Path: """ Validate that a path is within the project directory Args: path: Path to validate (can be relative or absolute) project_dir: Project directory path Returns: Resolved absolute path Raises: ValueError: If path is outside project directory """ p = Path(path) # If relative, resolve against project directory if not p.is_absolute(): p = project_dir / p # Resolve to absolute path p = p.resolve() # Security check: ensure path is within project directory try: p.relative_to(project_dir.resolve()) except ValueError: raise ValueError(f"Path '{path}' is outside project directory") return p def create_project_directory(name: str, user_id: int) -> tuple[str, Path]: """ Create a new project directory Args: name: Project name user_id: User ID Returns: Tuple of (relative_path, absolute_path) """ workspace_root = get_workspace_root() # Create user-specific directory user_dir = workspace_root / f"user_{user_id}" user_dir.mkdir(parents=True, exist_ok=True) # Create project directory project_dir = user_dir / name # Handle name conflicts counter = 1 original_name = name while project_dir.exists(): name = f"{original_name}_{counter}" project_dir = user_dir / name counter += 1 project_dir.mkdir(parents=True, exist_ok=True) # Return relative path (from workspace root) and absolute path relative_path = f"user_{user_id}/{name}" return relative_path, project_dir.resolve() def delete_project_directory(project_path: str) -> bool: """ Delete a project directory Args: project_path: Relative path from workspace root Returns: True if deleted successfully """ workspace_root = get_workspace_root() project_dir = workspace_root / project_path if project_dir.exists() and project_dir.is_dir(): # Verify it's within workspace root (security check) try: project_dir.resolve().relative_to(workspace_root.resolve()) shutil.rmtree(project_dir) return True except ValueError: raise ValueError("Cannot delete directory outside workspace root") return False def copy_folder_to_project(source_path: str, project_dir: Path, project_name: str) -> dict: """ Copy a folder to project directory (for folder upload) Args: source_path: Source folder path project_dir: Target project directory project_name: Project name Returns: Dict with copy statistics """ source = Path(source_path) if not source.exists(): raise ValueError(f"Source path does not exist: {source_path}") if not source.is_dir(): raise ValueError(f"Source path is not a directory: {source_path}") # Security check: don't copy from sensitive system directories sensitive_dirs = ["/etc", "/usr", "/bin", "/sbin", "/root", "/home"] for sensitive in sensitive_dirs: if str(source.resolve()).startswith(sensitive): raise ValueError(f"Cannot copy from system directory: {sensitive}") # Copy directory if project_dir.exists(): shutil.rmtree(project_dir) shutil.copytree(source, project_dir) # Count files file_count = sum(1 for _ in project_dir.rglob("*") if _.is_file()) dir_count = sum(1 for _ in project_dir.rglob("*") if _.is_dir()) return { "files": file_count, "directories": dir_count, "size": sum(f.stat().st_size for f in project_dir.rglob("*") if f.is_file()) }