61 lines
1.9 KiB
Python
61 lines
1.9 KiB
Python
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
|