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
@@ -5,6 +5,7 @@
 >>> import tempfile
 >>> from django.core.files.storage import FileSystemStorage
 >>> from django.core.files.base import ContentFile
+>>> import os.path
 
 # Set up a unique temporary directory
 >>> import os
@@ -43,6 +44,26 @@
   ...
 SuspiciousOperation: Attempted access to '/etc/passwd' denied.
 
+# Case should be preserved by the storage backend
+
+# Set up a unique temporary directory with a mixed case name
+>>> temp_dir2 = tempfile.mktemp()
+>>> dn, bn = os.path.split(temp_dir2)
+>>> bn = bn[0].swapcase() + bn[1:-1] + bn[-1].swapcase()
+>>> temp_dir2 = os.path.join(dn, bn)
+>>> os.makedirs(temp_dir2)
+
+>>> temp_storage2 = FileSystemStorage(location=temp_dir2)
+
+# Ask the storage backend to store a file with a mixed case filename
+>>> mixed_case = 'CaSe_SeNsItIvE'
+>>> file = temp_storage2.open(mixed_case, 'w')
+>>> file.write('storage contents')
+>>> file.close()
+>>> os.path.join(temp_dir2, mixed_case) == temp_storage2.path(mixed_case)
+True
+>>> temp_storage2.delete(mixed_case)
+
 # Custom storage systems can be created to customize behavior
 
 >>> class CustomStorage(FileSystemStorage):
@@ -70,8 +91,9 @@
 >>> custom_storage.delete(first)
 >>> custom_storage.delete(second)
 
-# Cleanup the temp dir
+# Cleanup the temp dirs
 >>> os.rmdir(temp_dir)
+>>> os.rmdir(temp_dir2)
 
 
 # Regression test for #8156: files with unicode names I can't quite figure out the
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,4 +1,5 @@
 import os
+import os.path
 import errno
 import shutil
 import unittest
@@ -229,6 +230,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
@@ -9,4 +9,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
@@ -88,3 +88,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
@@ -137,9 +137,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
