1. import mimetypes
    
  2. import unittest
    
  3. from os import path
    
  4. from urllib.parse import quote
    
  5. 
    
  6. from django.conf.urls.static import static
    
  7. from django.core.exceptions import ImproperlyConfigured
    
  8. from django.http import FileResponse, HttpResponseNotModified
    
  9. from django.test import SimpleTestCase, override_settings
    
  10. from django.utils.http import http_date
    
  11. from django.views.static import was_modified_since
    
  12. 
    
  13. from .. import urls
    
  14. from ..urls import media_dir
    
  15. 
    
  16. 
    
  17. @override_settings(DEBUG=True, ROOT_URLCONF="view_tests.urls")
    
  18. class StaticTests(SimpleTestCase):
    
  19.     """Tests django views in django/views/static.py"""
    
  20. 
    
  21.     prefix = "site_media"
    
  22. 
    
  23.     def test_serve(self):
    
  24.         "The static view can serve static media"
    
  25.         media_files = ["file.txt", "file.txt.gz", "%2F.txt"]
    
  26.         for filename in media_files:
    
  27.             response = self.client.get("/%s/%s" % (self.prefix, quote(filename)))
    
  28.             response_content = b"".join(response)
    
  29.             file_path = path.join(media_dir, filename)
    
  30.             with open(file_path, "rb") as fp:
    
  31.                 self.assertEqual(fp.read(), response_content)
    
  32.             self.assertEqual(
    
  33.                 len(response_content), int(response.headers["Content-Length"])
    
  34.             )
    
  35.             self.assertEqual(
    
  36.                 mimetypes.guess_type(file_path)[1],
    
  37.                 response.get("Content-Encoding", None),
    
  38.             )
    
  39. 
    
  40.     def test_chunked(self):
    
  41.         "The static view should stream files in chunks to avoid large memory usage"
    
  42.         response = self.client.get("/%s/%s" % (self.prefix, "long-line.txt"))
    
  43.         first_chunk = next(response.streaming_content)
    
  44.         self.assertEqual(len(first_chunk), FileResponse.block_size)
    
  45.         second_chunk = next(response.streaming_content)
    
  46.         response.close()
    
  47.         # strip() to prevent OS line endings from causing differences
    
  48.         self.assertEqual(len(second_chunk.strip()), 1449)
    
  49. 
    
  50.     def test_unknown_mime_type(self):
    
  51.         response = self.client.get("/%s/file.unknown" % self.prefix)
    
  52.         self.assertEqual("application/octet-stream", response.headers["Content-Type"])
    
  53.         response.close()
    
  54. 
    
  55.     def test_copes_with_empty_path_component(self):
    
  56.         file_name = "file.txt"
    
  57.         response = self.client.get("/%s//%s" % (self.prefix, file_name))
    
  58.         response_content = b"".join(response)
    
  59.         with open(path.join(media_dir, file_name), "rb") as fp:
    
  60.             self.assertEqual(fp.read(), response_content)
    
  61. 
    
  62.     def test_is_modified_since(self):
    
  63.         file_name = "file.txt"
    
  64.         response = self.client.get(
    
  65.             "/%s/%s" % (self.prefix, file_name),
    
  66.             HTTP_IF_MODIFIED_SINCE="Thu, 1 Jan 1970 00:00:00 GMT",
    
  67.         )
    
  68.         response_content = b"".join(response)
    
  69.         with open(path.join(media_dir, file_name), "rb") as fp:
    
  70.             self.assertEqual(fp.read(), response_content)
    
  71. 
    
  72.     def test_not_modified_since(self):
    
  73.         file_name = "file.txt"
    
  74.         response = self.client.get(
    
  75.             "/%s/%s" % (self.prefix, file_name),
    
  76.             HTTP_IF_MODIFIED_SINCE="Mon, 18 Jan 2038 05:14:07 GMT"
    
  77.             # This is 24h before max Unix time. Remember to fix Django and
    
  78.             # update this test well before 2038 :)
    
  79.         )
    
  80.         self.assertIsInstance(response, HttpResponseNotModified)
    
  81. 
    
  82.     def test_invalid_if_modified_since(self):
    
  83.         """Handle bogus If-Modified-Since values gracefully
    
  84. 
    
  85.         Assume that a file is modified since an invalid timestamp as per RFC
    
  86.         2616, section 14.25.
    
  87.         """
    
  88.         file_name = "file.txt"
    
  89.         invalid_date = "Mon, 28 May 999999999999 28:25:26 GMT"
    
  90.         response = self.client.get(
    
  91.             "/%s/%s" % (self.prefix, file_name), HTTP_IF_MODIFIED_SINCE=invalid_date
    
  92.         )
    
  93.         response_content = b"".join(response)
    
  94.         with open(path.join(media_dir, file_name), "rb") as fp:
    
  95.             self.assertEqual(fp.read(), response_content)
    
  96.         self.assertEqual(len(response_content), int(response.headers["Content-Length"]))
    
  97. 
    
  98.     def test_invalid_if_modified_since2(self):
    
  99.         """Handle even more bogus If-Modified-Since values gracefully
    
  100. 
    
  101.         Assume that a file is modified since an invalid timestamp as per RFC
    
  102.         2616, section 14.25.
    
  103.         """
    
  104.         file_name = "file.txt"
    
  105.         invalid_date = ": 1291108438, Wed, 20 Oct 2010 14:05:00 GMT"
    
  106.         response = self.client.get(
    
  107.             "/%s/%s" % (self.prefix, file_name), HTTP_IF_MODIFIED_SINCE=invalid_date
    
  108.         )
    
  109.         response_content = b"".join(response)
    
  110.         with open(path.join(media_dir, file_name), "rb") as fp:
    
  111.             self.assertEqual(fp.read(), response_content)
    
  112.         self.assertEqual(len(response_content), int(response.headers["Content-Length"]))
    
  113. 
    
  114.     def test_404(self):
    
  115.         response = self.client.get("/%s/nonexistent_resource" % self.prefix)
    
  116.         self.assertEqual(404, response.status_code)
    
  117. 
    
  118.     def test_index(self):
    
  119.         response = self.client.get("/%s/" % self.prefix)
    
  120.         self.assertContains(response, "Index of ./")
    
  121.         # Directories have a trailing slash.
    
  122.         self.assertIn("subdir/", response.context["file_list"])
    
  123. 
    
  124.     def test_index_subdir(self):
    
  125.         response = self.client.get("/%s/subdir/" % self.prefix)
    
  126.         self.assertContains(response, "Index of subdir/")
    
  127.         # File with a leading dot (e.g. .hidden) aren't displayed.
    
  128.         self.assertEqual(response.context["file_list"], ["visible"])
    
  129. 
    
  130.     @override_settings(
    
  131.         TEMPLATES=[
    
  132.             {
    
  133.                 "BACKEND": "django.template.backends.django.DjangoTemplates",
    
  134.                 "OPTIONS": {
    
  135.                     "loaders": [
    
  136.                         (
    
  137.                             "django.template.loaders.locmem.Loader",
    
  138.                             {
    
  139.                                 "static/directory_index.html": "Test index",
    
  140.                             },
    
  141.                         ),
    
  142.                     ],
    
  143.                 },
    
  144.             }
    
  145.         ]
    
  146.     )
    
  147.     def test_index_custom_template(self):
    
  148.         response = self.client.get("/%s/" % self.prefix)
    
  149.         self.assertEqual(response.content, b"Test index")
    
  150. 
    
  151. 
    
  152. class StaticHelperTest(StaticTests):
    
  153.     """
    
  154.     Test case to make sure the static URL pattern helper works as expected
    
  155.     """
    
  156. 
    
  157.     def setUp(self):
    
  158.         super().setUp()
    
  159.         self._old_views_urlpatterns = urls.urlpatterns[:]
    
  160.         urls.urlpatterns += static("media/", document_root=media_dir)
    
  161. 
    
  162.     def tearDown(self):
    
  163.         super().tearDown()
    
  164.         urls.urlpatterns = self._old_views_urlpatterns
    
  165. 
    
  166.     def test_prefix(self):
    
  167.         self.assertEqual(static("test")[0].pattern.regex.pattern, "^test(?P<path>.*)$")
    
  168. 
    
  169.     @override_settings(DEBUG=False)
    
  170.     def test_debug_off(self):
    
  171.         """No URLs are served if DEBUG=False."""
    
  172.         self.assertEqual(static("test"), [])
    
  173. 
    
  174.     def test_empty_prefix(self):
    
  175.         with self.assertRaisesMessage(
    
  176.             ImproperlyConfigured, "Empty static prefix not permitted"
    
  177.         ):
    
  178.             static("")
    
  179. 
    
  180.     def test_special_prefix(self):
    
  181.         """No URLs are served if prefix contains a netloc part."""
    
  182.         self.assertEqual(static("http://example.org"), [])
    
  183.         self.assertEqual(static("//example.org"), [])
    
  184. 
    
  185. 
    
  186. class StaticUtilsTests(unittest.TestCase):
    
  187.     def test_was_modified_since_fp(self):
    
  188.         """
    
  189.         A floating point mtime does not disturb was_modified_since (#18675).
    
  190.         """
    
  191.         mtime = 1343416141.107817
    
  192.         header = http_date(mtime)
    
  193.         self.assertFalse(was_modified_since(header, mtime))
    
  194. 
    
  195.     def test_was_modified_since_empty_string(self):
    
  196.         self.assertTrue(was_modified_since(header="", mtime=1))