file_config.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import os
  2. import re
  3. from openhands.core.config import AppConfig
  4. from openhands.core.logger import openhands_logger as logger
  5. from openhands.server.shared import config as shared_config
  6. FILES_TO_IGNORE = [
  7. '.git/',
  8. '.DS_Store',
  9. 'node_modules/',
  10. '__pycache__/',
  11. ]
  12. def sanitize_filename(filename):
  13. """Sanitize the filename to prevent directory traversal"""
  14. # Remove any directory components
  15. filename = os.path.basename(filename)
  16. # Remove any non-alphanumeric characters except for .-_
  17. filename = re.sub(r'[^\w\-_\.]', '', filename)
  18. # Limit the filename length
  19. max_length = 255
  20. if len(filename) > max_length:
  21. name, ext = os.path.splitext(filename)
  22. filename = name[: max_length - len(ext)] + ext
  23. return filename
  24. def load_file_upload_config(
  25. config: AppConfig = shared_config,
  26. ) -> tuple[int, bool, list[str]]:
  27. """Load file upload configuration from the config object.
  28. This function retrieves the file upload settings from the global config object.
  29. It handles the following settings:
  30. - Maximum file size for uploads
  31. - Whether to restrict file types
  32. - List of allowed file extensions
  33. It also performs sanity checks on the values to ensure they are valid and safe.
  34. Returns:
  35. tuple: A tuple containing:
  36. - max_file_size_mb (int): Maximum file size in MB. 0 means no limit.
  37. - restrict_file_types (bool): Whether file type restrictions are enabled.
  38. - allowed_extensions (set): Set of allowed file extensions.
  39. """
  40. # Retrieve values from config
  41. max_file_size_mb = config.file_uploads_max_file_size_mb
  42. restrict_file_types = config.file_uploads_restrict_file_types
  43. allowed_extensions = config.file_uploads_allowed_extensions
  44. # Sanity check for max_file_size_mb
  45. if not isinstance(max_file_size_mb, int) or max_file_size_mb < 0:
  46. logger.warning(
  47. f'Invalid max_file_size_mb: {max_file_size_mb}. Setting to 0 (no limit).'
  48. )
  49. max_file_size_mb = 0
  50. # Sanity check for allowed_extensions
  51. if not isinstance(allowed_extensions, (list, set)) or not allowed_extensions:
  52. logger.warning(
  53. f'Invalid allowed_extensions: {allowed_extensions}. Setting to [".*"].'
  54. )
  55. allowed_extensions = ['.*']
  56. else:
  57. # Ensure all extensions start with a dot and are lowercase
  58. allowed_extensions = [
  59. ext.lower() if ext.startswith('.') else f'.{ext.lower()}'
  60. for ext in allowed_extensions
  61. ]
  62. # If restrictions are disabled, allow all
  63. if not restrict_file_types:
  64. allowed_extensions = ['.*']
  65. logger.debug(
  66. f'File upload config: max_size={max_file_size_mb}MB, '
  67. f'restrict_types={restrict_file_types}, '
  68. f'allowed_extensions={allowed_extensions}'
  69. )
  70. return max_file_size_mb, restrict_file_types, allowed_extensions
  71. # Load configuration
  72. MAX_FILE_SIZE_MB, RESTRICT_FILE_TYPES, ALLOWED_EXTENSIONS = load_file_upload_config()
  73. def is_extension_allowed(filename):
  74. """Check if the file extension is allowed based on the current configuration.
  75. This function supports wildcards and files without extensions.
  76. The check is case-insensitive for extensions.
  77. Args:
  78. filename (str): The name of the file to check.
  79. Returns:
  80. bool: True if the file extension is allowed, False otherwise.
  81. """
  82. if not RESTRICT_FILE_TYPES:
  83. return True
  84. file_ext = os.path.splitext(filename)[1].lower() # Convert to lowercase
  85. return (
  86. '.*' in ALLOWED_EXTENSIONS
  87. or file_ext in (ext.lower() for ext in ALLOWED_EXTENSIONS)
  88. or (file_ext == '' and '.' in ALLOWED_EXTENSIONS)
  89. )