mirror of
https://github.com/langgenius/dify.git
synced 2026-08-15 04:59:46 +08:00
fix: preserve file preview content type (#37211)
Co-authored-by: FFXN <31929997+FFXN@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
0b36a0bbc0
commit
c903e93e8b
@ -1,3 +1,4 @@
|
||||
import os
|
||||
from urllib.parse import quote
|
||||
from uuid import UUID
|
||||
|
||||
@ -29,6 +30,18 @@ class FilePreviewQuery(FileSignatureQuery):
|
||||
register_schema_models(files_ns, FileSignatureQuery, FilePreviewQuery)
|
||||
|
||||
|
||||
def _is_svg_content(mime_type: str | None, filename: str | None, extension: str | None) -> bool:
|
||||
normalized_mime_type = mime_type.split(";", 1)[0].strip().lower() if mime_type else ""
|
||||
if normalized_mime_type == "image/svg+xml":
|
||||
return True
|
||||
|
||||
normalized_extension = extension.lstrip(".").lower() if extension else ""
|
||||
if normalized_extension == "svg":
|
||||
return True
|
||||
|
||||
return bool(filename and os.path.splitext(filename)[1].lstrip(".").lower() == "svg")
|
||||
|
||||
|
||||
@files_ns.route("/<uuid:file_id>/image-preview")
|
||||
class ImagePreviewApi(Resource):
|
||||
"""Deprecated endpoint for retrieving image previews."""
|
||||
@ -129,10 +142,13 @@ class FilePreviewApi(Resource):
|
||||
response.headers["Accept-Ranges"] = "bytes"
|
||||
if upload_file.size > 0:
|
||||
response.headers["Content-Length"] = str(upload_file.size)
|
||||
if args.as_attachment:
|
||||
is_svg = _is_svg_content(upload_file.mime_type, upload_file.name, upload_file.extension)
|
||||
if args.as_attachment or is_svg:
|
||||
encoded_filename = quote(upload_file.name)
|
||||
response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{encoded_filename}"
|
||||
response.headers["Content-Type"] = "application/octet-stream"
|
||||
response.headers["Content-Type"] = "application/octet-stream"
|
||||
if is_svg:
|
||||
response.headers["X-Content-Type-Options"] = "nosniff"
|
||||
|
||||
enforce_download_for_html(
|
||||
response,
|
||||
|
||||
@ -79,7 +79,7 @@ class TestImagePreviewApi:
|
||||
class TestFilePreviewApi:
|
||||
@patch.object(module, "enforce_download_for_html")
|
||||
@patch.object(module, "FileService")
|
||||
def test_basic_stream(self, mock_file_service, mock_enforce):
|
||||
def test_inline_preview_uses_upload_file_mimetype(self, mock_file_service, mock_enforce):
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
@ -90,7 +90,12 @@ class TestFilePreviewApi:
|
||||
)
|
||||
|
||||
generator = iter([b"data"])
|
||||
upload_file = DummyUploadFile(size=100)
|
||||
upload_file = DummyUploadFile(
|
||||
mime_type="application/pdf",
|
||||
size=100,
|
||||
name="doc.pdf",
|
||||
extension="pdf",
|
||||
)
|
||||
|
||||
mock_file_service.return_value.get_file_generator_by_file_id.return_value = (
|
||||
generator,
|
||||
@ -102,11 +107,87 @@ class TestFilePreviewApi:
|
||||
|
||||
response = get_fn("file-id")
|
||||
|
||||
assert response.mimetype == "application/octet-stream"
|
||||
assert response.mimetype == "application/pdf"
|
||||
assert response.headers["Content-Type"] == "application/pdf"
|
||||
assert response.headers["Content-Length"] == "100"
|
||||
assert "Accept-Ranges" not in response.headers
|
||||
mock_enforce.assert_called_once()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("mime_type", "name", "extension"),
|
||||
[
|
||||
("Image/SVG+XML; charset=UTF-8", "image.png", "png"),
|
||||
("image/png", "image.SVG", "png"),
|
||||
("image/png", "image.png", ".SVG"),
|
||||
],
|
||||
ids=("mime-type", "filename", "extension"),
|
||||
)
|
||||
@patch.object(module, "FileService")
|
||||
def test_svg_preview_forces_download(self, mock_file_service, mime_type, name, extension):
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "sig",
|
||||
"as_attachment": False,
|
||||
}
|
||||
)
|
||||
|
||||
generator = iter([b"<svg></svg>"])
|
||||
upload_file = DummyUploadFile(
|
||||
mime_type=mime_type,
|
||||
size=11,
|
||||
name=name,
|
||||
extension=extension,
|
||||
)
|
||||
|
||||
mock_file_service.return_value.get_file_generator_by_file_id.return_value = (
|
||||
generator,
|
||||
upload_file,
|
||||
)
|
||||
|
||||
api = module.FilePreviewApi()
|
||||
get_fn = unwrap(api.get)
|
||||
|
||||
response = get_fn("file-id")
|
||||
|
||||
assert response.headers["Content-Disposition"].startswith("attachment")
|
||||
assert response.headers["Content-Type"] == "application/octet-stream"
|
||||
assert response.headers["X-Content-Type-Options"] == "nosniff"
|
||||
|
||||
@patch.object(module, "FileService")
|
||||
def test_html_preview_still_forces_download(self, mock_file_service):
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "sig",
|
||||
"as_attachment": False,
|
||||
}
|
||||
)
|
||||
|
||||
generator = iter([b"<script>alert(1)</script>"])
|
||||
upload_file = DummyUploadFile(
|
||||
mime_type="text/html",
|
||||
size=25,
|
||||
name="unsafe.html",
|
||||
extension="html",
|
||||
)
|
||||
|
||||
mock_file_service.return_value.get_file_generator_by_file_id.return_value = (
|
||||
generator,
|
||||
upload_file,
|
||||
)
|
||||
|
||||
api = module.FilePreviewApi()
|
||||
get_fn = unwrap(api.get)
|
||||
|
||||
response = get_fn("file-id")
|
||||
|
||||
assert response.headers["Content-Disposition"].startswith("attachment")
|
||||
assert response.headers["Content-Type"] == "application/octet-stream"
|
||||
assert response.headers["X-Content-Type-Options"] == "nosniff"
|
||||
|
||||
@patch.object(module, "enforce_download_for_html")
|
||||
@patch.object(module, "FileService")
|
||||
def test_as_attachment(self, mock_file_service, mock_enforce):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user