Spaces:
Build error
Build error
import os | |
import re | |
from openhands.core.config import OpenHandsConfig | |
from openhands.core.logger import openhands_logger as logger | |
from openhands.server.shared import config as shared_config | |
FILES_TO_IGNORE = [ | |
'.git/', | |
'.DS_Store', | |
'node_modules/', | |
'__pycache__/', | |
'lost+found/', | |
'.vscode/', | |
] | |
def sanitize_filename(filename: str) -> str: | |
"""Sanitize the filename to prevent directory traversal""" | |
# Remove any directory components | |
filename = os.path.basename(filename) | |
# Remove any non-alphanumeric characters except for .-_ | |
filename = re.sub(r'[^\w\-_\.]', '', filename) | |
# Limit the filename length | |
max_length = 255 | |
if len(filename) > max_length: | |
name, ext = os.path.splitext(filename) | |
filename = name[: max_length - len(ext)] + ext | |
return filename | |
def load_file_upload_config( | |
config: OpenHandsConfig = shared_config, | |
) -> tuple[int, bool, list[str]]: | |
"""Load file upload configuration from the config object. | |
This function retrieves the file upload settings from the global config object. | |
It handles the following settings: | |
- Maximum file size for uploads | |
- Whether to restrict file types | |
- List of allowed file extensions | |
It also performs sanity checks on the values to ensure they are valid and safe. | |
Returns: | |
tuple: A tuple containing: | |
- max_file_size_mb (int): Maximum file size in MB. 0 means no limit. | |
- restrict_file_types (bool): Whether file type restrictions are enabled. | |
- allowed_extensions (set): Set of allowed file extensions. | |
""" | |
# Retrieve values from config | |
max_file_size_mb = config.file_uploads_max_file_size_mb | |
restrict_file_types = config.file_uploads_restrict_file_types | |
allowed_extensions = config.file_uploads_allowed_extensions | |
# Sanity check for max_file_size_mb | |
if not isinstance(max_file_size_mb, int) or max_file_size_mb < 0: | |
logger.warning( | |
f'Invalid max_file_size_mb: {max_file_size_mb}. Setting to 0 (no limit).' | |
) | |
max_file_size_mb = 0 | |
# Sanity check for allowed_extensions | |
if not isinstance(allowed_extensions, (list, set)) or not allowed_extensions: | |
logger.warning( | |
f'Invalid allowed_extensions: {allowed_extensions}. Setting to [".*"].' | |
) | |
allowed_extensions = ['.*'] | |
else: | |
# Ensure all extensions start with a dot and are lowercase | |
allowed_extensions = [ | |
ext.lower() if ext.startswith('.') else f'.{ext.lower()}' | |
for ext in allowed_extensions | |
] | |
# If restrictions are disabled, allow all | |
if not restrict_file_types: | |
allowed_extensions = ['.*'] | |
logger.debug( | |
f'File upload config: max_size={max_file_size_mb}MB, ' | |
f'restrict_types={restrict_file_types}, ' | |
f'allowed_extensions={allowed_extensions}' | |
) | |
return max_file_size_mb, restrict_file_types, allowed_extensions | |
# Load configuration | |
MAX_FILE_SIZE_MB, RESTRICT_FILE_TYPES, ALLOWED_EXTENSIONS = load_file_upload_config() | |
def is_extension_allowed(filename: str) -> bool: | |
"""Check if the file extension is allowed based on the current configuration. | |
This function supports wildcards and files without extensions. | |
The check is case-insensitive for extensions. | |
Args: | |
filename (str): The name of the file to check. | |
Returns: | |
bool: True if the file extension is allowed, False otherwise. | |
""" | |
if not RESTRICT_FILE_TYPES: | |
return True | |
file_ext = os.path.splitext(filename)[1].lower() # Convert to lowercase | |
return ( | |
'.*' in ALLOWED_EXTENSIONS | |
or file_ext in (ext.lower() for ext in ALLOWED_EXTENSIONS) | |
or (file_ext == '' and '.' in ALLOWED_EXTENSIONS) | |
) | |