{"record":{"id":"df6dcd1e90196c55","repo":"harry0703/MoneyPrinterTurbo","slug":"invalid-background-music-filename","errorCode":null,"errorMessage":"invalid background music filename","messagePattern":"invalid background music filename","errorType":"validation","errorClass":"BgmUploadError","httpStatus":400,"severity":"error","filePath":"app/services/bgm.py","lineNumber":101,"sourceCode":"        # 等更准确的原始异常覆盖掉，但必须留下路径和系统错误供运维定位。\n        logger.warning(\n            f\"failed to remove staged background music: path={file_path}, \"\n            f\"error={str(exc)}\"\n        )\n\n\ndef sanitize_upload_filename(filename: str) -> str:\n    \"\"\"提取可跨平台展示的音频文件名，并拒绝非法名称与不支持的扩展名。\"\"\"\n    safe_name = (filename or \"\").replace(\"\\\\\", \"/\").split(\"/\")[-1].strip()\n    if (\n        not safe_name\n        or safe_name in {\".\", \"..\"}\n        or len(safe_name) > 255\n        or any(ord(character) < 32 for character in safe_name)\n        or any(character in _WINDOWS_INVALID_FILENAME_CHARS for character in safe_name)\n        or safe_name.lower().startswith(_INTERNAL_UPLOAD_PREFIX)\n    ):\n        raise BgmUploadError(\"invalid background music filename\")\n\n    # Windows 会把扩展名前的首段识别为设备名，例如 CON.mp3、LPT1.wav 都\n    # 不能作为普通文件创建。即使服务端最终使用 UUID，提前拒绝这类名称也能\n    # 保证 API 在不同平台上的输入行为一致。\n    windows_basename = safe_name.split(\".\", 1)[0].rstrip(\" .\").upper()\n    if windows_basename in _WINDOWS_RESERVED_FILENAMES:\n        raise BgmUploadError(\"invalid background music filename\")\n    if Path(safe_name).suffix.lower() not in SUPPORTED_BGM_EXTENSIONS:\n        supported_formats = \", \".join(\n            extension.removeprefix(\".\").upper()\n            for extension in SUPPORTED_BGM_EXTENSIONS\n        )\n        raise BgmUploadError(\n            f\"unsupported background music format; supported formats: {supported_formats}\"\n        )\n    return safe_name\n\n","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/harry0703/MoneyPrinterTurbo/blob/1f9f19c2021a68d04df228f33e9099a0c947f6f8/app/services/bgm.py#L83-L119","documentation":"Raised by sanitize_upload_filename in app/services/bgm.py when the extracted basename fails basic hygiene: empty, '.'/'..', longer than 255 chars, contains control characters (ord < 32), contains Windows-invalid filename characters, or starts with the server's internal upload prefix (_INTERNAL_UPLOAD_PREFIX, reserved for temp files the service itself creates). All these make the name unsafe or ambiguous to persist cross-platform, so it is rejected before any bytes are written.","triggerScenarios":"Uploading with filename 'aux?.mp3' (invalid char), a >255-char name, a name with embedded \\0 or other control chars, or one beginning with the internal prefix (e.g. '.bgm-tmp-...' style) that could collide with staged temp files.","commonSituations":"Metadata-rich auto-generated names (track titles) exceeding length limits; filenames copied from Windows with trailing spaces/invalid chars; adversarial or fuzzed upload attempts probing the temp-file namespace.","solutions":["Rename the file client-side to a short, plain basename (letters, digits, dash, underscore, one allowed audio extension).","Strip control characters and the internal prefix before upload if you generate names programmatically.","Cap generated names to <255 characters including extension."],"exampleFix":"# before\nname = track_title + \".mp3\"   # could be 300 chars or contain '*/:<>?' chars\n\n# after\nimport re\nname = re.sub(r\"[^\\w\\-. ]\", \"\", track_title)[:200].strip() or \"audio\"\nname += \".mp3\"","handlingStrategy":"validation","validationCode":"import re\nname = (filename or \"\").replace(\"\\\\\", \"/\").split(\"/\")[-1].strip()\nif (not name or name in {\".\", \"..\"} or len(name) > 255\n        or any(ord(c) < 32 for c in name)\n        or re.search(r'[<>:\"/\\\\|?*]', name)):\n    raise ValueError(\"filename fails server hygiene rules\")","typeGuard":"def is_hygienic_filename(name: str) -> bool:\n    n = (name or \"\").replace(\"\\\\\", \"/\").split(\"/\")[-1].strip()\n    return (\n        bool(n) and n not in {\".\", \"..\"} and len(n) <= 255\n        and all(ord(c) >= 32 for c in n)\n    )","tryCatchPattern":null,"preventionTips":["Generate upload names from a strict allowlist alphabet (\\w, dash, dot).","Cap names at 200-odd chars to leave room for the extension.","Never echo internal server temp-prefix patterns into user filenames."],"tags":["filename","upload","bgm","sanitization"],"backgroundTag":null,"analyzedSha":"1f9f19c2021a68d04df228f33e9099a0c947f6f8","analyzedAt":"2026-08-14T19:41:05.568Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}