mirror of https://github.com/django/django.git
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.core.exceptions import ImproperlyConfigured
|
||||
from django.template import TemplateDoesNotExist
|
||||
import os
|
||||
from django.utils._os import safe_join
|
||||
|
||||
# At compile time, cache the directories to search.
|
||||
app_template_dirs = []
|
||||
|
@ -28,8 +33,14 @@ for app in settings.INSTALLED_APPS:
|
|||
app_template_dirs = tuple(app_template_dirs)
|
||||
|
||||
def get_template_sources(template_name, template_dirs=None):
|
||||
for template_dir in app_template_dirs:
|
||||
yield os.path.join(template_dir, template_name)
|
||||
if not template_dirs:
|
||||
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):
|
||||
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.template import TemplateDoesNotExist
|
||||
import os
|
||||
from django.utils._os import safe_join
|
||||
|
||||
def get_template_sources(template_name, template_dirs=None):
|
||||
if not template_dirs:
|
||||
template_dirs = settings.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):
|
||||
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'.
|
||||
settings.configure()
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django import template
|
||||
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.tzinfo import LocalTimezone
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from unicode import unicode_tests
|
||||
import unittest
|
||||
|
||||
# Some other tests we would like to run
|
||||
__test__ = {
|
||||
|
@ -75,6 +79,46 @@ class UTF8Class:
|
|||
return u'ŠĐĆŽćžšđ'.encode('utf-8')
|
||||
|
||||
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):
|
||||
# NOW and NOW_tz are used by timesince tag tests.
|
||||
NOW = datetime.now()
|
||||
|
|
Loading…
Reference in New Issue