diff --git a/django/utils/_os.py b/django/utils/_os.py
index f7b279d..2c7b88e 100644
|
a
|
b
|
def safe_join(base, *paths):
|
| 30 | 30 | The final path must be located inside of the base path component (otherwise |
| 31 | 31 | a ValueError is raised). |
| 32 | 32 | """ |
| 33 | | # We need to use normcase to ensure we don't false-negative on case |
| 34 | | # insensitive operating systems (like Windows). |
| 35 | 33 | base = force_unicode(base) |
| 36 | 34 | paths = [force_unicode(p) for p in paths] |
| 37 | | final_path = normcase(abspathu(join(base, *paths))) |
| 38 | | base_path = normcase(abspathu(base)) |
| | 35 | final_path = abspathu(join(base, *paths)) |
| | 36 | base_path = abspathu(base) |
| 39 | 37 | base_path_len = len(base_path) |
| 40 | | # Ensure final_path starts with base_path and that the next character after |
| 41 | | # the final path is os.sep (or nothing, in which case final_path must be |
| 42 | | # equal to base_path). |
| 43 | | if not final_path.startswith(base_path) \ |
| | 38 | # Ensure final_path starts with base_path (using normcase to ensure we |
| | 39 | # don't false-negative on case insensitive operating systems like Windows) |
| | 40 | # and that the next character after the final path is os.sep (or nothing, |
| | 41 | # in which case final_path must be equal to base_path). |
| | 42 | if not normcase(final_path).startswith(normcase(base_path)) \ |
| 44 | 43 | or final_path[base_path_len:base_path_len+1] not in ('', sep): |
| 45 | 44 | raise ValueError('The joined path (%s) is located outside of the base ' |
| 46 | 45 | 'path component (%s)' % (final_path, base_path)) |
diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py
index e9e9d31..cdec187 100644
|
a
|
b
|
class FileStorageTests(unittest.TestCase):
|
| 90 | 90 | storage_class = FileSystemStorage |
| 91 | 91 | |
| 92 | 92 | def setUp(self): |
| 93 | | self.temp_dir = tempfile.mktemp() |
| 94 | | os.makedirs(self.temp_dir) |
| | 93 | self.temp_dir = tempfile.mkdtemp() |
| 95 | 94 | self.storage = self.storage_class(location=self.temp_dir, |
| 96 | 95 | base_url='/test_media_url/') |
| | 96 | # Set up a second temporary directory which is ensured to have a mixed |
| | 97 | # case name. |
| | 98 | self.temp_dir2 = tempfile.mkdtemp(suffix='aBc') |
| 97 | 99 | |
| 98 | 100 | def tearDown(self): |
| 99 | 101 | shutil.rmtree(self.temp_dir) |
| | 102 | shutil.rmtree(self.temp_dir2) |
| 100 | 103 | |
| 101 | 104 | def test_file_access_options(self): |
| 102 | 105 | """ |
| … |
… |
class FileStorageTests(unittest.TestCase):
|
| 265 | 268 | self.assertRaises(SuspiciousOperation, self.storage.exists, '..') |
| 266 | 269 | self.assertRaises(SuspiciousOperation, self.storage.exists, '/etc/passwd') |
| 267 | 270 | |
| | 271 | def test_file_storage_preserves_filename_case(self): |
| | 272 | """The storage backend should preserve case of filenames.""" |
| | 273 | # Create a storage backend associated with the mixed case name |
| | 274 | # directory. |
| | 275 | temp_storage = self.storage_class(location=self.temp_dir2) |
| | 276 | # Ask that storage backend to store a file with a mixed case filename. |
| | 277 | mixed_case = 'CaSe_SeNsItIvE' |
| | 278 | file = temp_storage.open(mixed_case, 'w') |
| | 279 | file.write('storage contents') |
| | 280 | file.close() |
| | 281 | self.assertEqual(os.path.join(self.temp_dir2, mixed_case), |
| | 282 | temp_storage.path(mixed_case)) |
| | 283 | temp_storage.delete(mixed_case) |
| | 284 | |
| 268 | 285 | class CustomStorage(FileSystemStorage): |
| 269 | 286 | def get_available_name(self, name): |
| 270 | 287 | """ |
diff --git a/tests/regressiontests/file_uploads/tests.py b/tests/regressiontests/file_uploads/tests.py
index 3c126b7..d42e027 100644
|
a
|
b
|
class FileUploadTests(TestCase):
|
| 278 | 278 | # CustomUploadError is the error that should have been raised |
| 279 | 279 | self.assertEqual(err.__class__, uploadhandler.CustomUploadError) |
| 280 | 280 | |
| | 281 | def test_filename_case_preservation(self): |
| | 282 | """ |
| | 283 | The storage backend shouldn't mess with the case of the filenames |
| | 284 | uploaded. |
| | 285 | """ |
| | 286 | # Synthesize the contents of a file upload with a mixed case filename |
| | 287 | # so we don't have to carry such a file in the Django tests source code |
| | 288 | # tree. |
| | 289 | vars = {'boundary': 'oUrBoUnDaRyStRiNg'} |
| | 290 | post_data = [ |
| | 291 | '--%(boundary)s', |
| | 292 | 'Content-Disposition: form-data; name="file_field"; ' |
| | 293 | 'filename="MiXeD_cAsE.txt"', |
| | 294 | 'Content-Type: application/octet-stream', |
| | 295 | '', |
| | 296 | 'file contents\n' |
| | 297 | '', |
| | 298 | '--%(boundary)s--\r\n', |
| | 299 | ] |
| | 300 | response = self.client.post( |
| | 301 | '/file_uploads/filename_case/', |
| | 302 | '\r\n'.join(post_data) % vars, |
| | 303 | 'multipart/form-data; boundary=%(boundary)s' % vars |
| | 304 | ) |
| | 305 | self.assertEqual(response.status_code, 200) |
| | 306 | id = int(response.content) |
| | 307 | obj = FileModel.objects.get(pk=id) |
| | 308 | # The name of the file uploaded and the file stored in the server-side |
| | 309 | # shouldn't differ. |
| | 310 | self.assertEqual(os.path.basename(obj.testfile.path), 'MiXeD_cAsE.txt') |
| | 311 | |
| 281 | 312 | class DirectoryCreationTests(unittest.TestCase): |
| 282 | 313 | """ |
| 283 | 314 | Tests for error handling during directory creation |
diff --git a/tests/regressiontests/file_uploads/urls.py b/tests/regressiontests/file_uploads/urls.py
index 9f814c4..a5c2702 100644
|
a
|
b
|
urlpatterns = patterns('',
|
| 11 | 11 | (r'^quota/broken/$', views.file_upload_quota_broken), |
| 12 | 12 | (r'^getlist_count/$', views.file_upload_getlist_count), |
| 13 | 13 | (r'^upload_errors/$', views.file_upload_errors), |
| | 14 | (r'^filename_case/$', views.file_upload_filename_case_view), |
| 14 | 15 | ) |
diff --git a/tests/regressiontests/file_uploads/views.py b/tests/regressiontests/file_uploads/views.py
index dba7522..c88c775 100644
|
a
|
b
|
def file_upload_getlist_count(request):
|
| 120 | 120 | def file_upload_errors(request): |
| 121 | 121 | request.upload_handlers.insert(0, ErroringUploadHandler()) |
| 122 | 122 | return file_upload_echo(request) |
| | 123 | |
| | 124 | def file_upload_filename_case_view(request): |
| | 125 | """ |
| | 126 | Check adding the file to the database will preserve the filename case. |
| | 127 | """ |
| | 128 | file = request.FILES['file_field'] |
| | 129 | obj = FileModel() |
| | 130 | obj.testfile.save(file.name, file) |
| | 131 | return HttpResponse('%d' % obj.pk) |
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index abfdb8d..5237bb6 100644
|
a
|
b
|
class Templates(unittest.TestCase):
|
| 161 | 161 | fs_loader = filesystem.Loader() |
| 162 | 162 | def test_template_sources(path, template_dirs, expected_sources): |
| 163 | 163 | if isinstance(expected_sources, list): |
| 164 | | # Fix expected sources so they are normcased and abspathed |
| 165 | | expected_sources = [os.path.normcase(os.path.abspath(s)) for s in expected_sources] |
| | 164 | # Fix expected sources so they are abspathed |
| | 165 | expected_sources = [os.path.abspath(s) for s in expected_sources] |
| 166 | 166 | # Test the two loaders (app_directores and filesystem). |
| 167 | 167 | func1 = lambda p, t: list(ad_loader.get_template_sources(p, t)) |
| 168 | 168 | func2 = lambda p, t: list(fs_loader.get_template_sources(p, t)) |
| … |
… |
class Templates(unittest.TestCase):
|
| 205 | 205 | if os.path.normcase('/TEST') == os.path.normpath('/test'): |
| 206 | 206 | template_dirs = ['/dir1', '/DIR2'] |
| 207 | 207 | test_template_sources('index.html', template_dirs, |
| 208 | | ['/dir1/index.html', '/dir2/index.html']) |
| | 208 | ['/dir1/index.html', '/DIR2/index.html']) |
| 209 | 209 | test_template_sources('/DIR1/index.HTML', template_dirs, |
| 210 | | ['/dir1/index.html']) |
| | 210 | ['/DIR1/index.HTML']) |
| 211 | 211 | |
| 212 | 212 | def test_loader_debug_origin(self): |
| 213 | 213 | # Turn TEMPLATE_DEBUG on, so that the origin file name will be kept with |