[BACKEND] Validate write permissions on working directory and output directory

This commit is contained in:
Jesse Bannon 2025-12-06 14:54:32 -08:00
parent 8d5d2be6a7
commit a56e1249ec
5 changed files with 56 additions and 0 deletions

View file

@ -12,6 +12,8 @@ from ytdl_sub.config.defaults import DEFAULT_FFPROBE_PATH
from ytdl_sub.config.defaults import DEFAULT_LOCK_DIRECTORY
from ytdl_sub.config.defaults import MAX_FILE_NAME_BYTES
from ytdl_sub.prebuilt_presets import PREBUILT_PRESETS
from ytdl_sub.utils.exceptions import SubscriptionPermissionError
from ytdl_sub.utils.file_handler import FileHandler
from ytdl_sub.validators.file_path_validators import FFmpegFileValidator
from ytdl_sub.validators.file_path_validators import FFprobeFileValidator
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
@ -147,6 +149,12 @@ class ConfigOptions(StrictDictValidator):
key="file_name_max_bytes", validator=IntValidator, default=MAX_FILE_NAME_BYTES
)
if not FileHandler.is_path_writable(self.working_directory):
raise SubscriptionPermissionError(
"ytdl-sub does not have permissions to the working directory: "
f"{self.working_directory}"
)
@property
def working_directory(self) -> str:
"""

View file

@ -10,6 +10,8 @@ from ytdl_sub.config.preset_options import OutputOptions
from ytdl_sub.config.preset_options import YTDLOptions
from ytdl_sub.downloaders.url.validators import MultiUrlValidator
from ytdl_sub.entries.variables.override_variables import SubscriptionVariables
from ytdl_sub.utils.exceptions import SubscriptionPermissionError
from ytdl_sub.utils.file_handler import FileHandler
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
from ytdl_sub.utils.logger import Logger
from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
@ -94,6 +96,12 @@ class BaseSubscription(ABC):
self._exception: Optional[Exception] = None
if not FileHandler.is_path_writable(self.output_directory):
raise SubscriptionPermissionError(
"ytdl-sub does not have write permissions to the output directory: "
f"{self.output_directory}"
)
@property
def download_archive(self) -> EnhancedDownloadArchive:
"""

View file

@ -40,3 +40,7 @@ class FileNotDownloadedException(ValueError):
class ExperimentalFeatureNotEnabled(ValidationException):
"""Feature is not enabled for usage"""
class SubscriptionPermissionError(ValidationException):
"""Early-caught permission error"""

View file

@ -379,6 +379,22 @@ class FileHandler:
"""
return self._file_handler_transaction_log
@classmethod
def is_path_writable(cls, src_file_path: Union[str, Path]) -> bool:
""""
Check whether a path is writable. If it does not exist, try to find the base directory
and check permissions on that.
"""
path = os.path.abspath(src_file_path)
while not os.path.exists(path):
new_path = os.path.dirname(path)
if new_path == path: # reached root
break
path = new_path
return os.access(path, os.W_OK)
@classmethod
def copy(cls, src_file_path: Union[str, Path], dst_file_path: Union[str, Path]):
"""

View file

@ -0,0 +1,20 @@
import pytest
from ytdl_sub.utils.file_handler import FileHandler
from ytdl_sub.utils.system import IS_WINDOWS
class TestFileHandler:
def test_directory_exists(self):
if IS_WINDOWS:
return
assert FileHandler.is_path_writable("/tmp")
assert FileHandler.is_path_writable("/tmp/")
assert FileHandler.is_path_writable("/tmp/non-existent")
assert FileHandler.is_path_writable("/tmp/non-existent/")
assert FileHandler.is_path_writable("/tmp/non-existent/nested")
assert not FileHandler.is_path_writable("/lol-in-root")
assert not FileHandler.is_path_writable("/")