Fixed #4952 -- Fixed the `get_template_sources` functions of the `app_directories` and `filesystem` template loaders to not return paths outside of given template directories. Both functions now make use of a new `safe_join` utility function. Thanks to SmileyChris for help with the patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5750 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
7a16a1d81a
commit
304381616f
|
@ -1,9 +1,14 @@
|
||||||
# Wrapper for loading templates from "template" directories in installed app packages.
|
"""
|
||||||
|
Wrapper for loading templates from "template" directories in INSTALLED_APPS
|
||||||
|
packages.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.template import TemplateDoesNotExist
|
from django.template import TemplateDoesNotExist
|
||||||
import os
|
from django.utils._os import safe_join
|
||||||
|
|
||||||
# At compile time, cache the directories to search.
|
# At compile time, cache the directories to search.
|
||||||
app_template_dirs = []
|
app_template_dirs = []
|
||||||
|
@ -28,8 +33,14 @@ for app in settings.INSTALLED_APPS:
|
||||||
app_template_dirs = tuple(app_template_dirs)
|
app_template_dirs = tuple(app_template_dirs)
|
||||||
|
|
||||||
def get_template_sources(template_name, template_dirs=None):
|
def get_template_sources(template_name, template_dirs=None):
|
||||||
for template_dir in app_template_dirs:
|
if not template_dirs:
|
||||||
yield os.path.join(template_dir, template_name)
|
template_dirs = app_template_dirs
|
||||||
|
for template_dir in template_dirs:
|
||||||
|
try:
|
||||||
|
yield safe_join(template_dir, template_name)
|
||||||
|
except ValueError:
|
||||||
|
# The joined path was located outside of template_dir.
|
||||||
|
pass
|
||||||
|
|
||||||
def load_template_source(template_name, template_dirs=None):
|
def load_template_source(template_name, template_dirs=None):
|
||||||
for filepath in get_template_sources(template_name, template_dirs):
|
for filepath in get_template_sources(template_name, template_dirs):
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
# Wrapper for loading templates from the filesystem.
|
"""
|
||||||
|
Wrapper for loading templates from the filesystem.
|
||||||
|
"""
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template import TemplateDoesNotExist
|
from django.template import TemplateDoesNotExist
|
||||||
import os
|
from django.utils._os import safe_join
|
||||||
|
|
||||||
def get_template_sources(template_name, template_dirs=None):
|
def get_template_sources(template_name, template_dirs=None):
|
||||||
if not template_dirs:
|
if not template_dirs:
|
||||||
template_dirs = settings.TEMPLATE_DIRS
|
template_dirs = settings.TEMPLATE_DIRS
|
||||||
for template_dir in template_dirs:
|
for template_dir in template_dirs:
|
||||||
yield os.path.join(template_dir, template_name)
|
try:
|
||||||
|
yield safe_join(template_dir, template_name)
|
||||||
|
except ValueError:
|
||||||
|
# The joined path was located outside of template_dir.
|
||||||
|
pass
|
||||||
|
|
||||||
def load_template_source(template_name, template_dirs=None):
|
def load_template_source(template_name, template_dirs=None):
|
||||||
tried = []
|
tried = []
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
from os.path import join, normcase, abspath, sep
|
||||||
|
|
||||||
|
def safe_join(base, *paths):
|
||||||
|
"""
|
||||||
|
Join one or more path components to the base path component intelligently.
|
||||||
|
Return a normalized, absolute version of the final path.
|
||||||
|
|
||||||
|
The final path must be located inside of the base path component (otherwise
|
||||||
|
a ValueError is raised).
|
||||||
|
"""
|
||||||
|
# We need to use normcase to ensure we don't false-negative on case
|
||||||
|
# insensitive operating systems (like Windows).
|
||||||
|
final_path = normcase(abspath(join(base, *paths)))
|
||||||
|
base_path = normcase(abspath(base))
|
||||||
|
base_path_len = len(base_path)
|
||||||
|
# Ensure final_path starts with base_path and that the next character after
|
||||||
|
# the final path is os.sep (or nothing, in which case final_path must be
|
||||||
|
# equal to base_path).
|
||||||
|
if not final_path.startswith(base_path) \
|
||||||
|
or final_path[base_path_len:base_path_len+1] not in ('', sep):
|
||||||
|
raise ValueError('the joined path is located outside of the base path'
|
||||||
|
' component')
|
||||||
|
return final_path
|
|
@ -6,13 +6,17 @@ if __name__ == '__main__':
|
||||||
# before importing 'template'.
|
# before importing 'template'.
|
||||||
settings.configure()
|
settings.configure()
|
||||||
|
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
|
from django.template.loaders import app_directories, filesystem
|
||||||
from django.utils.translation import activate, deactivate, install, ugettext as _
|
from django.utils.translation import activate, deactivate, install, ugettext as _
|
||||||
from django.utils.tzinfo import LocalTimezone
|
from django.utils.tzinfo import LocalTimezone
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from unicode import unicode_tests
|
from unicode import unicode_tests
|
||||||
import unittest
|
|
||||||
|
|
||||||
# Some other tests we would like to run
|
# Some other tests we would like to run
|
||||||
__test__ = {
|
__test__ = {
|
||||||
|
@ -75,6 +79,46 @@ class UTF8Class:
|
||||||
return u'ŠĐĆŽćžšđ'.encode('utf-8')
|
return u'ŠĐĆŽćžšđ'.encode('utf-8')
|
||||||
|
|
||||||
class Templates(unittest.TestCase):
|
class Templates(unittest.TestCase):
|
||||||
|
def test_loaders_security(self):
|
||||||
|
def test_template_sources(path, template_dirs, expected_sources):
|
||||||
|
# Fix expected sources so they are normcased and abspathed
|
||||||
|
expected_sources = [os.path.normcase(os.path.abspath(s)) for s in expected_sources]
|
||||||
|
# Test app_directories loader
|
||||||
|
sources = app_directories.get_template_sources(path, template_dirs)
|
||||||
|
self.assertEqual(list(sources), expected_sources)
|
||||||
|
# Test filesystem loader
|
||||||
|
sources = filesystem.get_template_sources(path, template_dirs)
|
||||||
|
self.assertEqual(list(sources), expected_sources)
|
||||||
|
|
||||||
|
template_dirs = ['/dir1', '/dir2']
|
||||||
|
test_template_sources('index.html', template_dirs,
|
||||||
|
['/dir1/index.html', '/dir2/index.html'])
|
||||||
|
test_template_sources('/etc/passwd', template_dirs,
|
||||||
|
[])
|
||||||
|
test_template_sources('etc/passwd', template_dirs,
|
||||||
|
['/dir1/etc/passwd', '/dir2/etc/passwd'])
|
||||||
|
test_template_sources('../etc/passwd', template_dirs,
|
||||||
|
[])
|
||||||
|
test_template_sources('../../../etc/passwd', template_dirs,
|
||||||
|
[])
|
||||||
|
test_template_sources('/dir1/index.html', template_dirs,
|
||||||
|
['/dir1/index.html'])
|
||||||
|
test_template_sources('../dir2/index.html', template_dirs,
|
||||||
|
['/dir2/index.html'])
|
||||||
|
test_template_sources('/dir1blah', template_dirs,
|
||||||
|
[])
|
||||||
|
test_template_sources('../dir1blah', template_dirs,
|
||||||
|
[])
|
||||||
|
|
||||||
|
# Case insensitive tests (for win32). Not run unless we're on
|
||||||
|
# a case insensitive operating system.
|
||||||
|
if os.path.normcase('/TEST') == os.path.normpath('/test'):
|
||||||
|
template_dirs = ['/dir1', '/DIR2']
|
||||||
|
test_template_sources('index.html', template_dirs,
|
||||||
|
['/dir1/index.html', '/dir2/index.html'])
|
||||||
|
test_template_sources('/DIR1/index.HTML', template_dirs,
|
||||||
|
['/dir1/index.html'])
|
||||||
|
|
||||||
def test_templates(self):
|
def test_templates(self):
|
||||||
# NOW and NOW_tz are used by timesince tag tests.
|
# NOW and NOW_tz are used by timesince tag tests.
|
||||||
NOW = datetime.now()
|
NOW = datetime.now()
|
||||||
|
|
Loading…
Reference in New Issue