From 40f6073862979c29205ccea004aa951b23c7a345 Mon Sep 17 00:00:00 2001 From: RUiNtheExtinct Date: Sun, 28 Dec 2025 14:43:19 +0530 Subject: [PATCH] fix(server): use RFC 2183 backslash escaping for quotes in filenames Previous approach replaced " with ' which corrupts the actual filename. RFC 2183 specifies that within a quoted-string, backslash is the escape character. Now properly escapes both \ and " with backslash prefix. The UTF-8 filename* parameter uses percent-encoding which correctly preserves the original filename for modern clients. --- server.py | 4 ++-- .../server_test/test_content_disposition.py | 21 +++++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/server.py b/server.py index ca71e13b3..5a47c768a 100644 --- a/server.py +++ b/server.py @@ -200,8 +200,8 @@ def create_content_disposition_header(filename: str) -> str: """ # ASCII-safe filename for legacy clients (replace non-ASCII with ?) ascii_filename = filename.encode('ascii', 'replace').decode('ascii') - # Escape quotes to prevent malformed header (e.g., filename="file"name.png") - ascii_filename = ascii_filename.replace('"', "'") + # RFC 2183: escape backslashes and quotes within quoted-string + ascii_filename = ascii_filename.replace('\\', '\\\\').replace('"', '\\"') # RFC 5987 percent-encoded filename for UTF-8 support encoded_filename = quote(filename, safe='') return f"attachment; filename=\"{ascii_filename}\"; filename*=UTF-8''{encoded_filename}" diff --git a/tests-unit/server_test/test_content_disposition.py b/tests-unit/server_test/test_content_disposition.py index 36f87410e..1d4f163f1 100644 --- a/tests-unit/server_test/test_content_disposition.py +++ b/tests-unit/server_test/test_content_disposition.py @@ -15,8 +15,8 @@ def create_content_disposition_header(filename: str) -> str: """ # ASCII-safe filename for legacy clients (replace non-ASCII with ?) ascii_filename = filename.encode('ascii', 'replace').decode('ascii') - # Escape quotes to prevent malformed header (e.g., filename="file"name.png") - ascii_filename = ascii_filename.replace('"', "'") + # RFC 2183: escape backslashes and quotes within quoted-string + ascii_filename = ascii_filename.replace('\\', '\\\\').replace('"', '\\"') # RFC 5987 percent-encoded filename for UTF-8 support encoded_filename = quote(filename, safe='') return f"attachment; filename=\"{ascii_filename}\"; filename*=UTF-8''{encoded_filename}" @@ -81,16 +81,25 @@ class TestContentDispositionHeader: assert 'filename=""' in result def test_filename_with_quotes(self): - """Test that quotes in filename don't break the header.""" - # Quotes in filenames must be escaped to prevent malformed headers + """Test that quotes in filename are properly escaped per RFC 2183.""" result = create_content_disposition_header('file"name.png') assert result.startswith("attachment;") - # Quotes should be replaced with single quotes in ASCII fallback - assert "filename=\"file'name.png\"" in result + # RFC 2183: quotes must be backslash-escaped within quoted-string + assert 'filename="file\\"name.png"' in result # UTF-8 version should have percent-encoded quote assert "filename*=UTF-8''file%22name.png" in result + def test_filename_with_backslash(self): + """Test that backslashes in filename are properly escaped per RFC 2183.""" + result = create_content_disposition_header('file\\name.png') + + assert result.startswith("attachment;") + # RFC 2183: backslashes must be escaped as \\ + assert 'filename="file\\\\name.png"' in result + # UTF-8 version should have percent-encoded backslash + assert "filename*=UTF-8''file%5Cname.png" in result + def test_mixed_ascii_unicode(self): """Test filenames with both ASCII and Unicode characters.""" result = create_content_disposition_header("photo_照片_2024.png")