From 37a43babb9a98e7b4d1367ac11bbe7075742fec9 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Fri, 6 Feb 2026 10:44:22 -0500 Subject: [PATCH] fix(v3-actions): fix UnicodeEncodeError in download filenames Hardened StreamingResponse headers to use RFC 6266 filename* parameter with UTF-8 encoding. This prevents the latin-1 codec crash when filenames contain non-ASCII characters like smart quotes. --- app/routers/api_v3_actions_hosted_file.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/routers/api_v3_actions_hosted_file.py b/app/routers/api_v3_actions_hosted_file.py index 58ab9f6..8cca3f0 100644 --- a/app/routers/api_v3_actions_hosted_file.py +++ b/app/routers/api_v3_actions_hosted_file.py @@ -7,6 +7,7 @@ import pathlib from typing import Dict, List, Optional, Set, Union import asyncio import logging +from urllib.parse import quote log = logging.getLogger(__name__) @@ -286,6 +287,12 @@ async def download_file_action( end = min(end, file_size - 1) content_length = end - start + 1 + # ID Vision: Properly encode filename for headers to avoid UnicodeEncodeError (latin-1) + # 1. Standard filename (Sanitized for legacy clients - latin-1 safe) + safe_filename = target_filename.encode('ascii', errors='ignore').decode('ascii') + # 2. filename* (UTF-8 encoded for modern clients) + encoded_filename = quote(target_filename) + return StreamingResponse( file_streamer(full_file_path, start, end + 1), media_type = media_type, @@ -294,7 +301,7 @@ async def download_file_action( 'Accept-Ranges': 'bytes', 'Content-Range': f'bytes {start}-{end}/{file_size}', 'Content-Length': str(content_length), - 'Content-Disposition': f'attachment; filename="{target_filename}"' + 'Content-Disposition': f'attachment; filename="{safe_filename}"; filename*=utf-8\'\'{encoded_filename}' } )