web/lib/django/views/static.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     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.html``.
       
    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     mimetype = mimetypes.guess_type(fullpath)[0] or 'application/octet-stream'
       
    60     if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
       
    61                               statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]):
       
    62         return HttpResponseNotModified(mimetype=mimetype)
       
    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 }}</title>
       
    77   </head>
       
    78   <body>
       
    79     <h1>Index of {{ directory }}</h1>
       
    80     <ul>
       
    81       {% ifnotequal directory "/" %}
       
    82       <li><a href="../">../</a></li>
       
    83       {% endifnotequal %}
       
    84       {% for f in file_list %}
       
    85       <li><a href="{{ f|urlencode }}">{{ f }}</a></li>
       
    86       {% endfor %}
       
    87     </ul>
       
    88   </body>
       
    89 </html>
       
    90 """
       
    91 
       
    92 def directory_index(path, fullpath):
       
    93     try:
       
    94         t = loader.select_template(['static/directory_index.html',
       
    95                 'static/directory_index'])
       
    96     except TemplateDoesNotExist:
       
    97         t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE, name='Default directory index template')
       
    98     files = []
       
    99     for f in os.listdir(fullpath):
       
   100         if not f.startswith('.'):
       
   101             if os.path.isdir(os.path.join(fullpath, f)):
       
   102                 f += '/'
       
   103             files.append(f)
       
   104     c = Context({
       
   105         'directory' : path + '/',
       
   106         'file_list' : files,
       
   107     })
       
   108     return HttpResponse(t.render(c))
       
   109 
       
   110 def was_modified_since(header=None, mtime=0, size=0):
       
   111     """
       
   112     Was something modified since the user last downloaded it?
       
   113 
       
   114     header
       
   115       This is the value of the If-Modified-Since header.  If this is None,
       
   116       I'll just return True.
       
   117 
       
   118     mtime
       
   119       This is the modification time of the item we're talking about.
       
   120 
       
   121     size
       
   122       This is the size of the item we're talking about.
       
   123     """
       
   124     try:
       
   125         if header is None:
       
   126             raise ValueError
       
   127         matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
       
   128                            re.IGNORECASE)
       
   129         header_mtime = mktime_tz(parsedate_tz(matches.group(1)))
       
   130         header_len = matches.group(3)
       
   131         if header_len and int(header_len) != size:
       
   132             raise ValueError
       
   133         if mtime > header_mtime:
       
   134             raise ValueError
       
   135     except (AttributeError, ValueError):
       
   136         return True
       
   137     return False