Fixed #24399 -- Made filesystem loaders use more specific exceptions.

This commit is contained in:
Preston Timmons 2015-03-02 13:31:14 -06:00 committed by Aymeric Augustin
parent 85757d0e79
commit 70123cf084
5 changed files with 50 additions and 43 deletions

View File

@ -2,6 +2,7 @@
Wrapper for loading templates from the filesystem. Wrapper for loading templates from the filesystem.
""" """
import errno
import io import io
from django.core.exceptions import SuspiciousFileOperation from django.core.exceptions import SuspiciousFileOperation
@ -37,6 +38,7 @@ class Loader(BaseLoader):
try: try:
with io.open(filepath, encoding=self.engine.file_charset) as fp: with io.open(filepath, encoding=self.engine.file_charset) as fp:
return fp.read(), filepath return fp.read(), filepath
except IOError: except IOError as e:
pass if e.errno != errno.ENOENT:
raise
raise TemplateDoesNotExist(template_name) raise TemplateDoesNotExist(template_name)

View File

@ -269,10 +269,6 @@ class ExceptionReporter(object):
def format_path_status(self, path): def format_path_status(self, path):
if not os.path.exists(path): if not os.path.exists(path):
return "File does not exist" return "File does not exist"
if not os.path.isfile(path):
return "Not a file"
if not os.access(path, os.R_OK):
return "File is not readable"
return "File exists" return "File exists"
def get_traceback_data(self): def get_traceback_data(self):

View File

@ -224,6 +224,19 @@ the keyword argument ``clear=True``.
assignment for many-to-many relations and as a consequence now use the new assignment for many-to-many relations and as a consequence now use the new
behavior. behavior.
Filesystem-based template loaders catch more specific exceptions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When using the :class:`filesystem.Loader <django.template.loaders.filesystem.Loader>`
or :class:`app_directories.Loader <django.template.loaders.app_directories.Loader>`
template loaders, earlier versions of Django raised a
:exc:`~django.template.TemplateDoesNotExist` error if a template source existed
but was unreadable. This could happen under many circumstances, such as if
Django didn't have permissions to open the file, or if the template source was
a directory. Now, Django only silences the exception if the template source
does not exist. All other situations result in the original ``IOError`` being
raised.
Miscellaneous Miscellaneous
~~~~~~~~~~~~~ ~~~~~~~~~~~~~

View File

@ -3,6 +3,7 @@ from __future__ import unicode_literals
import os.path import os.path
import sys import sys
import tempfile
import types import types
import unittest import unittest
from contextlib import contextmanager from contextlib import contextmanager
@ -176,7 +177,16 @@ class EggLoaderTests(SimpleTestCase):
class FileSystemLoaderTests(SimpleTestCase): class FileSystemLoaderTests(SimpleTestCase):
def setUp(self): def setUp(self):
self.engine = Engine() self.engine = Engine(dirs=[TEMPLATE_DIR])
@contextmanager
def set_dirs(self, dirs):
original_dirs = self.engine.dirs
self.engine.dirs = dirs
try:
yield
finally:
self.engine.dirs = original_dirs
@contextmanager @contextmanager
def source_checker(self, dirs): def source_checker(self, dirs):
@ -189,12 +199,8 @@ class FileSystemLoaderTests(SimpleTestCase):
expected_sources, expected_sources,
) )
original_dirs = self.engine.dirs with self.set_dirs(dirs):
self.engine.dirs = dirs
try:
yield check_sources yield check_sources
finally:
self.engine.dirs = original_dirs
def test_directory_security(self): def test_directory_security(self):
with self.source_checker(['/dir1', '/dir2']) as check_sources: with self.source_checker(['/dir1', '/dir2']) as check_sources:
@ -238,6 +244,27 @@ class FileSystemLoaderTests(SimpleTestCase):
check_sources('index.html', ['/dir1/index.html', '/DIR2/index.html']) check_sources('index.html', ['/dir1/index.html', '/DIR2/index.html'])
check_sources('/DIR1/index.HTML', ['/DIR1/index.HTML']) check_sources('/DIR1/index.HTML', ['/DIR1/index.HTML'])
def test_file_does_not_exist(self):
with self.assertRaises(TemplateDoesNotExist):
self.engine.get_template('doesnotexist.html')
@unittest.skipIf(
sys.platform == 'win32',
"Python on Windows doesn't have working os.chmod().",
)
def test_permissions_error(self):
with tempfile.NamedTemporaryFile() as tmpfile:
tmpdir = os.path.dirname(tmpfile.name)
tmppath = os.path.join(tmpdir, tmpfile.name)
os.chmod(tmppath, 0o0222)
with self.set_dirs([tmpdir]):
with self.assertRaisesMessage(IOError, 'Permission denied'):
self.engine.get_template(tmpfile.name)
def test_notafile_error(self):
with self.assertRaisesMessage(IOError, 'Is a directory'):
self.engine.get_template('first')
class AppDirectoriesLoaderTest(SimpleTestCase): class AppDirectoriesLoaderTest(SimpleTestCase):

View File

@ -7,7 +7,6 @@ import importlib
import inspect import inspect
import os import os
import re import re
import shutil
import sys import sys
import tempfile import tempfile
from unittest import skipIf from unittest import skipIf
@ -152,36 +151,6 @@ class DebugViewTests(TestCase):
response = self.client.get(reverse('raises_template_does_not_exist', kwargs={"path": template_name})) response = self.client.get(reverse('raises_template_does_not_exist', kwargs={"path": template_name}))
self.assertContains(response, "%s (File does not exist)" % template_path, status_code=500, count=1) self.assertContains(response, "%s (File does not exist)" % template_path, status_code=500, count=1)
@skipIf(sys.platform == "win32", "Python on Windows doesn't have working os.chmod() and os.access().")
def test_template_loader_postmortem_notreadable(self):
"""Tests for not readable file"""
with tempfile.NamedTemporaryFile() as tmpfile:
template_name = tmpfile.name
tempdir = os.path.dirname(tmpfile.name)
template_path = os.path.join(tempdir, template_name)
os.chmod(template_path, 0o0222)
with override_settings(TEMPLATES=[{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [tempdir],
}]):
response = self.client.get(reverse('raises_template_does_not_exist', kwargs={"path": template_name}))
self.assertContains(response, "%s (File is not readable)" % template_path, status_code=500, count=1)
def test_template_loader_postmortem_notafile(self):
"""Tests for not being a file"""
try:
template_path = tempfile.mkdtemp()
template_name = os.path.basename(template_path)
tempdir = os.path.dirname(template_path)
with override_settings(TEMPLATES=[{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [tempdir],
}]):
response = self.client.get(reverse('raises_template_does_not_exist', kwargs={"path": template_name}))
self.assertContains(response, "%s (Not a file)" % template_path, status_code=500, count=1)
finally:
shutil.rmtree(template_path)
def test_no_template_source_loaders(self): def test_no_template_source_loaders(self):
""" """
Make sure if you don't specify a template, the debug view doesn't blow up. Make sure if you don't specify a template, the debug view doesn't blow up.