diff --git a/ui/pages/downloads.py b/ui/pages/downloads.py index 51f9a777..2c04fcb6 100644 --- a/ui/pages/downloads.py +++ b/ui/pages/downloads.py @@ -7751,6 +7751,11 @@ class DownloadsPage(QWidget): sanitized = re.sub(r'[<>:"/\\|?*]', '_', filename) # Remove multiple spaces and trim sanitized = re.sub(r'\s+', ' ', sanitized).strip() + # Windows forbids trailing dots/spaces on files and folders + sanitized = sanitized.rstrip('. ') or '_' + # Windows reserved device names + if re.match(r'^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\.|$)', sanitized, re.IGNORECASE): + sanitized = '_' + sanitized # Limit length to avoid filesystem issues return sanitized[:200] if len(sanitized) > 200 else sanitized diff --git a/web_server.py b/web_server.py index 484f3cd5..6acf2097 100644 --- a/web_server.py +++ b/web_server.py @@ -12543,6 +12543,13 @@ def _sanitize_filename(filename: str) -> str: import re sanitized = re.sub(r'[<>:"/\\|?*]', '_', filename) sanitized = re.sub(r'\s+', ' ', sanitized).strip() + # Windows forbids trailing dots/spaces on files and folders. + # Artists like "Fred again.." would create mangled 8.3 short names. + sanitized = sanitized.rstrip('. ') or '_' + # Windows reserved device names (CON, PRN, AUX, NUL, COM1-9, LPT1-9) + # can't be used as file or folder names even with extensions. + if re.match(r'^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\.|$)', sanitized, re.IGNORECASE): + sanitized = '_' + sanitized return sanitized[:200] def _sanitize_context_values(context: dict) -> dict: