Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixes #8593 -- better handling of safe_join case sensitivity on windo…

…ws. Thanks for the initial patch, ramiro.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16267 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit fcf7fbc68cadb7efd8bc779c0e189389d6475463 1 parent dab90dd
@SmileyChris SmileyChris authored
View
15 django/utils/_os.py
@@ -30,17 +30,16 @@ def safe_join(base, *paths):
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).
base = force_unicode(base)
paths = [force_unicode(p) for p in paths]
- final_path = normcase(abspathu(join(base, *paths)))
- base_path = normcase(abspathu(base))
+ final_path = abspathu(join(base, *paths))
+ base_path = abspathu(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) \
+ # 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):
raise ValueError('The joined path (%s) is located outside of the base '
'path component (%s)' % (final_path, base_path))
View
21 tests/regressiontests/file_storage/tests.py
@@ -90,13 +90,16 @@ class FileStorageTests(unittest.TestCase):
storage_class = FileSystemStorage
def setUp(self):
- self.temp_dir = tempfile.mktemp()
- os.makedirs(self.temp_dir)
+ self.temp_dir = tempfile.mkdtemp()
self.storage = self.storage_class(location=self.temp_dir,
base_url='/test_media_url/')
+ # Set up a second temporary directory which is ensured to have a mixed
+ # case name.
+ self.temp_dir2 = tempfile.mkdtemp(suffix='aBc')
def tearDown(self):
shutil.rmtree(self.temp_dir)
+ shutil.rmtree(self.temp_dir2)
def test_file_access_options(self):
"""
@@ -265,6 +268,20 @@ def test_file_storage_prevents_directory_traversal(self):
self.assertRaises(SuspiciousOperation, self.storage.exists, '..')
self.assertRaises(SuspiciousOperation, self.storage.exists, '/etc/passwd')
+ def test_file_storage_preserves_filename_case(self):
+ """The storage backend should preserve case of filenames."""
+ # Create a storage backend associated with the mixed case name
+ # directory.
+ temp_storage = self.storage_class(location=self.temp_dir2)
+ # Ask that storage backend to store a file with a mixed case filename.
+ mixed_case = 'CaSe_SeNsItIvE'
+ file = temp_storage.open(mixed_case, 'w')
+ file.write('storage contents')
+ file.close()
+ self.assertEqual(os.path.join(self.temp_dir2, mixed_case),
+ temp_storage.path(mixed_case))
+ temp_storage.delete(mixed_case)
+
class CustomStorage(FileSystemStorage):
def get_available_name(self, name):
"""
View
31 tests/regressiontests/file_uploads/tests.py
@@ -278,6 +278,37 @@ def handle_uncaught_exception(self, request, resolver, exc_info):
# CustomUploadError is the error that should have been raised
self.assertEqual(err.__class__, uploadhandler.CustomUploadError)
+ def test_filename_case_preservation(self):
+ """
+ The storage backend shouldn't mess with the case of the filenames
+ uploaded.
+ """
+ # Synthesize the contents of a file upload with a mixed case filename
+ # so we don't have to carry such a file in the Django tests source code
+ # tree.
+ vars = {'boundary': 'oUrBoUnDaRyStRiNg'}
+ post_data = [
+ '--%(boundary)s',
+ 'Content-Disposition: form-data; name="file_field"; '
+ 'filename="MiXeD_cAsE.txt"',
+ 'Content-Type: application/octet-stream',
+ '',
+ 'file contents\n'
+ '',
+ '--%(boundary)s--\r\n',
+ ]
+ response = self.client.post(
+ '/file_uploads/filename_case/',
+ '\r\n'.join(post_data) % vars,
+ 'multipart/form-data; boundary=%(boundary)s' % vars
+ )
+ self.assertEqual(response.status_code, 200)
+ id = int(response.content)
+ obj = FileModel.objects.get(pk=id)
+ # The name of the file uploaded and the file stored in the server-side
+ # shouldn't differ.
+ self.assertEqual(os.path.basename(obj.testfile.path), 'MiXeD_cAsE.txt')
+
class DirectoryCreationTests(unittest.TestCase):
"""
Tests for error handling during directory creation
View
1  tests/regressiontests/file_uploads/urls.py
@@ -11,4 +11,5 @@
(r'^quota/broken/$', views.file_upload_quota_broken),
(r'^getlist_count/$', views.file_upload_getlist_count),
(r'^upload_errors/$', views.file_upload_errors),
+ (r'^filename_case/$', views.file_upload_filename_case_view),
)
View
9 tests/regressiontests/file_uploads/views.py
@@ -120,3 +120,12 @@ def file_upload_getlist_count(request):
def file_upload_errors(request):
request.upload_handlers.insert(0, ErroringUploadHandler())
return file_upload_echo(request)
+
+def file_upload_filename_case_view(request):
+ """
+ Check adding the file to the database will preserve the filename case.
+ """
+ file = request.FILES['file_field']
+ obj = FileModel()
+ obj.testfile.save(file.name, file)
+ return HttpResponse('%d' % obj.pk)
View
8 tests/regressiontests/templates/tests.py
@@ -161,8 +161,8 @@ def test_loaders_security(self):
fs_loader = filesystem.Loader()
def test_template_sources(path, template_dirs, expected_sources):
if isinstance(expected_sources, list):
- # Fix expected sources so they are normcased and abspathed
- expected_sources = [os.path.normcase(os.path.abspath(s)) for s in expected_sources]
+ # Fix expected sources so they are abspathed
+ expected_sources = [os.path.abspath(s) for s in expected_sources]
# Test the two loaders (app_directores and filesystem).
func1 = lambda p, t: list(ad_loader.get_template_sources(p, t))
func2 = lambda p, t: list(fs_loader.get_template_sources(p, t))
@@ -205,9 +205,9 @@ def test_template_sources(path, template_dirs, expected_sources):
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'])
+ ['/dir1/index.html', '/DIR2/index.html'])
test_template_sources('/DIR1/index.HTML', template_dirs,
- ['/dir1/index.html'])
+ ['/DIR1/index.HTML'])
def test_loader_debug_origin(self):
# Turn TEMPLATE_DEBUG on, so that the origin file name will be kept with
Please sign in to comment.
Something went wrong with that request. Please try again.