from functools import lru_cache from importlib import import_module from pathlib import Path, PurePath from urllib.parse import quote import logging from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.http import Http404 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) # Python 3.5: Path.resolve() has no `strict` kwarg, so use pathmod from an # already instantiated Path object url = relpath._flavour.pathmod.normpath(str(url_root / relpath)) 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 # Python 3.5: Path.resolve() has no `strict` kwarg, so use pathmod from an # already instantiated Path object filepath_abs = Path(filepath_obj._flavour.pathmod.normpath(str(path_root / filepath_obj))) # if filepath_abs is not relative to path_root, relative_to throws an error try: filepath_abs.relative_to(path_root) except ValueError: raise Http404("{} wrt {} is impossible".format(filepath_abs, path_root)) return filepath_abs def sendfile(request, filename, mimetype="application/octet-stream", encoding=None): """ 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(): raise Http404('"%s" does not exist' % filepath_obj) response = _sendfile(request, filepath_obj, mimetype=mimetype) response["Content-length"] = filepath_obj.stat().st_size response["Content-Type"] = mimetype return response