2024-06-08 21:51:44 +00:00
|
|
|
import logging
|
2024-06-09 00:27:13 +00:00
|
|
|
import os
|
2020-12-14 14:00:48 +00:00
|
|
|
from functools import lru_cache
|
|
|
|
from importlib import import_module
|
|
|
|
from pathlib import Path, PurePath
|
|
|
|
from urllib.parse import quote
|
2020-12-28 16:44:55 +00:00
|
|
|
|
2020-12-14 14:00:48 +00:00
|
|
|
from django.conf import settings
|
|
|
|
from django.core.exceptions import ImproperlyConfigured
|
2024-06-08 21:51:44 +00:00
|
|
|
from fastapi import status
|
|
|
|
|
|
|
|
from ..exceptions import HttpError
|
2020-12-14 14:00:48 +00:00
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
@lru_cache(maxsize=None)
|
|
|
|
def _get_sendfile():
|
|
|
|
backend = getattr(settings, "SENDFILE_BACKEND", None)
|
|
|
|
if not backend:
|
|
|
|
raise ImproperlyConfigured("You must specify a value for SENDFILE_BACKEND")
|
|
|
|
module = import_module(backend)
|
|
|
|
return module.sendfile
|
|
|
|
|
|
|
|
|
|
|
|
def _convert_file_to_url(path):
|
|
|
|
try:
|
|
|
|
url_root = PurePath(getattr(settings, "SENDFILE_URL", None))
|
|
|
|
except TypeError:
|
|
|
|
return path
|
|
|
|
|
|
|
|
path_root = PurePath(settings.SENDFILE_ROOT)
|
|
|
|
path_obj = PurePath(path)
|
|
|
|
|
|
|
|
relpath = path_obj.relative_to(path_root)
|
2024-06-09 00:27:13 +00:00
|
|
|
url = os.path.normpath(str(url_root / relpath))
|
2020-12-14 14:00:48 +00:00
|
|
|
|
|
|
|
return quote(str(url))
|
|
|
|
|
|
|
|
|
|
|
|
def _sanitize_path(filepath):
|
|
|
|
try:
|
|
|
|
path_root = Path(getattr(settings, "SENDFILE_ROOT", None))
|
|
|
|
except TypeError:
|
|
|
|
raise ImproperlyConfigured("You must specify a value for SENDFILE_ROOT")
|
|
|
|
|
|
|
|
filepath_obj = Path(filepath)
|
|
|
|
|
|
|
|
# get absolute path
|
2024-06-09 00:27:13 +00:00
|
|
|
filepath_abs = Path(os.path.normpath(str(path_root / filepath_obj)))
|
2020-12-14 14:00:48 +00:00
|
|
|
|
|
|
|
# if filepath_abs is not relative to path_root, relative_to throws an error
|
|
|
|
try:
|
|
|
|
filepath_abs.relative_to(path_root)
|
|
|
|
except ValueError:
|
2020-12-29 13:37:11 +00:00
|
|
|
raise HttpError(
|
|
|
|
"generic", "{} wrt {} is impossible".format(filepath_abs, path_root), status_code=status.HTTP_404_NOT_FOUND
|
|
|
|
)
|
2020-12-14 14:00:48 +00:00
|
|
|
|
|
|
|
return filepath_abs
|
|
|
|
|
|
|
|
|
2020-12-28 16:44:55 +00:00
|
|
|
def sendfile(filename, mimetype="application/octet-stream", encoding=None):
|
2020-12-14 14:00:48 +00:00
|
|
|
"""
|
|
|
|
Create a response to send file using backend configured in ``SENDFILE_BACKEND``
|
|
|
|
|
|
|
|
``filename`` is the absolute path to the file to send.
|
|
|
|
"""
|
|
|
|
filepath_obj = _sanitize_path(filename)
|
|
|
|
logger.debug(
|
|
|
|
"filename '%s' requested \"\
|
|
|
|
\"-> filepath '%s' obtained",
|
|
|
|
filename,
|
|
|
|
filepath_obj,
|
|
|
|
)
|
|
|
|
_sendfile = _get_sendfile()
|
|
|
|
|
|
|
|
if not filepath_obj.exists():
|
2020-12-28 16:44:55 +00:00
|
|
|
raise HttpError("does_not_exist", '"%s" does not exist' % filepath_obj, status_code=status.HTTP_404_NOT_FOUND)
|
2020-12-14 14:00:48 +00:00
|
|
|
|
2020-12-28 16:44:55 +00:00
|
|
|
response = _sendfile(filepath_obj, mimetype=mimetype)
|
2020-12-14 14:00:48 +00:00
|
|
|
|
2020-12-28 16:44:55 +00:00
|
|
|
response.headers["Content-Type"] = mimetype
|
2020-12-14 14:00:48 +00:00
|
|
|
|
|
|
|
return response
|