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 |