@@ -540,6 +540,46 @@ def test_download_video_uses_resolved_file(self):
540540 self .assertEqual (response .filename , "final-1.mp4" )
541541 self .assertEqual (response .media_type , "video/mp4" )
542542
543+ def test_download_video_encodes_content_disposition_filename (self ):
544+ """
545+ 下载文件名必须按 HTTP 标准编码。
546+
547+ 普通 ASCII 文件名继续使用兼容性更好的 filename 参数;只要名称包含
548+ 空格、中文或响应头敏感符号,就应改用 UTF-8 filename*,避免浏览器下载
549+ 失败、文件名乱码,或特殊字符破坏 Content-Disposition 响应头结构。
550+ """
551+ cases = (
552+ ("final-1.mp4" , 'attachment; filename="final-1.mp4"' ),
553+ ("video name.mp4" , "attachment; filename*=utf-8''video%20name.mp4" ),
554+ (
555+ "中文 视频.mp4" ,
556+ "attachment; filename*=utf-8''%E4%B8%AD%E6%96%87%20"
557+ "%E8%A7%86%E9%A2%91.mp4" ,
558+ ),
559+ (
560+ 'name="draft";v1\\ test.mp4' ,
561+ "attachment; filename*=utf-8''name%3D%22draft%22%3Bv1%5Ctest.mp4" ,
562+ ),
563+ )
564+
565+ with tempfile .TemporaryDirectory () as temp_dir :
566+ with patch .object (
567+ video_controller .utils ,
568+ "task_dir" ,
569+ return_value = temp_dir ,
570+ ):
571+ for filename , expected_header in cases :
572+ with self .subTest (filename = filename ):
573+ Path (temp_dir , filename ).write_bytes (b"video" )
574+ response = asyncio .run (
575+ video_controller .download_video (self ._request (), filename )
576+ )
577+
578+ self .assertEqual (
579+ response .headers ["content-disposition" ],
580+ expected_header ,
581+ )
582+
543583
544584if __name__ == "__main__" :
545585 unittest .main ()
0 commit comments