From a5c8e22c41000133632243093bc12c73172b0bb4 Mon Sep 17 00:00:00 2001 From: J C Lawrence Date: Mon, 16 Dec 2013 20:06:48 -0800 Subject: [PATCH 1/5] Added no_file_check to sendfile for remote proxy case --- sendfile/__init__.py | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/sendfile/__init__.py b/sendfile/__init__.py index 67c0c1c..b853c4b 100644 --- a/sendfile/__init__.py +++ b/sendfile/__init__.py @@ -32,9 +32,9 @@ def _get_sendfile(): -def sendfile(request, filename, attachment=False, attachment_filename=None, mimetype=None, encoding=None): - ''' - create a response to send file using backend configured in SENDFILE_BACKEND +def sendfile(request, filename, attachment=False, attachment_filename=None, mimetype=None, encoding=None, no_file_check = False): + '''create a response to send file using backend configured in + SENDFILE_BACKEND If attachment is True the content-disposition header will be set. This will typically prompt the user to download the file, rather @@ -45,22 +45,35 @@ def sendfile(request, filename, attachment=False, attachment_filename=None, mime False: No content-disposition filename String: Value used as filename - If no mimetype or encoding are specified, then they will be guessed via the - filename (using the standard python mimetypes module) + If no mimetype or encoding are specified, then they will be + guessed via the filename (using the standard python mimetypes + module) + + If no_file_Check is true, sendfile() will check that the file + exists on the filesystem where stated. If no_file_check is False, + then sendfile() will simply encode the path without checking for + its existence. In this case it also won't set the Content-Length + header. This mode can be useful when using sendfile() under a + proxy atop a remote store (eg S3). + ''' _sendfile = _get_sendfile() - if not os.path.exists(filename): + if file_check and not os.path.exists(filename): from django.http import Http404 raise Http404('"%s" does not exist' % filename) - guessed_mimetype, guessed_encoding = guess_type(filename) - if mimetype is None: - if guessed_mimetype: - mimetype = guessed_mimetype - else: - mimetype = 'application/octet-stream' - + + if file_check: + guessed_mimetype, guessed_encoding = guess_type(filename) + if mimetype is None: + if guessed_mimetype: + mimetype = guessed_mimetype + else: + mimetype = 'application/octet-stream' + else: + mimetype = 'application/octet-stream' + response = _sendfile(request, filename, mimetype=mimetype) if attachment: if attachment_filename is None: @@ -82,7 +95,8 @@ def sendfile(request, filename, attachment=False, attachment_filename=None, mime parts.append('filename*=UTF-8\'\'%s' % quoted_filename) response['Content-Disposition'] = '; '.join(parts) - response['Content-length'] = os.path.getsize(filename) + if file_check: + response['Content-length'] = os.path.getsize(filename) response['Content-Type'] = mimetype if not encoding: encoding = guessed_encoding From 304fbb54ca0b6ee610169f1bcb1a0d56b71452b2 Mon Sep 17 00:00:00 2001 From: J C Lawrence Date: Mon, 16 Dec 2013 21:06:09 -0800 Subject: [PATCH 2/5] Silly var name change --- sendfile/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sendfile/__init__.py b/sendfile/__init__.py index b853c4b..ecf9d92 100644 --- a/sendfile/__init__.py +++ b/sendfile/__init__.py @@ -59,12 +59,12 @@ def sendfile(request, filename, attachment=False, attachment_filename=None, mime ''' _sendfile = _get_sendfile() - if file_check and not os.path.exists(filename): + if not no_file_check and not os.path.exists(filename): from django.http import Http404 raise Http404('"%s" does not exist' % filename) - if file_check: + if not no_file_check: guessed_mimetype, guessed_encoding = guess_type(filename) if mimetype is None: if guessed_mimetype: @@ -95,7 +95,7 @@ def sendfile(request, filename, attachment=False, attachment_filename=None, mime parts.append('filename*=UTF-8\'\'%s' % quoted_filename) response['Content-Disposition'] = '; '.join(parts) - if file_check: + if not no_file_check: response['Content-length'] = os.path.getsize(filename) response['Content-Type'] = mimetype if not encoding: From 712a51be82183c5e069b459f4647e333fea54917 Mon Sep 17 00:00:00 2001 From: J C Lawrence Date: Mon, 16 Dec 2013 21:08:13 -0800 Subject: [PATCH 3/5] Encoding --- sendfile/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendfile/__init__.py b/sendfile/__init__.py index ecf9d92..eba1b28 100644 --- a/sendfile/__init__.py +++ b/sendfile/__init__.py @@ -98,7 +98,7 @@ def sendfile(request, filename, attachment=False, attachment_filename=None, mime if not no_file_check: response['Content-length'] = os.path.getsize(filename) response['Content-Type'] = mimetype - if not encoding: + if not encoding and not no_file_check: encoding = guessed_encoding if encoding: response['Content-Encoding'] = encoding From c6c496c3ddc6ec37184b7a4a90d363c0eddb6be4 Mon Sep 17 00:00:00 2001 From: J C Lawrence Date: Tue, 17 Dec 2013 13:43:31 -0800 Subject: [PATCH 4/5] Stop rewriting the path against the filesystem. --- sendfile/__init__.py | 5 +++-- sendfile/backends/_internalredirect.py | 16 ++++++++++++---- sendfile/backends/mod_wsgi.py | 4 ++-- sendfile/backends/nginx.py | 3 ++- sendfile/backends/simple.py | 12 ++++++------ sendfile/backends/xsendfile.py | 2 -- 6 files changed, 25 insertions(+), 17 deletions(-) diff --git a/sendfile/__init__.py b/sendfile/__init__.py index eba1b28..168ad89 100644 --- a/sendfile/__init__.py +++ b/sendfile/__init__.py @@ -53,8 +53,9 @@ def sendfile(request, filename, attachment=False, attachment_filename=None, mime exists on the filesystem where stated. If no_file_check is False, then sendfile() will simply encode the path without checking for its existence. In this case it also won't set the Content-Length - header. This mode can be useful when using sendfile() under a - proxy atop a remote store (eg S3). + or Content-Encoding header, so your surrounding proxy will need to + do that as needed. This mode can be useful when using sendfile() + under a proxy atop a remote store (eg S3). ''' _sendfile = _get_sendfile() diff --git a/sendfile/backends/_internalredirect.py b/sendfile/backends/_internalredirect.py index 0aa1b37..dc9d3c2 100644 --- a/sendfile/backends/_internalredirect.py +++ b/sendfile/backends/_internalredirect.py @@ -1,14 +1,22 @@ from django.conf import settings import os.path -def _convert_file_to_url(filename): +def _convert_file_to_url(filename, no_file_check = False): + """In the normal case (no_file_check is False) reduce the filepath + against the filesystem and content root. If no_file_check is + True, then don't do any of that and instead assume that the path + passed is correct and in its ultimate form. + + """ + if no_file_check: # We already a priori know that the path is + # correct and in its final form. + return filename relpath = os.path.relpath(filename, settings.SENDFILE_ROOT) - + url = [settings.SENDFILE_URL] while relpath: relpath, head = os.path.split(relpath) url.insert(1, head) - return u'/'.join(url) - + return u'/'.join(url) # Note: xlates from os.path.sep to '/' diff --git a/sendfile/backends/mod_wsgi.py b/sendfile/backends/mod_wsgi.py index 743fa9f..c3dba1d 100644 --- a/sendfile/backends/mod_wsgi.py +++ b/sendfile/backends/mod_wsgi.py @@ -4,11 +4,11 @@ def sendfile(request, filename, **kwargs): response = HttpResponse() - response['Location'] = _convert_file_to_url(filename) + response['Location'] = _convert_file_to_url( + filename, no_file_check = kwargs.get ("no_file_check", False)) # 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: '' return response - diff --git a/sendfile/backends/nginx.py b/sendfile/backends/nginx.py index f541a88..e1b70ac 100644 --- a/sendfile/backends/nginx.py +++ b/sendfile/backends/nginx.py @@ -4,7 +4,8 @@ def sendfile(request, filename, **kwargs): response = HttpResponse() - url = _convert_file_to_url(filename) + url = _convert_file_to_url( + filename, no_file_check = kwargs.get ("no_file_check", False)) response['X-Accel-Redirect'] = url.encode('utf-8') return response diff --git a/sendfile/backends/simple.py b/sendfile/backends/simple.py index 2f103ea..f5529e2 100644 --- a/sendfile/backends/simple.py +++ b/sendfile/backends/simple.py @@ -11,16 +11,17 @@ def sendfile(request, filename, **kwargs): # Respect the If-Modified-Since header. statobj = os.stat(filename) - if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), - statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]): + if not was_modified_since( + request.META.get('HTTP_IF_MODIFIED_SINCE'), + statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]): return HttpResponseNotModified() - - + + 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? @@ -52,4 +53,3 @@ def was_modified_since(header=None, mtime=0, size=0): except (AttributeError, ValueError, OverflowError): return True return False - diff --git a/sendfile/backends/xsendfile.py b/sendfile/backends/xsendfile.py index ccf3775..2acc2e9 100644 --- a/sendfile/backends/xsendfile.py +++ b/sendfile/backends/xsendfile.py @@ -3,6 +3,4 @@ def sendfile(request, filename, **kwargs): response = HttpResponse() response['X-Sendfile'] = unicode(filename).encode('utf-8') - return response - From 1d020c3b63595afc20e950c985934c56c62b1d63 Mon Sep 17 00:00:00 2001 From: J C Lawrence Date: Tue, 17 Dec 2013 14:32:06 -0800 Subject: [PATCH 5/5] Ah yes, have to pass the no_file_check down. Silly rabbit! --- sendfile/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sendfile/__init__.py b/sendfile/__init__.py index 168ad89..0437df6 100644 --- a/sendfile/__init__.py +++ b/sendfile/__init__.py @@ -75,7 +75,8 @@ def sendfile(request, filename, attachment=False, attachment_filename=None, mime else: mimetype = 'application/octet-stream' - response = _sendfile(request, filename, mimetype=mimetype) + response = _sendfile(request, filename, mimetype=mimetype, + no_file_check=no_file_check) if attachment: if attachment_filename is None: attachment_filename = os.path.basename(filename)