| 1 |
""" |
|---|
| 2 |
Views and functions for serving static files. These are only to be used |
|---|
| 3 |
during development, and SHOULD NOT be used in a production setting. |
|---|
| 4 |
""" |
|---|
| 5 |
|
|---|
| 6 |
import mimetypes |
|---|
| 7 |
import os |
|---|
| 8 |
import posixpath |
|---|
| 9 |
import re |
|---|
| 10 |
import stat |
|---|
| 11 |
import urllib |
|---|
| 12 |
from email.Utils import parsedate_tz, mktime_tz |
|---|
| 13 |
|
|---|
| 14 |
from django.template import loader |
|---|
| 15 |
from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified |
|---|
| 16 |
from django.template import Template, Context, TemplateDoesNotExist |
|---|
| 17 |
from django.utils.http import http_date |
|---|
| 18 |
|
|---|
| 19 |
def serve(request, path, document_root=None, show_indexes=False): |
|---|
| 20 |
""" |
|---|
| 21 |
Serve static files below a given point in the directory structure. |
|---|
| 22 |
|
|---|
| 23 |
To use, put a URL pattern such as:: |
|---|
| 24 |
|
|---|
| 25 |
(r'^(?P<path>.*)$', 'django.views.static.serve', {'document_root' : '/path/to/my/files/'}) |
|---|
| 26 |
|
|---|
| 27 |
in your URLconf. You must provide the ``document_root`` param. You may |
|---|
| 28 |
also set ``show_indexes`` to ``True`` if you'd like to serve a basic index |
|---|
| 29 |
of the directory. This index view will use the template hardcoded below, |
|---|
| 30 |
but if you'd like to override it, you can create a template called |
|---|
| 31 |
``static/directory_index``. |
|---|
| 32 |
""" |
|---|
| 33 |
|
|---|
| 34 |
# Clean up given path to only allow serving files below document_root. |
|---|
| 35 |
path = posixpath.normpath(urllib.unquote(path)) |
|---|
| 36 |
path = path.lstrip('/') |
|---|
| 37 |
newpath = '' |
|---|
| 38 |
for part in path.split('/'): |
|---|
| 39 |
if not part: |
|---|
| 40 |
# Strip empty path components. |
|---|
| 41 |
continue |
|---|
| 42 |
drive, part = os.path.splitdrive(part) |
|---|
| 43 |
head, part = os.path.split(part) |
|---|
| 44 |
if part in (os.curdir, os.pardir): |
|---|
| 45 |
# Strip '.' and '..' in path. |
|---|
| 46 |
continue |
|---|
| 47 |
newpath = os.path.join(newpath, part).replace('\\', '/') |
|---|
| 48 |
if newpath and path != newpath: |
|---|
| 49 |
return HttpResponseRedirect(newpath) |
|---|
| 50 |
fullpath = os.path.join(document_root, newpath) |
|---|
| 51 |
if os.path.isdir(fullpath): |
|---|
| 52 |
if show_indexes: |
|---|
| 53 |
return directory_index(newpath, fullpath) |
|---|
| 54 |
raise Http404, "Directory indexes are not allowed here." |
|---|
| 55 |
if not os.path.exists(fullpath): |
|---|
| 56 |
raise Http404, '"%s" does not exist' % fullpath |
|---|
| 57 |
# Respect the If-Modified-Since header. |
|---|
| 58 |
statobj = os.stat(fullpath) |
|---|
| 59 |
if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), |
|---|
| 60 |
statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]): |
|---|
| 61 |
return HttpResponseNotModified() |
|---|
| 62 |
mimetype = mimetypes.guess_type(fullpath)[0] or 'application/octet-stream' |
|---|
| 63 |
contents = open(fullpath, 'rb').read() |
|---|
| 64 |
response = HttpResponse(contents, mimetype=mimetype) |
|---|
| 65 |
response["Last-Modified"] = http_date(statobj[stat.ST_MTIME]) |
|---|
| 66 |
response["Content-Length"] = len(contents) |
|---|
| 67 |
return response |
|---|
| 68 |
|
|---|
| 69 |
DEFAULT_DIRECTORY_INDEX_TEMPLATE = """ |
|---|
| 70 |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
|---|
| 71 |
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> |
|---|
| 72 |
<head> |
|---|
| 73 |
<meta http-equiv="Content-type" content="text/html; charset=utf-8" /> |
|---|
| 74 |
<meta http-equiv="Content-Language" content="en-us" /> |
|---|
| 75 |
<meta name="robots" content="NONE,NOARCHIVE" /> |
|---|
| 76 |
<title>Index of {{ directory|escape }}</title> |
|---|
| 77 |
</head> |
|---|
| 78 |
<body> |
|---|
| 79 |
<h1>Index of {{ directory|escape }}</h1> |
|---|
| 80 |
<ul> |
|---|
| 81 |
{% for f in file_list %} |
|---|
| 82 |
<li><a href="{{ f|urlencode }}">{{ f|escape }}</a></li> |
|---|
| 83 |
{% endfor %} |
|---|
| 84 |
</ul> |
|---|
| 85 |
</body> |
|---|
| 86 |
</html> |
|---|
| 87 |
""" |
|---|
| 88 |
|
|---|
| 89 |
def directory_index(path, fullpath): |
|---|
| 90 |
try: |
|---|
| 91 |
t = loader.get_template('static/directory_index') |
|---|
| 92 |
except TemplateDoesNotExist: |
|---|
| 93 |
t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE, name='Default directory index template') |
|---|
| 94 |
files = [] |
|---|
| 95 |
for f in os.listdir(fullpath): |
|---|
| 96 |
if not f.startswith('.'): |
|---|
| 97 |
if os.path.isdir(os.path.join(fullpath, f)): |
|---|
| 98 |
f += '/' |
|---|
| 99 |
files.append(f) |
|---|
| 100 |
c = Context({ |
|---|
| 101 |
'directory' : path + '/', |
|---|
| 102 |
'file_list' : files, |
|---|
| 103 |
}) |
|---|
| 104 |
return HttpResponse(t.render(c)) |
|---|
| 105 |
|
|---|
| 106 |
def was_modified_since(header=None, mtime=0, size=0): |
|---|
| 107 |
""" |
|---|
| 108 |
Was something modified since the user last downloaded it? |
|---|
| 109 |
|
|---|
| 110 |
header |
|---|
| 111 |
This is the value of the If-Modified-Since header. If this is None, |
|---|
| 112 |
I'll just return True. |
|---|
| 113 |
|
|---|
| 114 |
mtime |
|---|
| 115 |
This is the modification time of the item we're talking about. |
|---|
| 116 |
|
|---|
| 117 |
size |
|---|
| 118 |
This is the size of the item we're talking about. |
|---|
| 119 |
""" |
|---|
| 120 |
try: |
|---|
| 121 |
if header is None: |
|---|
| 122 |
raise ValueError |
|---|
| 123 |
matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header, |
|---|
| 124 |
re.IGNORECASE) |
|---|
| 125 |
header_mtime = mktime_tz(parsedate_tz(matches.group(1))) |
|---|
| 126 |
header_len = matches.group(3) |
|---|
| 127 |
if header_len and int(header_len) != size: |
|---|
| 128 |
raise ValueError |
|---|
| 129 |
if mtime > header_mtime: |
|---|
| 130 |
raise ValueError |
|---|
| 131 |
except (AttributeError, ValueError): |
|---|
| 132 |
return True |
|---|
| 133 |
return False |
|---|