static.py 5.1 KB

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