Commit b865009d authored by Alex Gaynor's avatar Alex Gaynor
Browse files

Fixed #12397 -- allow safe_join to work with the root file system path, which...

Fixed #12397 -- allow safe_join to work with the root file system path, which means you can have your root template or file upload path at this location. You almost certainly don't want to do this, except in *very* limited sandboxed situations.
parent 3a10bcc9
Loading
Loading
Loading
Loading
+10 −7
Original line number Diff line number Diff line
import os
import stat
from os.path import join, normcase, normpath, abspath, isabs, sep
from os.path import join, normcase, normpath, abspath, isabs, sep, dirname
from django.utils.encoding import force_text
from django.utils import six

@@ -41,13 +41,16 @@ def safe_join(base, *paths):
    paths = [force_text(p) for p in paths]
    final_path = abspathu(join(base, *paths))
    base_path = abspathu(base)
    base_path_len = len(base_path)
    # Ensure final_path starts with base_path (using normcase to ensure we
    # don't false-negative on case insensitive operating systems like Windows)
    # 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 normcase(final_path).startswith(normcase(base_path)) \
       or final_path[base_path_len:base_path_len+1] not in ('', sep):
    # don't false-negative on case insensitive operating systems like Windows),
    # further, one of the following conditions must be true:
    #  a) The next character is the path separator (to prevent conditions like
    #     safe_join("/dir", "/../d"))
    #  b) The final path must be the same as the base path.
    #  c) The base path must be the most root path (meaning either "/" or "C:\\")
    if (not normcase(final_path).startswith(normcase(base_path + sep)) and
        normcase(final_path) != normcase(base_path) and
        dirname(normcase(base_path)) != normcase(base_path)):
        raise ValueError('The joined path (%s) is located outside of the base '
                         'path component (%s)' % (final_path, base_path))
    return final_path
+21 −0
Original line number Diff line number Diff line
from django.utils import unittest
from django.utils._os import safe_join


class SafeJoinTests(unittest.TestCase):
    def test_base_path_ends_with_sep(self):
        self.assertEqual(
            safe_join("/abc/", "abc"),
            "/abc/abc",
        )

    def test_root_path(self):
        self.assertEqual(
            safe_join("/", "path"),
            "/path",
        )

        self.assertEqual(
            safe_join("/", ""),
            "/",
        )
+1 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ from .http import TestUtilsHttp
from .ipv6 import TestUtilsIPv6
from .jslex import JsToCForGettextTest, JsTokensTest
from .module_loading import CustomLoader, DefaultLoader, EggLoader
from .os_utils import SafeJoinTests
from .regex_helper import NormalizeTests
from .simplelazyobject import TestUtilsSimpleLazyObject
from .termcolors import TermColorTests