Implement sendfile.

This commit is contained in:
Tom Hacohen 2020-12-28 18:44:55 +02:00
parent c7f09d3fef
commit f0a8689712
15 changed files with 46 additions and 124 deletions

View File

@ -1,17 +0,0 @@
import os.path
from django.views.static import serve
def sendfile(request, filename, **kwargs):
"""
Send file using Django dev static file server.
.. warning::
Do not use in production. This is only to be used when developing and
is provided for convenience only
"""
dirname = os.path.dirname(filename)
basename = os.path.basename(filename)
return serve(request, basename, dirname)

View File

@ -1,17 +0,0 @@
from __future__ import absolute_import
from django.http import HttpResponse
from ..utils import _convert_file_to_url
def sendfile(request, filename, **kwargs):
response = HttpResponse()
response['Location'] = _convert_file_to_url(filename)
# need to destroy get_host() to stop django
# rewriting our location to include http, so that
# mod_wsgi is able to do the internal redirect
request.get_host = lambda: ''
request.build_absolute_uri = lambda location: location
return response

View File

@ -1,12 +0,0 @@
from __future__ import absolute_import
from django.http import HttpResponse
from ..utils import _convert_file_to_url
def sendfile(request, filename, **kwargs):
response = HttpResponse()
response['X-Accel-Redirect'] = _convert_file_to_url(filename)
return response

View File

@ -1,60 +0,0 @@
from email.utils import mktime_tz, parsedate_tz
import re
from django.core.files.base import File
from django.http import HttpResponse, HttpResponseNotModified
from django.utils.http import http_date
def sendfile(request, filepath, **kwargs):
'''Use the SENDFILE_ROOT value composed with the path arrived as argument
to build an absolute path with which resolve and return the file contents.
If the path points to a file out of the root directory (should cover both
situations with '..' and symlinks) then a 404 is raised.
'''
statobj = filepath.stat()
# Respect the If-Modified-Since header.
if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
statobj.st_mtime, statobj.st_size):
return HttpResponseNotModified()
with File(filepath.open('rb')) as f:
response = HttpResponse(f.chunks())
response["Last-Modified"] = http_date(statobj.st_mtime)
return response
def was_modified_since(header=None, mtime=0, size=0):
"""
Was something modified since the user last downloaded it?
header
This is the value of the If-Modified-Since header. If this is None,
I'll just return True.
mtime
This is the modification time of the item we're talking about.
size
This is the size of the item we're talking about.
"""
try:
if header is None:
raise ValueError
matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
re.IGNORECASE)
header_date = parsedate_tz(matches.group(1))
if header_date is None:
raise ValueError
header_mtime = mktime_tz(header_date)
header_len = matches.group(3)
if header_len and int(header_len) != size:
raise ValueError
if mtime > header_mtime:
raise ValueError
except (AttributeError, ValueError, OverflowError):
return True
return False

View File

@ -1,9 +0,0 @@
from django.http import HttpResponse
def sendfile(request, filename, **kwargs):
filename = str(filename)
response = HttpResponse()
response['X-Sendfile'] = filename
return response

View File

@ -7,7 +7,6 @@ from django.core.files.base import ContentFile
from django.db import transaction, IntegrityError from django.db import transaction, IntegrityError
from django.db.models import Q, QuerySet from django.db.models import Q, QuerySet
from fastapi import APIRouter, Depends, status, Request from fastapi import APIRouter, Depends, status, Request
from fastapi.responses import FileResponse
from django_etebase import models from django_etebase import models
from .authentication import get_authenticated_user from .authentication import get_authenticated_user
@ -26,6 +25,7 @@ from .utils import (
PERMISSIONS_READWRITE, PERMISSIONS_READWRITE,
) )
from .dependencies import get_collection_queryset, get_item_queryset, get_collection from .dependencies import get_collection_queryset, get_item_queryset, get_collection
from .sendfile import sendfile
User = get_user_model() User = get_user_model()
collection_router = APIRouter(route_class=MsgpackRoute, responses=permission_responses) collection_router = APIRouter(route_class=MsgpackRoute, responses=permission_responses)
@ -582,4 +582,4 @@ def chunk_download(
chunk = get_object_or_404(collection.chunks, uid=chunk_uid) chunk = get_object_or_404(collection.chunks, uid=chunk_uid)
filename = chunk.chunkFile.path filename = chunk.chunkFile.path
return FileResponse(filename, media_type="application/octet-stream") return sendfile(filename)

View File

@ -0,0 +1,9 @@
from __future__ import absolute_import
from fastapi import Response
from ..utils import _convert_file_to_url
def sendfile(filename, **kwargs):
return Response(headers={"Location": _convert_file_to_url(filename)})

View File

@ -0,0 +1,9 @@
from __future__ import absolute_import
from fastapi import Response
from ..utils import _convert_file_to_url
def sendfile(filename, **kwargs):
return Response(headers={"X-Accel-Redirect": _convert_file_to_url(filename)})

View File

@ -0,0 +1,12 @@
from fastapi.responses import FileResponse
def sendfile(filename, mimetype, **kwargs):
"""Use the SENDFILE_ROOT value composed with the path arrived as argument
to build an absolute path with which resolve and return the file contents.
If the path points to a file out of the root directory (should cover both
situations with '..' and symlinks) then a 404 is raised.
"""
return FileResponse(filename, media_type=mimetype)

View File

@ -0,0 +1,6 @@
from fastapi import Response
def sendfile(filename, **kwargs):
filename = str(filename)
return Response(headers={"X-Sendfile": filename})

View File

@ -4,9 +4,11 @@ from pathlib import Path, PurePath
from urllib.parse import quote from urllib.parse import quote
import logging import logging
from fastapi import status
from ..exceptions import HttpError
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.http import Http404
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -54,12 +56,12 @@ def _sanitize_path(filepath):
try: try:
filepath_abs.relative_to(path_root) filepath_abs.relative_to(path_root)
except ValueError: except ValueError:
raise Http404("{} wrt {} is impossible".format(filepath_abs, path_root)) raise HttpError("generic", "{} wrt {} is impossible".format(filepath_abs, path_root), status_code=status.HTTP_404_NOT_FOUND)
return filepath_abs return filepath_abs
def sendfile(request, filename, mimetype="application/octet-stream", encoding=None): def sendfile(filename, mimetype="application/octet-stream", encoding=None):
""" """
Create a response to send file using backend configured in ``SENDFILE_BACKEND`` Create a response to send file using backend configured in ``SENDFILE_BACKEND``
@ -75,11 +77,10 @@ def sendfile(request, filename, mimetype="application/octet-stream", encoding=No
_sendfile = _get_sendfile() _sendfile = _get_sendfile()
if not filepath_obj.exists(): if not filepath_obj.exists():
raise Http404('"%s" does not exist' % filepath_obj) raise HttpError("does_not_exist", '"%s" does not exist' % filepath_obj, status_code=status.HTTP_404_NOT_FOUND)
response = _sendfile(request, filepath_obj, mimetype=mimetype) response = _sendfile(filepath_obj, mimetype=mimetype)
response["Content-length"] = filepath_obj.stat().st_size response.headers["Content-Type"] = mimetype
response["Content-Type"] = mimetype
return response return response