From 8595a48795a8572232c240585a7451c695423766 Mon Sep 17 00:00:00 2001 From: lif <1835304752@qq.com> Date: Thu, 18 Dec 2025 00:31:10 +0800 Subject: [PATCH] fix: Content-Disposition header format to comply with RFC 2183 Fixes #8914 The Content-Disposition header was set to `filename="name.ext"` which doesn't match RFC 2183 specification. This caused third-party libraries (e.g., Go's mime.ParseMediaType) to fail parsing the header. Changed to `inline; filename="name.ext"` format which properly includes the disposition-type as required by RFC 2183. --- server.py | 8 +- .../server_test/test_content_disposition.py | 75 +++++++++++++++++++ 2 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 tests-unit/server_test/test_content_disposition.py diff --git a/server.py b/server.py index ac4f42222..ca8bd726f 100644 --- a/server.py +++ b/server.py @@ -511,7 +511,7 @@ class PromptServer(): buffer.seek(0) return web.Response(body=buffer.read(), content_type=f'image/{image_format}', - headers={"Content-Disposition": f"filename=\"{filename}\""}) + headers={"Content-Disposition": f"inline; filename=\"{filename}\""}) if 'channel' not in request.rel_url.query: channel = 'rgba' @@ -531,7 +531,7 @@ class PromptServer(): buffer.seek(0) return web.Response(body=buffer.read(), content_type='image/png', - headers={"Content-Disposition": f"filename=\"{filename}\""}) + headers={"Content-Disposition": f"inline; filename=\"{filename}\""}) elif channel == 'a': with Image.open(file) as img: @@ -548,7 +548,7 @@ class PromptServer(): alpha_buffer.seek(0) return web.Response(body=alpha_buffer.read(), content_type='image/png', - headers={"Content-Disposition": f"filename=\"{filename}\""}) + headers={"Content-Disposition": f"inline; filename=\"{filename}\""}) else: # Get content type from mimetype, defaulting to 'application/octet-stream' content_type = mimetypes.guess_type(filename)[0] or 'application/octet-stream' @@ -560,7 +560,7 @@ class PromptServer(): return web.FileResponse( file, headers={ - "Content-Disposition": f"filename=\"{filename}\"", + "Content-Disposition": f"inline; filename=\"{filename}\"", "Content-Type": content_type } ) diff --git a/tests-unit/server_test/test_content_disposition.py b/tests-unit/server_test/test_content_disposition.py new file mode 100644 index 000000000..dc54ac25c --- /dev/null +++ b/tests-unit/server_test/test_content_disposition.py @@ -0,0 +1,75 @@ +"""Tests for Content-Disposition header format (RFC 2183 compliance) + +Relates to issue #8914: Content-Disposition Header not matching RFC2183 rules +""" + +import pytest +import re + + +class TestContentDispositionHeader: + """Test Content-Disposition header format compliance with RFC 2183.""" + + def test_rfc2183_format_with_inline(self): + """Verify inline disposition type format matches RFC 2183.""" + filename = "test_image.png" + header = f'inline; filename="{filename}"' + + # RFC 2183 requires disposition-type followed by parameters + # Format: disposition-type ";" disposition-parm + pattern = r'^(inline|attachment);\s*filename="[^"]*"$' + assert re.match(pattern, header), f"Header '{header}' does not match RFC 2183 format" + + def test_rfc2183_format_with_attachment(self): + """Verify attachment disposition type format matches RFC 2183.""" + filename = "download.mp4" + header = f'attachment; filename="{filename}"' + + pattern = r'^(inline|attachment);\s*filename="[^"]*"$' + assert re.match(pattern, header), f"Header '{header}' does not match RFC 2183 format" + + def test_invalid_format_missing_disposition_type(self): + """Verify that format without disposition type is invalid.""" + filename = "test.jpg" + invalid_header = f'filename="{filename}"' + + pattern = r'^(inline|attachment);\s*filename="[^"]*"$' + assert not re.match(pattern, invalid_header), \ + "Header without disposition type should not match RFC 2183 format" + + @pytest.mark.parametrize("filename", [ + "image.png", + "video.mp4", + "file with spaces.jpg", + "special_chars-123.webp", + ]) + def test_various_filenames(self, filename): + """Test RFC 2183 format with various filename patterns.""" + header = f'inline; filename="{filename}"' + + # Should have disposition type before filename + assert header.startswith("inline; ") or header.startswith("attachment; ") + assert f'filename="{filename}"' in header + + +class TestContentDispositionParsing: + """Test that Content-Disposition headers can be parsed by standard libraries.""" + + def test_parse_inline_disposition(self): + """Test parsing inline disposition header.""" + header = 'inline; filename="test.png"' + + # Simple parsing test - split by semicolon + parts = [p.strip() for p in header.split(';')] + assert parts[0] in ('inline', 'attachment') + assert 'filename=' in parts[1] + + def test_extract_filename(self): + """Test extracting filename from header.""" + filename = "my_image.jpg" + header = f'inline; filename="{filename}"' + + # Extract filename using regex + match = re.search(r'filename="([^"]*)"', header) + assert match is not None + assert match.group(1) == filename