# Architecture Guide ## High-Level Design ``` ┌──────────────────────────────────────────────────────────┐ │ Application Layer (UI) │ │ ┌────────────────────────────────────────────────────────┤ │ │ QMainWindow (main_window.py) │ │ │ ├─ QWebEngineView (web app container) │ │ │ └─ DragInterceptor (drag handling) │ │ └────────────────────────────────────────────────────────┘ │ ↓ Events (dragEnterEvent, etc.) ├──────────────────────────────────────────────────────────┤ │ Business Logic Layer (Core) │ │ ┌────────────────────────────────────────────────────────┤ │ │ PathValidator (security & validation) │ │ │ DragInterceptor (drag/drop conversion) │ │ │ Config (configuration management) │ │ └────────────────────────────────────────────────────────┘ │ ↓ Platform APIs ├──────────────────────────────────────────────────────────┤ │ Platform Layer (OS Integration) │ │ ┌────────────────────────────────────────────────────────┤ │ │ QUrl (Windows: CF_HDROP, macOS: NSFilenamesPboardType)│ │ │ QMimeData (cross-platform mime data) │ │ │ QDrag (native drag-drop implementation) │ │ └────────────────────────────────────────────────────────┘ ``` ## Module Organization ### `src/webdrop_bridge/core/` **Purpose**: Core business logic, independent of UI **Key Components:** - `validator.py`: Path validation against whitelist with security checks - `drag_interceptor.py`: Drag event handling and native drag operations - `config_manager.py`: Configuration loading from files and caching - `url_converter.py`: Azure Blob Storage URL → local path conversion - `updater.py`: Update checking via Forgejo API **Dependencies**: None (only stdlib + pathlib) ### `src/webdrop_bridge/ui/` **Purpose**: User interface components using Qt/PySide6 **Key Components:** - `main_window.py`: Main application window with web engine integration - `restricted_web_view.py`: Hardened QWebEngineView with security policies - `settings_dialog.py`: Settings UI for configuration - `update_manager_ui.py`: Update checking and notification UI - `bridge_script_intercept.js`: JavaScript drag interception and WebChannel bridge for Qt communication - `download_interceptor.js`: Download handling for web content **Dependencies**: PySide6, core/ ### `src/webdrop_bridge/utils/` **Purpose**: Shared utilities and helpers **Key Components:** - `logging.py`: Logging configuration (console + file with rotation) **Dependencies**: stdlib only ## Data Flow ### Drag-and-Drop Operation ``` User in Web App (browser) ↓ [dragstart event] → bridge_script_intercept.js detects drag ├─ Checks if content is convertible (file path or Azure URL) ├─ Calls window.bridge.start_file_drag(url) └─ preventDefault() → Blocks normal browser drag ↓ JavaScript → QWebChannel Bridge ↓ _DragBridge.start_file_drag(path_text) [main_window.py] ├─ Defers execution via QTimer (drag manager safety) └─ Calls DragInterceptor.handle_drag() ↓ DragInterceptor.handle_drag() [core/drag_interceptor.py] ├─ Check if Azure URL: Use URLConverter → local path ├─ Else: Treat as direct file path └─ Validate with PathValidator ↓ PathValidator.validate(path) ├─ Resolve to absolute path ├─ Check file exists (if configured) ├─ Check is regular file (not directory) └─ Check path within allowed_roots (whitelist) ↓ If valid: → Create QUrl.fromLocalFile(path) → Create QMimeData with file URL → QDrag.exec(Qt.CopyAction) → Native file drag → Emit drag_started signal ↓ If invalid: → Emit drag_failed signal with error → Log validation error ↓ OS receives native file drag ↓ Target application (InDesign/Word) receives file handle ``` **Key Components in Data Flow:** 1. **bridge_script_intercept.js**: Opens a WebChannel to Qt's _DragBridge 2. **_DragBridge**: Exposes `start_file_drag()` slot to JavaScript 3. **DragInterceptor**: Handles validation and native drag creation 4. **URLConverter**: Maps Azure Blob Storage URLs to local paths via config 5. **PathValidator**: Security-critical validation against whitelist ## Security Model ### Path Validation Strategy 1. **Whitelist-based**: Only allow configured root directories 2. **Absolute path resolution**: Prevent directory traversal attacks 3. **Symlink handling**: Resolve to real path before checking 4. **File existence check**: Don't drag non-existent files 5. **Type verification**: Only allow regular files ```python # Example allowed roots ALLOWED_ROOTS = [ Path("Z:/"), # Network share Path("C:/Users/Public"), # Windows public folder ] # Example validation path = Path(r"Z:\projects\file.psd") → Resolve: Z:\projects\file.psd → Check: starts with Z:\? YES → Check: file exists? YES → Check: is regular file? YES → Result: ALLOW ``` ### Web Engine Security - **LocalContentCanAccessFileUrls**: Enabled (required for drag) - **LocalContentCanAccessRemoteUrls**: Disabled (prevent phishing) - **Certificate validation**: Enforced for HTTPS ## Performance Considerations ### Drag Operation Timing ``` Event Time (ms) Critical Path ───────────────────────────────────── dragenter <1 ✓ Must be fast validation <10 ✓ Keep fast QUrl creation <1 QDrag.exec() <50 ✓ Start quickly ───────────────────────── TOTAL <50 ✓ Imperceptible ``` ### Memory Management - **Web engine**: ~100-150 MB baseline - **Drag interceptor**: Minimal overhead - **Logging**: Rotated to prevent growth - **File cache**: None (on-demand validation) ## Testing Strategy ### Unit Testing ``` validator.py ├─ test_is_allowed() ├─ test_is_valid_file() └─ test_symlink_handling() config.py ├─ test_load_from_env() ├─ test_default_values() └─ test_validation() drag_interceptor.py ├─ test_dragEnterEvent() ├─ test_invalid_path_rejected() └─ test_file_drag_created() ``` ### Integration Testing ``` end_to_end.py ├─ test_app_startup() ├─ test_webapp_loads() ├─ test_complete_drag_workflow() └─ test_invalid_path_handling() ``` ## Extension Points ### Adding New Handlers ```python # Future: Support for additional file types or sources class HandlerRegistry: def register(self, source_type: str, handler: Handler): self.handlers[source_type] = handler def handle(self, data: QMimeData) -> Optional[QMimeData]: for handler in self.handlers.values(): if handler.can_handle(data): return handler.handle(data) return None ``` ### Custom Validators ```python # Future: Pluggable validation rules class ValidatorPlugin(ABC): @abstractmethod def validate(self, path: Path) -> bool: pass class FileSizeValidator(ValidatorPlugin): def __init__(self, max_size: int): self.max_size = max_size def validate(self, path: Path) -> bool: return path.stat().st_size <= self.max_size ``` ## Deployment Architecture ### Single-File Executable ``` WebDropBridge.exe (built with PyInstaller) ├─ Python runtime ├─ PySide6 libraries ├─ Qt libraries ├─ Embedded web app (index.html) └─ Bundled resources (icons, etc.) Size: ~100-120 MB Startup: <1 second ``` ### Portable Deployment - Single executable: No installation required - No registry entries: Clean removal - Relative paths: Can run from any directory - Configuration: `.env` file per instance ## Platform Differences ### Windows - **Drag Format**: CF_HDROP (file paths) - **File URLs**: `file:///C:/path/to/file` - **Paths**: Backslash `\` (converted to forward `/`) - **Permissions**: Admin privileges not required ### macOS - **Drag Format**: NSFilenamesPboardType (same as Windows, different implementation) - **File URLs**: `file:///Users/username/path/to/file` - **Paths**: Forward slash `/` (native) - **Permissions**: May require accessibility permissions ## Update Manager The `UpdateManager` class checks for new releases using the Forgejo API. It caches results and only signals updates for newer versions. See `src/webdrop_bridge/core/updater.py` for implementation. ## Release Flow - Checks for new releases on startup or user request - Parses release notes and assets - Notifies UI if update is available ## Integration Test Strategy Integration tests verify workflows across modules. The update workflow is covered in [tests/integration/test_update_flow.py](../tests/integration/test_update_flow.py). --- For more details, see [DEVELOPMENT_PLAN.md](DEVELOPMENT_PLAN.md)