mirror of
https://github.com/langgenius/dify.git
synced 2026-03-08 00:55:57 +08:00
- Removed unused app asset download and upload endpoints, along with sandbox archive and file download endpoints. - Updated imports in the file controller to reflect the removal of these endpoints. - Simplified the generator.py file by consolidating the code context field definition. - Enhanced the storage layer with a unified presign wrapper for better handling of presigned URLs.
103 lines
3.2 KiB
Python
103 lines
3.2 KiB
Python
"""Unified file proxy controller for storage operations.
|
|
|
|
This controller handles file download and upload operations when the underlying
|
|
storage backend doesn't support presigned URLs. It verifies signed proxy URLs
|
|
generated by FilePresignStorage and streams files to/from storage.
|
|
|
|
Endpoints:
|
|
GET /files/storage/{filename}/download - Download a file
|
|
PUT /files/storage/{filename}/upload - Upload a file
|
|
"""
|
|
|
|
from urllib.parse import quote, unquote
|
|
|
|
from flask import Response, request
|
|
from flask_restx import Resource
|
|
from pydantic import BaseModel, Field
|
|
from werkzeug.exceptions import Forbidden, NotFound
|
|
|
|
from controllers.files import files_ns
|
|
from extensions.ext_storage import storage
|
|
from extensions.storage.file_presign_storage import FilePresignStorage
|
|
|
|
DEFAULT_REF_TEMPLATE_SWAGGER_2_0 = "#/definitions/{model}"
|
|
|
|
|
|
class StorageProxyQuery(BaseModel):
|
|
"""Query parameters for storage proxy URLs."""
|
|
|
|
timestamp: str = Field(..., description="Unix timestamp used in the signature")
|
|
nonce: str = Field(..., description="Random string for signature")
|
|
sign: str = Field(..., description="HMAC signature")
|
|
|
|
|
|
files_ns.schema_model(
|
|
StorageProxyQuery.__name__,
|
|
StorageProxyQuery.model_json_schema(ref_template=DEFAULT_REF_TEMPLATE_SWAGGER_2_0),
|
|
)
|
|
|
|
|
|
@files_ns.route("/storage/<path:filename>/download")
|
|
class StorageFileDownloadApi(Resource):
|
|
"""Handle file downloads through the proxy."""
|
|
|
|
def get(self, filename: str):
|
|
"""Download a file from storage.
|
|
|
|
Verifies the signed URL and streams the file content back to the client.
|
|
"""
|
|
filename = unquote(filename)
|
|
args = StorageProxyQuery.model_validate(request.args.to_dict(flat=True))
|
|
|
|
if not FilePresignStorage.verify_signature(
|
|
filename=filename,
|
|
operation="download",
|
|
timestamp=args.timestamp,
|
|
nonce=args.nonce,
|
|
sign=args.sign,
|
|
):
|
|
raise Forbidden("Invalid or expired download link")
|
|
|
|
try:
|
|
generator = storage.load_stream(filename)
|
|
except FileNotFoundError:
|
|
raise NotFound("File not found")
|
|
|
|
encoded_filename = quote(filename.split("/")[-1])
|
|
|
|
return Response(
|
|
generator,
|
|
mimetype="application/octet-stream",
|
|
direct_passthrough=True,
|
|
headers={
|
|
"Content-Disposition": f"attachment; filename*=UTF-8''{encoded_filename}",
|
|
},
|
|
)
|
|
|
|
|
|
@files_ns.route("/storage/<path:filename>/upload")
|
|
class StorageFileUploadApi(Resource):
|
|
"""Handle file uploads through the proxy."""
|
|
|
|
def put(self, filename: str):
|
|
"""Upload a file to storage.
|
|
|
|
Verifies the signed URL and saves the request body to storage.
|
|
"""
|
|
filename = unquote(filename)
|
|
args = StorageProxyQuery.model_validate(request.args.to_dict(flat=True))
|
|
|
|
if not FilePresignStorage.verify_signature(
|
|
filename=filename,
|
|
operation="upload",
|
|
timestamp=args.timestamp,
|
|
nonce=args.nonce,
|
|
sign=args.sign,
|
|
):
|
|
raise Forbidden("Invalid or expired upload link")
|
|
|
|
content = request.get_data()
|
|
storage.save(filename, content)
|
|
|
|
return Response(status=204)
|