diff --git a/3rd_party_apps/sendfile/__init__.py b/3rd_party_apps/sendfile/__init__.py index 9d7ce68ad2..eea11a74d8 100644 --- a/3rd_party_apps/sendfile/__init__.py +++ b/3rd_party_apps/sendfile/__init__.py @@ -4,6 +4,8 @@ __version__ = '.'.join(map(str, VERSION)) import os.path from mimetypes import guess_type +from django.http import Http404 + def _lazy_load(fn): _cached = [] def _decorated(): @@ -19,7 +21,7 @@ def _get_sendfile(): from django.conf import settings from django.core.exceptions import ImproperlyConfigured - backend = getattr(settings, 'SENDFILE_BACKEND', 'sendfile.backends.simple') + backend = getattr(settings, 'SENDFILE_BACKEND', None) if not backend: raise ImproperlyConfigured('You must specify a valued for SENDFILE_BACKEND') module = import_module(backend) @@ -36,16 +38,22 @@ def sendfile(request, filename, attachment=False, attachment_filename=None): will typically prompt the user to download the file, rather than view it. ''' _sendfile = _get_sendfile() - response = _sendfile(request, filename) + + if not os.path.exists(filename): + raise Http404('"%s" does not exist' % filename) + + mimetype, encoding = guess_type(filename) + if mimetype is None: + mimetype = 'application/octet-stream' + + response = _sendfile(request, filename, mimetype=mimetype) if attachment: attachment_filename = attachment_filename or os.path.basename(filename) response['Content-Disposition'] = 'attachment; filename=%s' % attachment_filename response['Content-length'] = os.path.getsize(filename) - - content_type = guess_type(filename)[0] - if content_type is None: - content_type = "application/octet-stream" - response['Content-Type'] = content_type + response['Content-Type'] = mimetype + if encoding: + response['Content-Encoding'] = encoding return response diff --git a/3rd_party_apps/sendfile/backends/development.py b/3rd_party_apps/sendfile/backends/development.py index 5fa2221188..08a7dd0a42 100644 --- a/3rd_party_apps/sendfile/backends/development.py +++ b/3rd_party_apps/sendfile/backends/development.py @@ -2,7 +2,7 @@ from django.views.static import serve import os.path -def sendfile(request, filename): +def sendfile(request, filename, **kwargs): ''' Send file using django dev static file server. diff --git a/3rd_party_apps/sendfile/backends/mod_wsgi.py b/3rd_party_apps/sendfile/backends/mod_wsgi.py index 82cf55c806..b8bdfc58d1 100644 --- a/3rd_party_apps/sendfile/backends/mod_wsgi.py +++ b/3rd_party_apps/sendfile/backends/mod_wsgi.py @@ -18,7 +18,7 @@ def _convert_file_to_url(filename): return u''.join(url) -def sendfile(request, filename): +def sendfile(request, filename, **kwargs): response = HttpResponse() response['Location'] = _convert_file_to_url(filename) # need to destroy get_host() to stop django diff --git a/3rd_party_apps/sendfile/backends/simple.py b/3rd_party_apps/sendfile/backends/simple.py index d65e8d6b9a..e74eeeb8b0 100644 --- a/3rd_party_apps/sendfile/backends/simple.py +++ b/3rd_party_apps/sendfile/backends/simple.py @@ -1,6 +1,56 @@ -from django.core.servers.basehttp import FileWrapper -from django.http import HttpResponse +import os +import stat +import re +from email.Utils import parsedate_tz, mktime_tz + +from django.core.files.base import File +from django.http import HttpResponse, HttpResponseNotModified +from django.utils.http import http_date + +def sendfile(request, filename, **kwargs): + # Respect the If-Modified-Since header. + statobj = os.stat(filename) + mimetype = kwargs.get('mimetype', 'application/octet-stream') + + if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), + statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]): + return HttpResponseNotModified(mimetype=mimetype) + + + response = HttpResponse(File(file(filename, 'rb'))) + + response["Last-Modified"] = http_date(statobj[stat.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 -def sendfile(request, filename): - wrapper = FileWrapper(file(filename)) - return HttpResponse(wrapper) diff --git a/3rd_party_apps/sendfile/backends/xsendfile.py b/3rd_party_apps/sendfile/backends/xsendfile.py index 5bba6a3759..b0f2720f60 100644 --- a/3rd_party_apps/sendfile/backends/xsendfile.py +++ b/3rd_party_apps/sendfile/backends/xsendfile.py @@ -1,6 +1,6 @@ from django.http import HttpResponse -def sendfile(request, filename): +def sendfile(request, filename, **kwargs): response = HttpResponse() response['X-Sendfile'] = filename