mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-04-06 00:36:49 +08:00
Add a new server-side download API that allows frontends and desktop apps
to download models directly into ComfyUI's models directory, eliminating
the need for DOM scraping of the frontend UI.
New files:
- app/download_manager.py: Async download manager with streaming downloads,
pause/resume/cancel, manual redirect following with per-hop host validation,
sidecar metadata for safe resume, and concurrency limiting.
API endpoints (all under /download/, also mirrored at /api/download/):
- POST /download/model - Start a download (url, directory, filename)
- GET /download/status - List all downloads (filterable by client_id)
- GET /download/status/{id} - Get single download status
- POST /download/pause/{id} - Pause (cancels transfer, keeps temp)
- POST /download/resume/{id} - Resume (new request with Range header)
- POST /download/cancel/{id} - Cancel and clean up temp files
Security:
- Gated behind --enable-download-api CLI flag (403 if disabled)
- HTTPS-only with exact host allowlist (huggingface.co, civitai.com + CDNs)
- Manual redirect following with per-hop host validation (no SSRF)
- Path traversal protection via realpath + commonpath
- Extension allowlist (.safetensors, .sft)
- Filename sanitization (no separators, .., control chars)
- Destination re-checked before final rename
- Progress events scoped to initiating client_id
Closes Comfy-Org/ComfyUI-Desktop-2.0-Beta#293
Amp-Thread-ID: https://ampcode.com/threads/T-019d2344-139e-77a5-9f24-1cbb3b26a8ec
Co-authored-by: Amp <amp@ampcode.com>
74 lines
1.9 KiB
Python
74 lines
1.9 KiB
Python
"""
|
|
Feature flags module for ComfyUI WebSocket protocol negotiation.
|
|
|
|
This module handles capability negotiation between frontend and backend,
|
|
allowing graceful protocol evolution while maintaining backward compatibility.
|
|
"""
|
|
|
|
from typing import Any
|
|
|
|
from comfy.cli_args import args
|
|
|
|
# Default server capabilities
|
|
SERVER_FEATURE_FLAGS: dict[str, Any] = {
|
|
"supports_preview_metadata": True,
|
|
"max_upload_size": args.max_upload_size * 1024 * 1024, # Convert MB to bytes
|
|
"extension": {"manager": {"supports_v4": True}},
|
|
"node_replacements": True,
|
|
"assets": args.enable_assets,
|
|
"download_api": args.enable_download_api,
|
|
}
|
|
|
|
|
|
def get_connection_feature(
|
|
sockets_metadata: dict[str, dict[str, Any]],
|
|
sid: str,
|
|
feature_name: str,
|
|
default: Any = False
|
|
) -> Any:
|
|
"""
|
|
Get a feature flag value for a specific connection.
|
|
|
|
Args:
|
|
sockets_metadata: Dictionary of socket metadata
|
|
sid: Session ID of the connection
|
|
feature_name: Name of the feature to check
|
|
default: Default value if feature not found
|
|
|
|
Returns:
|
|
Feature value or default if not found
|
|
"""
|
|
if sid not in sockets_metadata:
|
|
return default
|
|
|
|
return sockets_metadata[sid].get("feature_flags", {}).get(feature_name, default)
|
|
|
|
|
|
def supports_feature(
|
|
sockets_metadata: dict[str, dict[str, Any]],
|
|
sid: str,
|
|
feature_name: str
|
|
) -> bool:
|
|
"""
|
|
Check if a connection supports a specific feature.
|
|
|
|
Args:
|
|
sockets_metadata: Dictionary of socket metadata
|
|
sid: Session ID of the connection
|
|
feature_name: Name of the feature to check
|
|
|
|
Returns:
|
|
Boolean indicating if feature is supported
|
|
"""
|
|
return get_connection_feature(sockets_metadata, sid, feature_name, False) is True
|
|
|
|
|
|
def get_server_features() -> dict[str, Any]:
|
|
"""
|
|
Get the server's feature flags.
|
|
|
|
Returns:
|
|
Dictionary of server feature flags
|
|
"""
|
|
return SERVER_FEATURE_FLAGS.copy()
|