diff --git a/django/utils/_os.py b/django/utils/_os.py
--- a/django/utils/_os.py
+++ b/django/utils/_os.py
@@ -21,6 +21,11 @@
             path = join(os.getcwdu(), path)
         return normpath(path)
 
+if os.name == 'nt':
+    _normcase = lambda s: s.replace("/", "\\")
+else:
+    _normcase = normcase
+
 def safe_join(base, *paths):
     """
     Joins one or more path components to the base path component intelligently.
@@ -29,18 +34,24 @@
     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))
+    fp = normcase(final_path)
+    final_path = _normcase(final_path)
+
+    base_path = abspathu(base)
+    bp = normcase(base_path)
+    base_path = _normcase(base_path)
+
     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):
+    # the base path portion of final_path is os.sep (or nothing, in which case
+    # final_path must be equal to base_path).
+    # We need to use normcase in these checks to ensure we don't false-negative
+    # on case insensitive operating systems (like Windows).
+    if not fp.startswith(bp) \
+       or fp[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
diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py
--- a/tests/regressiontests/file_storage/tests.py
+++ b/tests/regressiontests/file_storage/tests.py
@@ -32,10 +32,18 @@
         self.temp_dir = tempfile.mktemp()
         os.makedirs(self.temp_dir)
         self.storage = self.storage_class(location=self.temp_dir)
-    
+
+        # Set up a second temporary directory with a unique mixed case name
+        self.temp_dir2 = tempfile.mktemp()
+        dn, bn = os.path.split(self.temp_dir2)
+        bn = bn[0].swapcase() + bn[1:-1] + bn[-1].swapcase()
+        self.temp_dir2 = os.path.join(dn, bn)
+        os.makedirs(self.temp_dir2)
+
     def tearDown(self):
         os.rmdir(self.temp_dir)
-        
+        os.rmdir(self.temp_dir2)
+
     def test_file_access_options(self):
         """
         Standard file access options are available, and work as expected.
@@ -61,6 +69,18 @@
         self.assertRaises(SuspiciousOperation, self.storage.exists, '..')
         self.assertRaises(SuspiciousOperation, self.storage.exists, '/etc/passwd')
 
+    def test_file_storage_preservers_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):
         """
diff --git a/tests/regressiontests/file_uploads/tests.py b/tests/regressiontests/file_uploads/tests.py
--- a/tests/regressiontests/file_uploads/tests.py
+++ b/tests/regressiontests/file_uploads/tests.py
@@ -1,5 +1,6 @@
 #! -*- coding: utf-8 -*-
 import os
+import os.path
 import errno
 import shutil
 import unittest
@@ -251,6 +252,33 @@
             # 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.
+        """
+        # Synthetize 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
+        OUR_BOUNDARY='oUrBoUnDaRyStRiNg'
+        post_data = [
+                '--%s' % OUR_BOUNDARY,
+                'Content-Disposition: form-data; name="file_field"; filename="MiXeD_cAsE.txt"',
+                'Content-Type: application/octet-stream',
+                '',
+                'file contents\n'
+                '',
+                '--%s--\r\n' % OUR_BOUNDARY,
+                ]
+        response = self.client.post('/file_uploads/filename_case/', '\r\n'.join(post_data),
+                'multipart/form-data; boundary=%s' % OUR_BOUNDARY)
+        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
diff --git a/tests/regressiontests/file_uploads/urls.py b/tests/regressiontests/file_uploads/urls.py
--- a/tests/regressiontests/file_uploads/urls.py
+++ b/tests/regressiontests/file_uploads/urls.py
@@ -10,4 +10,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),
 )
diff --git a/tests/regressiontests/file_uploads/views.py b/tests/regressiontests/file_uploads/views.py
--- a/tests/regressiontests/file_uploads/views.py
+++ b/tests/regressiontests/file_uploads/views.py
@@ -111,3 +111,12 @@
 def file_upload_errors(request):
     request.upload_handlers.insert(0, ErroringUploadHandler())
     return file_upload_echo(request)
+
+def file_upload_filename_case_view(request):
+    # Adding the file to the database should succeed
+    file = request.FILES['file_field']
+    obj = FileModel()
+    obj.testfile.save(file.name, file)
+
+    return HttpResponse('%d' % obj.pk)
+
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -146,9 +146,9 @@
         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_token_smart_split(self):
         # Regression test for #7027
